面向对象初步

wangqing 2020-04-25

1. 注意:  Python支持 (1)面向过程 (2)面向对象 (3) 函数式编程等多种编程范式
2. 面向对象编程的思想主要是针对大型软件设计而来的. 面向对象编程使得程序的扩展性更强,可读性更好,使得编程可以像搭积木一样简单.
3. 面向对象编程将数据和操作数据相关的方法封装到对象中,组织到吗和数据的方式更加接尽人的思维,从而大大提高了编程的效率.
4. Python完全采用了面向对象的思想,是真正面向对象的编程语言,完全支持面向对象的基本功能,例如: (1)继承(2)多态(3)封装等.   Python中一切都是对象,我们前面学习的(1)数据类型(2)函数都是对象.

面向对象和面向过程区别

1. 面向过程编程更加关注的是"程序的逻辑流程",是一种"执行者"思维,适合编写小规模的程序.
    (1) 面向过程思考方式 => 完成这件事情的步骤是(1.... 2... 3...)
2. 面向对象更加关注的是"软件中对象之间的关系",是一种"设计者"思维,适合编写大规模的程序.
    (1) 面向对象思考方式 => 完成这件事情需要由哪些对象来配合(需要调用哪些类)
3. 解决简单问题使用面向过程; 解决复杂问题: 宏观上是以哦那个面向对象把握,微观处理上仍然是面向过程. (一个优秀的软件设计师必然从程序员干起,底层的实现细节肯定也是十分了解)

对象的进化

面向对象初步

类的定义

1. Python中,"一切皆对象". 类也成为"类对象",类的实例也称为"实例对象".
2. 定义类的语法
class 类名: 
    类体
3. 定义类的要点: 
    (1) 类名必须符合"标识符"的规则,规定首字母大写,多个单词采用"驼峰原则".
    (2) 类体中我们可以定义属性和方法
    (3) 属性(变量)用来描述数据,方法(即函数)用来描述这些数据相关的操作.
# code01_一个典型类的定义.py

class Student:
    def __init__(self, name: str, score: int) -> None:  # 注意: self参数必须位于第一位
        self.name = name
        self.score = score

    def say_score(self)->None:  # 注意: self参数必须位于第一位
        print("{0}的分数为:{1}".format(self.name, self.score))



def main():
    # 解释器会默认把s1的地址传递给参数self
    s1 = Student("rowry", 98)  # 类名() 调用构造方法
    s1.say_score()  # 调用对象的方法


if __name__ == "__main__":
    main()

__init__构造方法和__new__方法

__init__()的要点如下:

1. 名称固定,必须为 __init__().
2. 第一个参数固定(类中的方法第一参数都必须为self),必须为self. 
    (1)self指的就是刚刚创建好的实例对象(的内存地址).
    (2) Python中的self相当于C++中的self指针, Java和C#中的this关键字.
    (3) Python中self必须为构造参数的第一个参数,名字可以任意修改,但是一般遵守惯例,都叫做self.
3. 构造函数通常用来初始化实例化对象的实例属性(变量) => 给属性赋初值
    def __init__(self,name,score):
        self.name = name
        self.score = score
4. 通过"类名(参数)"来调用构造函数(__init__). 解释器创建创建好对象后将对象的内存地址返回给相应的变量. 
    (1) 比如: s1 = Student("rowry",98)

5. __init__(self) 用于初始化创建好的对象. 初始化的意思是 "给实例属性赋值".
6. __new__(self) 用于创建对象. 一般是不需要重定义该方法的,知道有这个方法即可.
7. 如果我们不定义__init__(self)方法,系统会提供一个默认的无参__init__(self),如果我们定义了带参数的__init__(self),系统不创建默认的__init__(self).

实例属性和实例方法

实例属性

实例属性(变量)是从属于实例对象的属性,也称为"实例变量". 他的使用有如下几个要点:
1. 实例属性一般在 __init__() 中通过如下代码定义: self.实例属性名 = 初始值
2. 在本例的其他实例方法中,也是通过self进行访问: self.实例属性名
3. 创建实例对象后,通过实例对象访问: 
    obj01 =  类名()  # 创建对象,调用__init__() 初始化属性
    obj01.实例属性名 = 值  # 可以给已有属性赋值,也可以新加属性

