面向对象
对象的相关定义:
对象:对象指的是类的实例。一个对象有自己的状态(属性)、行为(方法)和唯一的标识(内存地址);所有相同类型的对象所具有的结构和行为在它们共同的类中被定义。
状态:包括这个对象已有的属性(通常是类里面已经定义好的),再加上对象具有的当前属性值(往往是动态的)。
行为:指一个对象如何影响外界和被外界影响,表现为对象自身状态的改变和信息的传递。
标识:值一个对象所具有的区别于所有其他对象的属性(本质上指内存中所创建的对象的地址)。
简言之,对象应具有属性(即状态)、方法(即行为)和标识。标识是在内存中自动完成的,不用去管它,平时主要用到属性和方法。
类:类描述了所创建的对象共同的属性和方法(数据属性和方法属性)。
实例:类是对象的定义,实例才是真实的物件,是具体的个体。实例只有数据属性,没有方法属性。实例调用的方法是类中的方法,由作用域决定。
属性:
类属性:在类中定义的变量,即类的属性。
>>> class A: ... x = 7 # 类中定义变量x ... >>> A.x # 通过类名调用类中变量x 7
类的属性本质上就是类中的变量,它的值不依赖于任何实例,只是由类中所写的变量赋值语句确定。所以这个类属性又叫静态变量或静态数据。类属性只会在创建类的时候定义一次,不会因为对象的创建而重新创建。
类属性可以增加、删除或者修改。
>>> A.y = 9 # 直接向类A中增加新的属性 >>> A.y 9 >>> del A.x # 删除类中的已有属性 >>> A.x Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: type object 'A' has no attribute 'x' >>> A.y = 99 # 修改类中的属性值 >>> A.y 99
查看类中的内容:
>>> dir(A) ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'y'] >>> A.__doc__ # 显示类的文档 >>> A.__name__ # 以字符串的形式返回类的名字 'A' >>> A.__base__ # 类的父类 <class 'object'> >>> A.__dict__ # 以字典的形式显示类的所有属性 mappingproxy({'__doc__': None, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, 'y': 99}) >>> A.__module__ # 类所在的模块 '__main__'
类属性的总结:
类属性跟类绑定,可以自定义、删除、修改值,也可以随时增加类属性;
每个类都有一些特殊属性。
创建实例:创建实例,就是调用类。
当类被调用之后:
创建实例对象;
检查是否实现__init__()方法。如果没有实现,返回实例对象;如果实现了__init__(),则调用该方法,并且将实例对象作为第一个参数self传递进去。
__init__()是一个特殊方法,它一般规定一些属性或者初始化,让类具有一些基本的特征。__init__()方法没有return语句。
实例属性:与类属性类似,实例所具有的属性叫做实例属性。每个实例的属性定义在该实例的命名空间(内存地址)中,通过实例可以在不同的方法里直接共享实例属性。不同于函数中的局部变量,只能在函数自己内部使用,不能在其他函数中直接调用。
>>> class A: ... x = 7 ... >>> a = A() >>> A.x # 类属性 7 >>> a.x # 实例属性 7 >>> a.x += 1 # 修改实例属性 >>> a.x 8 >>> A.x # 类属性不变 7
类属性和实例属性的区别在于,类属性是直接定义在类中的属性,而实例属性是通过实例化的对象去操作与类中同名的引用。相当于实例新建了一个新的属性,这个属性与类中的属性同名。所以修改实例属性不会影响类属性。
类属性能够影响实例属性,这是因为实例就是通过调用类来建立的。即实例属性跟着类属性的改变而改变。以上都是类中变量引用不可变对象的结果,比如字符串和数。
类中变量引用可变数据时,则情形会不同,因为可变数据能够进行原地修改。
>>> class B: ... lst = [1, 2, 3] # 类中属性是可变类型 ... >>> b = B() # 类的实例 >>> B.lst [1, 2, 3] >>> b.lst [1, 2, 3] >>> b.lst.append(9) # 修改实例的属性 >>> b.lst [1, 2, 3, 9] >>> B.lst # 类属性也变化了 [1, 2, 3, 9] >>> B.lst.append('python') # 修改类属性 >>> B.lst [1, 2, 3, 9, 'python'] >>> b.lst # 实例属性也变化了 [1, 2, 3, 9, 'python']
当类中变量引用的是可变对象时,类属性和实例属性都能直接修改这个对象,从而影响另一方的值。
如果增加一个类属性,相应的也增加了一个实例属性。增加实例属性,类属性不会跟着增加。
>>> B.y = 'java' # 直接增加了一个类属性 >>> b.y # 实例属性相应的增加 'java' >>> b.z = 99 # 增加一个实例属性 >>> b.z 99 >>> B.z # 类中没有此属性 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: type object 'B' has no attribute 'z'
以上的实例属性或类属性,都是源自类中的变量所引用的值,是静态数据。还有一种实例属性的生存方法,是在实例创建的时候,通过__init__()初始化方法建立,是动态数据。
self的作用:类中的任何方法,第一个参数都是self。
看下面的例子:
>>> class Stu: ... def __init__(self): ... self.name = 'Jack' ... self.age = 18 ... print(self) # 打印self的内存地址 ... print(type(self)) # 打印self的类型 ... >>> stu = Stu() # 实例化时自动执行类中__init__()方法 <__main__.Stu object at 0x7fc4dd792780> # self的地址 <class '__main__.Stu'> # self的类型 >>> print(stu) # 与self对应的实例的地址 <__main__.Stu object at 0x7fc4dd792780> >>> type(stu) # 实例的类型 <class '__main__.Stu'>
上面可以看到,self的内存地址和类型与实例stu完全一致。这说明self就是Stu类的实例,即self和stu引用了同一个实例对象。当创建实例的时候,实例变量作为第一个参数,被python解释器传给了self,所以初始化函数中的self.name就是初始化实例的属性。self和实例可以理解为一个主内、一个主外,实例对象的引用会被传给self,self就是实例在类的内部的形式。
这样,在类的内部,通过self实例,类中所有方法都能承接self实例对象,它的属性也被带到类中的每个方法之中,实现了数据在类内部的流转。
方法:
绑定方法和非绑定方法:
>>> class Foo: ... def bar(self): ... print('This is a normal method of class') ... >>> foo = Foo() # 实例化类对象 >>> foo.bar() # 实例调用类中方法 This is a normal method of class >>> Foo.bar(foo) # 类名调用类中方法,必须传入实例对象 This is a normal method of class >>> Foo.bar() # 类名调用类中方法,不传入实例对象时,报错 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: bar() missing 1 required positional argument: 'self'
报错信息告诉我们,bar()方法缺少一个参数self,它是一个实例,所以要传入一个实例。
>>> foo.bar # 实例调用方法对象 <bound method Foo.bar of <__main__.Foo object at 0x7fc4dd792940>> >>> Foo.bar # 类名调用方法对象 <function Foo.bar at 0x7fc4dd77c9d8>
通过类来获取方法的时候,得到的是非绑定方法对象;
过实例来获取方法的时候,得到的是绑定方法对象。
类方法:
看一个例子:
>>> class Foo: ... lang = 'Java' # 类属性 ... def __init__(self): ... self.lang = 'Python' # 实例属性 ... >>> def get_class_attr(cls): ... return cls.lang ... >>> if __name__ == '__main__': ... print('Foo.lang:', Foo.lang) # 打印类属性 ... r = get_class_attr(Foo) ... print('get class attribute:', r) # 打印类属性 ... f = Foo() ... print('instance attribute:', f.lang) # 打印实例属性 ... Foo.lang: Java get class attribute: Java instance attribute: Python
上面的例子中,在类Foo中,定义了一个属性lang = 'Java',这是类属性;在初始化方法中,又定义了self.lang = 'Python',这是实例属性。
在这个程序中,较特殊的函数是get_class_attr(cls),它写在类的外面,而这个函数又只能调用前面的那个类对象,这使得类和函数的耦合性太强,应将类和函数融为一体,写成类方法。
>>> class Foo: ... lang = 'Java' ... def __init__(self): ... self.lang = 'Python' ... @classmethod # 定义类方法 ... def get_class_attr(cls): ... return cls.lang ... >>> if __name__ == '__main__': ... print('Foo.lang:', Foo.lang) ... r = Foo.get_class_attr() # 直接使用类名调用类方法 ... print('get class attribute:', r) ... f = Foo() ... print('instance attribute:', f.lang) ... print('instance get_class_attr:', f.get_class_attr()) # 也可使用实例调用类方法 ... Foo.lang: Java get class attribute: Java instance attribute: Python instance get_class_attr: Java
在上面的程序中,装饰器@classmethod所装饰的方法get_class_attr(cls)和类中的其他方法不同,它的第一个参数的cls(习惯用这个)表示类对象,即参数cls引用的对象是类对象Foo。
所谓类方法,就是在类里面定义的方法。该方法由装饰器@classmethod装饰,其第一个参数cls引用这个类对象,即将类本身作为对象传入到此方法中。类方法可以直接用类名调用,也可用实例进行调用。
静态方法:
>>> import random >>> class Foo: ... def __init__(self): ... self.name = 'Jack' ... def get_name(self, age): ... if self.select(age): # 通过实例self调用静态方法 ... return self.name ... else: ... return 'the name is secret.' ... @staticmethod # 定义静态方法 ... def select(n): ... a = random.randint(1, 100) ... return a - n > 0 ... >>> if __name__ == '__main__': ... f = Foo() ... name = f.get_name(18) ... print(name) ... the name is secret.
上面程序中有一个函数select(n),这个函数虽然定义在类中,但它却是一个独立的方法,跟类没有关系。这个方法前要加装饰器@staticmethod,我们称之为静态方法。静态方法不以self作为第一个参数,当调用它的时候,可以同过实例调用,也可以通过类名调用。它存在的意义在于将要在类中调用的函数定于在类的命名空间中,便于维护。
当类中要定义的某个方法和实例有关系,则定义为实例方法;当类中要定义的某个方法和类属性有关系,则定义为类方法;当类中要定义的某个既和类有关系又和实例有关系,则定义为静态方法。
继承:
继承可以使得子类别获得父类别的所有属性和方法,而不需要再次编写相同的代码。子类别也可以重新定义新的属性,重写父类中的方法,使得子类别获得与父类别不同的功能。另外,也可为子类别追加新的属性和方法。
>>> class Person: ... def __init__(self, name): ... self.name = name ... def height(self, m): ... h = dict((['height', m],)) ... return h ... >>> class Girl(Person): ... def get_name(self): ... return self.name ... >>> if __name__ == '__main__': ... p = Girl('yang') ... print(p.get_name()) ... print(p.height(166)) ... yang {'height': 166}
上面的Girl类继承了类Person,那么Girl类就拥有了Person类中的所有方法和属性。当子类创建实例或调用父类中的方法时,也必须按照父类中方法的要求传入参数。
子类中重写了父类中的方法或属性时,父类中的相应方法或属性将不再被继承到父类中,如果要在子类中继承父类中被重写的方法或属性,怎么办?
>>> class Girl(Person): ... def __init__(self, name): ... Person.__init__(self, name) # 子类继承父类中的同名方法 ... # super(Girl, self).__init__(name) # 使用super继承父类中的同名方法 ... self.real_name = 'zhangsan' ... def get_name(self): ... return self.name ... >>> if __name__ == '__main__': ... g = Girl('yang') ... print(g.real_name) ... print(g.get_name()) ... print(g.height(166)) ... zhangsan yang {'height': 166}
要继承父类中的同名方法或属性时,可以以类方法的方式调用Person.__init__(self, name),也可以以super的方式调用super(Girl, self).__init__(name)。
多继承父类调用顺序:使用类名. __mro__查看父类的调用顺序。新式类采用广度优先的顺序;旧式类采用深度优先的顺序。
>>> class Person: ... pass ... >>> class Stu(Person): ... pass ... >>> print(Stu.__mro__) (<class '__main__.Stu'>, <class '__main__.Person'>, <class 'object'>)
接口的作用是定义子类必须实现的方法,python没有直接实现接口,需要依靠abc模块来实现接口的定义与继承。
多态和封装:
Python不检查传入对象的类型,这种方式被称为“隐式类型”或“结构式类型”,也被称为“鸭子类型”。“鸭子类型”意味着可以向任何对象发送任何消息,语言只关心该对象能否接收该消息,不强求该对象是否为某一种特定的类型。
Python中的封装私有化是将准备私有化的属性(方法)的名字前加双下划线。python中的私有化本质上是将以__开头的私有属性重命名为_类名__属性名。也可以以_开头定义私有属性和方法,以这种方式定义,只是一种规范的约定。
>>> class PrivateMe: ... def __init__(self): ... self.__name = 'Hello' # 私有化属性 ... def get_name(self): # 类中显式方法 ... return self.__name ... @property # 装饰器property ... def name(self): ... return self.__name ... >>> if __name__ == '__main__': ... p = PrivateMe() ... print(p.get_name()) # 得到类中私有属性 ... print(p.name) ... Hello Hello
私有化的方法或属性,按约定在类的外面无法调用。如果要调用类中私有属性,可以用property装饰器。用了@property之后,在调用那个方法的时候,用的是p.name的形式,就好像在调用一个属性一样。除此之外,也可在类中定义显式方法,在显式方法中返回或者输出私有属性,再通过调用类中的显式方法得到私有属性。
私有方法和属性不能被继承:
>>> class Person: ... def __init__(self): ... self.name = 'zhangsan' ... self.__age = 22 # 私有属性 ... def eat(self): ... print('eating...') ... def __sleep(self): # 私有方法 ... print('sleeping...') ... >>> class Stu(Person): ... pass ... >>> stu = Stu() >>> stu.name 'zhangsan' >>> stu.__age # 私有属性不能被继承在类中,私有属性被重命名为_Person__age Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Stu' object has no attribute '__age' >>> stu.eat() eating... >>> stu.__sleep() # 私有方法不能被继承在类中,私有方法被重命名为_Person__sleep Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Stu' object has no attribute '__sleep'
类的魔法方法:
__init__()方法:初始化对象的属性。
__new__()方法:创建对象,只用于创建对象。
对象在实例化时,分三步完成:
1.先调用__new__方法创建对象;
2.创建对象后,对象引用空间存在,此时会在对象引用空间内再调用__init__方法初始化对象,也就是说,只要对象引用空间存在了,有另一个变量也指向此对象空间时,__init__方法也会调用。__init__方法的调用不是通过__new__方法来实现的,而是只要有了对象空间,__init__方法便会调用;
3.最后返回对象。
只不过对于__new__方法,它只是用来创建对象,只有python内部才能够创建对象,我们几乎不会去重写它。
>>> class A: ... def __init__(self): ... print('执行初始化') ... def __new__(cls): # 重写了父类的__new__方法,没有创建对象的操作,对象不会被创建 ... print('执行创建对象') ... >>> a = A() # 先调用重写的__new__方法,方法中没有调用__init__方法,对象也不会初始化 执行创建对象 改进代码: >>> class A: ... def __init__(self): ... print('执行初始化') ... def __new__(cls): # __new__方法通过类创建对象,所以参数传入cls ... print('执行创建对象') ... return super().__new__(cls) # 调用父类__new__方法完成对象的创建 ... >>> a = A() 执行创建对象 执行初始化
__new__方法通常用来创建单例对象:
我们知道,每实例化一个对象时,都会创建其自己的引用空间。现在我们要求创建单例对象,即不论实例化多少个对象,它们的引用空间都是同一个,也即所有的对象名都指向同一个对象空间的引用。
>>> class A: ... __instance = None # 定义一个类属性,用于存放实例化的对象 ... def __new__(cls): ... if cls.__instance == None: # 如果类属性为空,则执行第一次创建 ... cls.__instance = super().__new__(cls) ... return cls.__instance # 返回创建的对象 ... else: # 如果类属性不为空,则不再执行对象创建,直接返回第一次创建的对象 ... return cls.__instance ... >>> a = A() >>> print(id(a)) # 两个对象名指向了同一个对象的引用空间 4341814776 >>> b = A() >>> print(id(b)) 4341814776
只初始化一次对象:
当创建了单例对象后,每个实例化的对象名都指向了同一个对象空间,但是当在实例化对象并初始化对象时,初始化方法__init__都会被调用,从而执行多次初始化。
>>> class A: ... __instance = None ... def __new__(cls, name): ... if cls.__instance == None: ... cls.__instance = super().__new__(cls) ... return cls.__instance ... else: ... return cls.__instance ... def __init__(self, name): ... self.name = name ... >>> a = A('zhangsan') # 执行初始化方法__init__ >>> print(a.name) zhangsan >>> b = A('lisi') # 再次执行初始化方法__init__ >>> print(b.name) lisi
由上面代码可以看到,在实例化对象时,虽然__new__方法只被执行了一次,只创建了一个对象空间,但是由于对象空间已经创建,在实例化不同对象时,都会调用初始化方法,即初始化方法__init__被执行了两次。
修改代码,使单例对象只初始化一次:
>>> class A: ... __instance = None ... __flag = False # 定义类属性作为判断的条件 ... def __new__(cls, name): ... if cls.__instance == None: ... cls.__instance = super().__new__(cls) ... return cls.__instance ... else: ... return cls.__instance ... def __init__(self, name): ... if A.__flag == False: # 满足条件则执行赋值,不满足就不执行 ... self.name = name ... A.__flag = True ... >>> a = A('zhangsan') >>> print(a.name) zhangsan >>> b = A('lisi') # 依然会执行初始化方法__init__,但不执行赋值操作 >>> print(b.name) zhangsan # 对象的name属性没有被修改
__str__()方法:打印对象名时自动调用此方法。
没有定义__str__()方法:
>>> class Person: ... def __init__(self, name, age): ... self.name = name ... self.age = age ... >>> p = Person('zhangsan', 22) >>> print(p) # 类中没有定义__str__()方法,打印对象的地址 <__main__.Person object at 0x7f94513b26d8>
定义了__str__()方法:
>>> class Person: ... def __init__(self, name, age): ... self.name = name ... self.age = age ... def __str__(self): ... return '%s的年龄是%d岁。'%(self.name, self.age) ... >>> p = Person('zhangsan', 22) >>> print(p) # 定义了__str__()方法,打印对象名时,自动输出__str__()方法的返回值 zhangsan的年龄是22岁。
__repr__()方法:在解析器中应用。
>>> class A: ... def __repr__(self): ... return '在解析器中应用' ... >>> a = A() >>> a 在解析器中应用
当类中同时定义了__str__()和__repr__(),打印对象名时,会调用__str__()方法,而当类中只定义了__repr__()方法时,打印对象名时,会调用__repr__()作为替代。
>>> class A: ... def __str__(self): ... return '打印对象名时调用__str__' ... def __repr__(self): ... return '调用__repr__' ... >>> a = A() >>> print(a) 打印对象名时调用__str__ >>> class B: ... def __repr__(self): ... return '调用__repr__作为替代' ... >>> b = B() >>> print(b) 调用__repr__作为替代
__del__()方法:析构方法,在对象结束前会被自动调用,可用来作对象结束前的善后工作。一般很少使用,因为我们很难明确对象什么时候结束。
__call__()方法:在类中定义了此方法,则类的实例就可调用。函数和方法中都会有此方法,表示函数和方法都是可调用的。
>>> class A: ... def __call__(self): ... print('我执行啦!') ... >>> a = A() >>> a() 我执行啦!