和其他的面向对象语言类似,Python也能使用面向对象机制,但Python的面向对象和其他语言的有所不同。首先,我们先来了解一下python的类和实例。
一:类和实例
我们知道面向对象与面向过程是有很大区别的,面向对象是将计算机的程序作为一组对象,计算机的执行实际上就是对象在互相之间进行传递消息的过程,python的面向对象将所有的事物作为对象(即一切都是对象!!)。
1.1封装和抽象:
Python将事物的一些特性抽象成属性和同一个事物的相关行为抽象成方法/函数,然后将两者组合(封装)在一起,形成一个对象(类对象)!这还没完,类对象接着进行具象化,变成一个个有具体含义的实例。--类对象是实例的上一层抽象。
ok,我们明白了什么是类和实例之后,我们先来看看新对象模型和经典对象模型(这有助于我们理解之后的一些问题)。
1.2新型对象模型和经典对象模型:
对于python中的类,分为经典类和新式类(经典类在2.7版本之前兼容,而在3之后的版本就只能使用新式类)。
区别经典类和新式类:
1、经典类默认没有派生自某个基类,只有显式继承了Object才会是新式类;而新式类默认派生自Object类2、经典类在类的多重继承时,采用从左到右深度优先算法;而新式类采用C3算法
3、经典类是没有__MRO__和instance.mro()调用的,而新式类是有的
4、新型类有__slots__属性和__getattribute__()方法,有修饰器;而经典类没有
5、新式类相同父类只执行一次构造函数,经典类会重复执行多次
6、经典类中的实例调用__class__定义了实例的类名,type()方法返回<type "instance">;而新型类中的实例调用__class__和type()方法都会返回实例的类名(新式类中内置类型和自定义类是相同的)
经典对象模型是在python的2.7版本之前使用的,而在3之后的版本就只使用新型对象模型了!ok,在我们了解了两种对象模型的区别之后,就可以继续学习之后的知识了。
1.3类:
1.3.1类的定义:
类是对数据(属性)和行为(方法)进行抽象并进行封装的集合,类会对外提供接口("."进行属性引用)以供类之外的对象进行调用。
我们先来看一个例子来具体看看到底类是怎样定义的,以及类中的方法和数据属性是怎样的。
>>> class A():
... def __init__(self,name):
... self.name=name
... ff=7
...
>>> A
<class '__main__.A'>
我们现在知道了类是由属性和方法构成的,为了对类进行进一步的分析,我会在下面一步步的对类的属性和方法分别详细介绍。
1.3.2类属性:
在了解类属性之前,我们需要先明白什么是属性!那么什么是属性呢?属性就是某个对象的数据元素。而类属性就是绑定在其被定义的类上的这些属性。(类属性不依赖于实例)
注意:属性也是对象,属性(对象)也会有自己的属性(数据元素和函数),即属性与属性之间形成了一个属性链。
类属性的三种表示形式:
1、一般属性(不以"__"开头)
2、以"__"开头但不以"__"结尾的属性
无法使用以""__""开头但不以"__"结尾的属性名
3、特殊属性(以"__"开头且以"__"结尾):
__class__ 类对象(作为实例)所对应的类
__doc__ 文档字符串,用于描述该类的各种信息
__bases__ 该类的父类构成的元组
__dict__ 该类的属性构成的字典(键为属性名,值为属性的值)
__module__ 该类所定义在的模块
__name__ 该类的名字
接下来,同样举一个例子来看看类的数据属性是怎样的。首先我们定义了三个不同的数据属性:name、__sum、__sam__,然后我们使用了dir()方法,该方法会将对象的属性以列表的形式表示出来!从该列表中我们可以看见name和__sam__,但是为什么没有__sum呢?
嘿嘿,虽然没有名为__sum的属性,但是有名为_A__sum的属性,这个属性和__sum很类似。我们先看看调用三个属性能否得到结果,就能明白_A__sum的含义了!
>>> class A():
... name=1
... __sum=2
... __sam__=3
...
>>> dir(A)
['_A__sum', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq_
_', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__'
, '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '_
_reduce__', '__reduce_ex__', '__repr__', '__sam__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', '__weakref__', 'name']
下面是调用三个属性的结果,我们发现在调用__sam__和name时并没有什么问题,但是在调用__sum的时候发现提示错误:A并没有__sum这个属性!!我们明明定义了该属性,但为什么没有这个属性呢?
>>> A.name
1
>>> A.__sam__
3
>>> A.__sum
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'A' has no attribute '__sum'
我们之前发现一个类似于__sum的属性_A__sum。接下来,我们试着调用_A__sum属性。
>>> A._A__sum
2
结果居然成功了!也就是说,这就是我们之前定义的属性__sum,但是为什么我们不能直接调用呢?原因是当定义一个以"__“开头但不以"__"结尾的属性时,会自动将属性名转化为"_类名__属性名"!
1.3.3类中的方法:
第一个问题,什么是方法?方法就是对外界事物的行为进行抽象而得到的一段代码(python中称为函数或方法)。而类方法就是本质上是绑定在类对象上的,与类的实例没有依赖关系(即只有类对象才有方法属性,实例只有数据属性)。
准确的说,只有类对象才有方法属性,实例是没有方法属性的(这点一定要注意)! 但是实例可以调用方法属性。下面是一个例子来具体说明类的方法属性。
>>> class A():
... def a():
... print("abcdefg")
...
>>> A.__dict__
mappingproxy({'__module__': '__main__', 'a': <function A.a at 0x00000073C0691268
>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute
'__weakref__' of 'A' objects>, '__doc__': None})
当我们调用类A的__dict__属性时,会发现方法a也是其中之一,所以方法是类对象的固有属性!!(这一点一定要记住)
类中的方法同样不是完全都是一样的,分为绑定方法和非绑定方法。
类中的方法的两种类型:
1、 绑定方法:在类中的方法默认是绑定到实例对象的。
绑定到实例对象:类来调用类中的方法时,认为方法仅仅是函数,绑定在实例上--即当你使用类来调用的时候,必须给函数传相应个数(函数定义时的个数)的参数。
实例来调用类中的方法时,认为方法是绑定在实例上--即当你使用实例调用的时候,实例对象会自动作为第一个参数传递到方法中。
我们可以从下面例子看出当使用类对象A和实例a调用方法的区别。
>>> class A():
... def x():
... print("方法x被使用.")
... def y(self):
... print("方法y被调用.")
...
>>> a=A()
>>> A.x
<function A.x at 0x00000073C0691488>
>>> a.x
<bound method A.x of <__main__.A object at 0x00000073C0697780>>
>>> A.x()
方法x被使用.
>>> A.y()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: y() missing 1 required positional argument: 'self'
>>> A.y(2)
方法y被调用.
>>> a.x()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: x() takes 0 positional arguments but 1 was given
>>> a.y()
方法y被调用.
绑定到类对象:需要使用@classmethod方法,即类方法(装饰器),将类中的方法绑定到类身上。
类来调用类方法时,认为方法绑定在类对象上--即当你使用类对象调用的时候,类对象作为第一个参数传递到方法中实例来调用类方法时,认为方法绑定在类对象上--即当你使用实例调用的时候,类对象作为第一个参数传递到方法中
>>> class A():
... @classmethod
... def x():
... print("方法x被使用.")
... @classmethod
... def y(self):
... print("方法y被调用.")
...
>>> A.x()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: x() takes 0 positional arguments but 1 was given
>>> a=A()
>>> A.y()
方法y被调用.
>>> a.x()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: x() takes 0 positional arguments but 1 was given
>>> a.y()
方法y被调用.
2、非绑定方法:使用@staticmethod方法,可以解除绑定关系,将一个类中的方法,变为一个普通函数。
无论是类或实例调用该方法,都必须按照要求传递定义函数时的参数个数。
>>> class A():
... @staticmethod
... def x():
... print("方法x被使用.")
... @staticmethod
... def y(self):
... print("方法y被调用.")
...
>>> A.x()
方法x被使用.
>>> A.y()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: y() missing 1 required positional argument: 'self'
>>> a=A()
>>> a.x()
方法x被使用.
>>> a.y()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: y() missing 1 required positional argument: 'self'
>>> a.y(1)
方法y被调用.
>>> A.y(1)
方法y被调用.
1.4实例:
1.4.1实例的创建、初始化和解构:
实例就是类的具体化的对象,换句话说,类就相当于实例的"类型"。
实例的创建:
使用 __new__()方法创建实例,该方法的第一个参数是类对象本身,之后可以定义任意参数作为构建对象之用。若该方法返回的对象是第一个参数(类对象,一般用clz表示)的实例,就会执行__init__()方法;若返回其他的对象,就不会执行__init__()方法。
>>> class A():
... def __new__(clz):
... print("该方法已经被调用。")
... return object.__new__(clz)
...
>>> a=A()
该方法已经被调用。
实例的初始化:
使用__init__()方法进行实例的初始化,该方法的第一个参数为类对象的实例(一般用self表示),之后可以定义任意参数。如果没有定义__init__()时,仅仅会返回一个实例,而不做任何初始化操作。
>>> class A():
... def __new__(clz,name):
... print("__new__方法已经被调用。")
... return object.__new__(clz)
... def __init__(self,name):
... self.name=name
... print("__init__方法已经被调用。")
...
...
>>> a=A(1)
__new__方法已经被调用。
__init__方法已经被调用。
注意:当使用__new__()方法并且使用__init__()方法时,__new__()和__init__()方法除了第一个参数外其余参数必须保持一致,且只有当__new__()方法返回一个其第一个参数(即类对象)的实例时,才会将实例给__init__()方法,并执行__init__()方法。
>>> class A():
... def __new__(clz):
... print("__new__方法已经被调用。")
... return object.__new__(clz)
... def __init__(self,name):
... self.name=name
... print("__init__方法已经被调用。")
...
...
>>> a=A()
__new__方法已经被调用。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __init__() missing 1 required positional argument: 'name'
>>> a=A(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __new__() takes 1 positional argument but 2 were given
看上面的例子,发现在创建实例时不论是带一个参数还是不带参数都报错!--原因:__new__()和__init__()方法的参数必须保持一致!!
我们再看下面的例子中__new__()方法返回值为1,而不是上面的A的实例。观察结果,发现只执行了__new__()方法,而__init__()方法并没有执行。
>>> class A():
... def __new__(clz,name):
... print("__new__方法已经被调用。")
... return 1
... def __init__(self,name):
... self.name=name
... print("__init__方法已经被调用。")
>>> a=A(1)
__new__方法已经被调用。
实例的解构:
使用__del__()方法来在实例释放前进行一些特殊的处理,只有实例的所有引用都被清除才会执行该方法。
1.4.2实例属性:
同样的,实例也有对应的属性,实例的属性被实例绑定。实例仅仅拥有数据属性,而没有方法属性(方法属性是绑定在类对象上的)。
实例属性的三种表示形式:
1、一般属性(不以"__"开头)
2、以"__"开头但不以"__"结尾的属性
3、实例的特殊属性(以"__"开头且以"__"结尾):
__class__ 实例的类
__dict__ 实例的属性所构成的字典(键为属性名,值为属性的值),字典中仅仅有实例属性,没有类属性和特殊属性
下面将以例子的方式来说明实例属性的三种表示形式。观察实例a的__dict__属性可以发现有_A__num这样一个属性,我们在之前介绍类的属性的时候,同样发现了类似的问题。--这里与类的属性原理是相同的,就不再赘述。
>>> class A():
... def __init__(self,name,num,sam):
... self.name=name
... self.__num=num
... self.__sam__=sam
...
>>> a=A(1,2,3)
>>> a.__dict__
{'name': 1, '_A__num': 2, '__sam__': 3}
访问数据属性:
访问类属性(类属性是不可变的):类属性可以使用类对象或实例进行访问。
使用实例访问类属性时,会先在实例的命名空间中寻该属性,若找不到,则到该实例的类的命名空间中寻找,若还是找不到,则不断向父类链进行寻找,直到最后找到或者返回一个错误。
例子如下:我们在类中定义了一个属性name,从A.__dict__中发现这个属性name是类A的,而不是实例a的。接下来我们再使用实例a对name进行赋值。
>>> class A():
... name=1
...
...
>>> a=A()
>>> a.__dict__
{}
>>> A.__dict__
mappingproxy({'__module__': '__main__', 'name': 1, '__dict__': <attribute '__dic
t__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '
__doc__': None})
使用a进行赋值后,再检验一下,发现成功了(a.name的值确实改变了)。
>>> a.name=3
>>> a.name
3
>>>
再来使用A来调用name属性试试,为什么name的结果仍为1?我们不是已经改变了值吗?其实我们并没有改变A的name的值,我们只是为实例a添加了一个名为name的属性。不信我们调用a和A的__dict__属性看看,发现两个对象都有名为name的属性,但是值不同!
>>> A.name
1
>>> a.__dict__
{'name': 3}
>>> A.__dict__
mappingproxy({'__module__': '__main__', 'name': 1, '__dict__': <attribute '__dic
t__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '
__doc__': None})
访问类属性(类属性是不可变的)
但是,使用实例不能修改类对象的值,只能使用类对象进行修改其值。删除时,删除的是实例的属性。
若是使用实例去给类对象进行赋值,会在实例的命名空间中创建一个与类对象中的属性名相同的属性。(赋值时,属性被添加到实例的__dict__中)
访问类属性(类属性是可变的)
使用实例去改变类属性的值就会修改类对象的属性值,无法删除使用实例引用的类对象。
>>> a=A()
>>> A.__dict__
mappingproxy({'__module__': '__main__', 'name': [1, 2, 3], '__dict__': <attribut
e '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' obj
ects>, '__doc__': None})
>>> a.__dict__
{}
>>> a=A()
>>> A.name
[1, 2, 3]
>>> a.name[0]=99
>>> a.name
[99, 2, 3]
>>> A.name
[99, 2, 3]
>>> A.__dict__
mappingproxy({'__module__': '__main__', 'name': [99, 2, 3], '__dict__': <attribu
te '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' ob
jects>, '__doc__': None})
>>> a.__dict__
{}
>>> del a.name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: name
访问方法:
访问方法会按照__MRO__中的类对象顺序进行访问(即使用"方法解析顺序"进行访问)。
二:继承
多重继承的两种模型:
继承的新模型:使用C3算法,多继承的属性访问顺序被存储在__MRO__中
使用super()访问父类的属性
继承的旧模型: 从左到右,深度优先
使用父类对象访问父类的属性
下面我会用三个例子来说明新模型是怎么实现多继承的。(因为旧模型在python3之后就不再使用,所以在这里不再说明!)
>>> class A():
... name=1
... def __init__(self):
... print("A已经被执行")
...
>>> class B(A):
... name=2
... def __init__(self):
... print("B已经被执行")
...
>>> class C(A):
... name=3
... def __init__(self):
... print("C已经被执行")
...
>>> class D(B,C):
... def __init__(self):
... print("D已经被执行")
...
>>> d=D()
D已经被执行
>>> d.name
2
>>> class A():
... name=1
... def __init__(self):
... print("A已经被执行")
...
>>> class B(A):
... def __init__(self):
... print("B已经被执行")
...
>>> class C(A):
... name=3
... def __init__(self):
... print("C已经被执行")
...
>>> class D(B,C):
... def __init__(self):
... print("D已经被执行")
...
>>> d=D()
D已经被执行
>>> d.name
3
>>> class A():
... name=1
... def __init__(self):
... print("A已经被执行")
...
>>> class B(A):
... def __init__(self):
... print("B已经被执行")
...
>>> class C(A):
... def __init__(self):
... print("C已经被执行")
...
>>> class D(B,C):
... def __init__(self):
... print("D已经被执行")
...
>>> d=D()
D已经被执行
>>> d.name
1
>>> class A():
... name=1
... def __init__(self):
... print("A已经被执行")
...
>>> class m():
... name=9
...
>>> class C(A):
... def __init__(self):
... print("C已经被执行")
...
>>> class B(m):
... def __init__(self):
... print("B已经被执行")
...
>>> class D(B,C):
... def __init__(self):
... print("D已经被执行")
...
>>> d=D()
D已经被执行
>>> d.name
9
从上面的四个例子中可以发现,类D的实例在查找某一属性时,首先会先查找实例d有没有该属性,若没有,则查找d的类D有没有该属性;若没有,则再根据D的两个父类左右的顺序关系,继续查找B有没有该属性;若没有,则查找类C有没有该属性;若没有,则先查找B的父类,再查找C的父类······ (这就是C3算法!)
多态:
在子类中定义一个与祖先类中相同的名字的数据属性或方法会覆盖祖先类中同名的属性或方法,祖先类中的属性或方法会被隐藏。
子类在继承祖先类时,不会自动调用祖先类的__init__()方法,必须主动调用该方法(使用祖先类调用或者使用super()调用)。
Object类:
Object类是所有的内置类型(内置类型也是类)和新型类的祖先。
下面随意定义了一个类A,使用isinstance()方法发现,object是A的类型(即A的祖先是object)。
>>> class A():
... def __init__(self):
... print("该方法已经被执行")
...
>>> isinstance(A,object)
True
Object类中实现了一些默认的特殊方法:
__new__()
__init__()
__delattr__(object,name) 用来删除对象的数据属性,不能删除对象的方法。
__getAttribute()__ __getattribute__是属性访问拦截器,就是当这个类的属性被访问时,会自动调用类的__getattribute__方法。默认的,即当调用实例对象的属性时,不会直接打印,而是把属性的值作为实参传进__getattribute__方法中,经过一系列操作后,再把属性的值返回。(此默认方法可以被重写)
默认的object的方法如下:
>>> def __getAttribute__(self,obj):
... return object.__getAttribute__(self,obj)
...
__setattr__(object,name,value) 用来添加对象的属性,不能添加对象的方法。
__hash__() 返回对象的哈希值
__repr__()
__str__()