实例方法

实例方法只是一个指向,并不是重新在定义一个方法没有必要

实例方法是从属于实例对象的方法. 
1. 实例方法定义
    def 方法名(self,[,形参列表]):
        函数体
2. 实例方法调用: 对象.方法名([实参列表])
3. 要点
    (1) 定义实例方法时,第一个参数必须为self,和前面一样,self指当前的实例对象.
    (2) 调用实例方法时,不需要也不能给self传参. self由解释器自动传参.
4. 函数和方法的区别
    (1) 都是用来完成一个功能的语句块,本质一样.
    (2) 方法调用时,通过对象来调用. 方法从属于特定实例对象,普通函数没有这个特点.
    (3) 直观上看,方法定义时需要传递self,函数不需要
5. 实例对象的实例方法调用本质
    (1) 对象.实例方法() <=> 类名.实例方法(对象)
    (2) 一般我们使用前者进行调用,然后解释器会将前者翻译为后者
6. 其他操作
    (1) dir(对象)可以获得对象的所有属性,方法
    (2) 对象.__dict__ 对象的属性字典, 获得自定义的对象属性变量字典
    (3) pass 空语句
    (4) isinstance(对象,类[型]) 判断"对象"是不是"指定类型"

面向对象初步

# code02_实例属性和实例方法.py

class Student:
    def __init__(self, name: str, score: int) -> None:  # 注意: self参数必须位于第一位
        self.name = name
        self.score = score

    def say_score(self) -> None:  # 注意: self参数必须位于第一位
        self.xxx = "xxx"  # 动态新增的实例属性
        print("{0}的分数为:{1}   test新增实例属性{2}".format(self.name, self.score, self.xxx))


def main():
    # 解释器会默认把s1的地址传递给参数self
    s1 = Student("rowry", 98)  # 类名() 调用构造方法
    s1.say_score()  # 调用对象的方法

    # Python对象可以动态添加属性(变量)
    s1.age = 18
    s1.salary = 3000
    s1.abs = abs  # 其实是一样的,只是新增加了一个实例属性变量,这个变量指向abs函数
    print(f"{s1.name}的年龄:{s1.age},工资:{s1.salary}")

    # 重新用该类创建创建的对象是没有s1新增加的属性的
    s2 = Student("张三", 60)  # 是没有 age 和  salary 的

    # 实例方法的本质
    # 对象.方法名()  <=> 类名.方法名(对象)
    s2.say_score()
    Student.say_score(s2)

    # dir(对象) 查看对象的所有属性和方法
    print(dir(s1))
    print(dir(s2))

    # 对象.__dict__ 对象的属性字典(自定义的属性)
    print(s1.__dict__)
    print(s2.__dict__)

    # isinstance(对象,类型) 判断给对象是不是该类型的实例
    print(isinstance(s1, Student))


if __name__ == "__main__":
    main()

类对象,类属性,类方法,静态方法

类对象

1. 我们签名将类定义格式中, "class 类名: ". 实际上,当解释器执行class语句时,就会创建一个类对象 (类本身就是一个对象, 是 type 类型的对象)
# code03_类对象的生成.py
class Student:
    pass


def main():
    print(type(Student))  # 所有自定义类都是type类型
    print(id(Student))  # 已经有Student这个对象了,执行class时候就自动创建Student这个类对象

    s1 = Student()  # 现在相当于调用 __init__ 和 __new__
    # 下面这种方式应该更加帮助理解类对象
    stu = Student
    s2 = stu()


if __name__ == "__main__":
    main()

类属性

1. 类属性是从属于"类对象"的属性,也称为"类变量". 由于类属性从归属于类对象,可以被所有实例对象共享.
2. 类属性的定义方式: 
    class 类名:
        类变量名 = 初始值
3. 在类中或者类的外部,可以通过"类名.类变量名"来进行读写
# code04_类属性.py


class Student:
    company = "ByteDance"  # 类属性
    count = 0  # 类属性

    def __init__(self, name, score):
        self.name = name  # 实例属性
        self.score = score  # 实例属性
        Student.count += 1  # 使用类属性

    def say_score(self):  # 实例方法
        print("我的公司是:", Student.company)
        print(f"{self.name}的分数是:{self.score}")


def main():
    s1 = Student("张三", 80)
    s1.say_score()
    s2 = Student("李四", 85)
    s2.say_score()
    print("一共创建{0}个Student对象".format(Student.count))

if __name__ == "__main__":
    main()

类方法

1. 类方法是从属于"类对象"的方法. 类方法通过装饰器@classmethod来定义,格式如下: 
    @classmethod
    def 类方法名(cls,[,形参列表]):
        函数体
2. 类方法的要点:
    (1) @classmethod必须位于方法上面一行
    (2) 第一个cls参数必须有; cls指的是"类对象"本身,与self用法一致 (可以改名,但是一般不改)
    (3) 调用类方法格式: "类名.类方法名(参数列表)". 参数列表中,不需要也不能给cls传值.
    (4) 类方法中访问实例属性和实例方法会导致错误(类创建了,对象还没有创建,当然不能使用实例属性和实例方法)
    (5) 子类继承父类方法是,传入cls是子类对象,而非父类对象.
# code05_类方法.py
class Student:
    company = "ByteDance"

    @classmethod
    def print_company(cls):  # 类方法第一个参数必须为cls
        print(cls.company)


def main():
    Student.print_company()  # 直接运行类方法即可(class 已经创建了这个对象了)


if __name__ == "__main__":
    main()

静态方法

1. Python中允许定义与"类对象"无关的方法,称为"静态方法".
2. "静态方法"和模块中定义普通函数没有区别,只不过"静态方法"放到了"类的名字空间里面",需要通过"类调用".
3. 静态放啊通过装饰器@staticmethod来定义,格式如下:
    @staticmethod
    def 静态方法名([形参列表]):
        函数体
4. 静态方法要点:
    (1) @staticmethod必须位于方法上面一行
    (2) 调用静态方法格式: "类名.静态方法名(参数列表)"
    (3) 静态方法中访问实例属性和实例方法会导致错误(需要传递self,但是还没有self呢)
# code06_静态方法.py

class Student:
    company = "ByteDance"

    @staticmethod
    def add(a, b) -> None:  # 静态方法(一般是不操作类属性的)
        print("{0}+{1}={2}".format(a, b, (a + b)))


def main():
    Student.add(10, 20)  # 调用类的静态方法


if __name__ == "__main__":
    main()

内存分析实例对象和类对象创建过程(重要)

面向对象初步

__del__方法(析构函数)和垃圾回收机制

1. 在创建对象时,系统会自动调用__init__(),在对象被清理时,系统也会自动调用一个__del__(),这个方法就是析构函数.
2. Python实现自动的垃圾回收,当对象没有被引用时(引用计数为0),由垃圾回收器调用__del__().
3. 我们也可以通过del关键字,手动调用__del__().
4. 系统会自动提供__del__(),一般不需要自定义析构方法.
# code07_析构方法.py

class Person:
    def __del__(self):
        print("销毁对象:", self)


def main():
    p1 = Person()
    p2 = Person()
    del p2  # 手动触发__del__()
    print("程序结束")  # 程序结束收p1也会被自动回收


if __name__ == "__main__":
    main()

__call__方法和可调用对象

对象() 实际上调用的是对象的 call()
定义了 call() 的对象,称为"可调用对象",即该对象可以像函数一样被调用. (函数本身也是一个对象)

# code08_可调用对象.py

class SalaryAccount:
    def __call__(self, salary):
        print("算工资啦...")
        yearSalary = salary * 12
        return f"年薪为:{yearSalary}"


def main():
    s = SalaryAccount()
    print(s(3000))  # 对象() <=> 函数()


if __name__ == "__main__":
    main()

方法没有重载

Python是没有方法重载的,如果出现同名函数,只保留最后一个(相当于不断的重新给变量赋值)

1. 在其他语言中,可以定义多个重名的方法,只要保证方法签名唯一即可. 
    (1) 方法签名包含3个部分 (a)方法名 (b)参数数量 (c)参数类型
2. Python中,方法的参数没有声明类型(调用时确定参数的类型),参数的数量也可以由可变参数控制.因此,Python中式没有方法的重载的. 定义一个方法即可有多种调用方式,相当于实现了其他语言中的方法重载.
3. 如果我们在类体中定义了多个重名的方法,只有最后一个方法有效.

方法的动态性

可以动态的修改 类方法,实例方法
本质就是方法也是一个对象,修改其实想到与给 类属性,对象实例属性 重新赋值一个对象引用而已
注意: 理解一下方法本质也是属性,只不过通过()执行 call() 而已

私有属性和素有方法(实现封装)

1. Python对于类的成员没有严格的访问控制限制,这与其他面向对象语言有区别.
2. 关于私有属性和私有方法,有如下要点:
    (1) 通常我们约定,两个下划线开头的属性是私有的(private),其他为公共的(public).
    (2) 类内部可以直接访问私有属性和私有方法
    (3) 类外部不能直接访问私有属性和私有方法,但是可以间接以  _类名__私有属性  这样进行私有属性的访问(Python解释器的私有属性做的一些小变通)
# code09_私有属性和私有方法.py

class Employee:
    __count = 100  # 类变量也是可以私有的

    def __init__(self, name, age):
        self.__name = name  # 定义私有属性
        self.__age = age

    def __work(self):  # 私有方法
        pass


def main():
    e = Employee("张三", 18)
    # 私有属性和私有方法的存储名字
    print(e.__dict__)  # {‘_Employee__name‘: ‘张三‘, ‘_Employee__age‘: 18}
    print(dir(e))  # ‘_Employee__age‘, ‘_Employee__name‘, ‘_Employee__work‘


if __name__ == "__main__":
    main()

@propety装饰器

@property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查.

1. @property 可以讲一个方法的调用方式变成"属性调用".
2. 对于下面的第一个程序,要修改score,如果使用直接调用属性修改 s.score = 220 首先220是不合法的分数,其次是直接这么修改虽然方便但是非常不安全
3. 使用getter和setter虽然可以安全的修改score,但是不如直接  对象.属性=值  这么修改来的方便,所以 => 既能参数检查,又能类似属性这样简单的方式访问类的变量 <= Python内置的 @property装饰器就是负责把一个方法变成属性调用的.
4. 对getter()上加上@property, 然后会生成 @xxx.setter  这个装饰器
# code10_property装饰器01.py

class Student:
    def __init__(self, name: str, score: int):
        self.name = name
        self.__score = score

    # 现在设置一个 setter() 和 getter() 保证数据安全
    def get_score(self):
        return self.__score

    def set_score(self, value: int):
        if not isinstance(value, int):
            raise ValueError("score must be an integer!")
        if value < 0 or value > 100:
            raise ValueError("score must between 0~100!")
        self.__score = value


def main():
    s = Student("张三", 60)
    # s.set_score(-20)
    s.set_score(20)
    print(s.get_score())


if __name__ == "__main__":
    main()
# code10_property装饰器02.py


class Student:
    def __init__(self, name: str, score: int):
        self.name = name
        self.__score = score

    @property
    def score(self):
        return self.__score

    @score.setter
    def score(self, value: int):
        if not isinstance(value, int):
            raise ValueError("score must be an integer!")
        if value < 0 or value > 100:
            raise ValueError("score must between 0~100!")
        self.__score = value


def main():
    s = Student("张三", 60)
    # 把对属性的一个赋值操作(应该使用函数进行判断的) @property简化为直接赋值
    # s.score = -20  
    s.score = 20
    print(s.score)


if __name__ == "__main__":
    main()

属性和方法命名总结

1. _xxx : 保护成员,不能用 from module import *  导入, 只有类对象和子类对象才能访问这些成员.
2. __xxx__ : 系统定义的特殊成员
3. __xxx : 类中的私有成员,只有类对象自己能访问,子类对象也不能访问. (但是私有成员在类外部可以通过 "对象名._类名__xxx"这种特殊方式访问. Python不存在严格意义的私有成员)

类编码风格

1. 类名首字母大写,多个单词之间采用驼峰原则.
2. 实例名,模块名采用小写,多个单词之间采用下划线隔开
3. 每个类,应紧跟"文档字符串",说明这个类的作用
4. 可以使用空行组织代码,但不能滥用. 在类中,使用一个空行隔开方法; 模块中,使用两个空行而开多个类.

实操作业

相关推荐