以下的讨论都限于,新式类(个人认为最好限于新式类)
1,python一切皆对象
除了object和type,两个逆天的存在,不是说它们不是,而是它们更高级一点。第一个区分的就是对象之间的关系,由__bases__和__class__两个主线来关联完成。
class AA(object):
x = 'x from AA'
class BB(type):
x = 'x from BB'
y = 'y from BB'
class CC(AA):
__metaclass__ = BB
z = 'z from CC'
c = CC()
print CC.__bases__ # 打印得到CC的师父 # (__mian__.AA,)
print CC.__class__ # 打印得到CC的爸爸 # __mian__.BB
print c.__class__ # 打印得到c的爸爸 # __mian__.CC
把实例化称作爸爸,即__class__称作爸爸,也就是c = CC()中,CC是c的爸爸,c由CC实例化得到。
把继承称作师父,即__base__称作师父,也就是 class CC(AA): pass 中,AA是CC的师父,CC继承自AA。
显然对于一个对象来说,爸爸只能有一个,而且必须有一个(可以是默认的爸爸),而师父可以有多个,也可以没有。
本文将着重讨论c和CC的属性访问区别,因为子c++/java里面,我们一般只考虑c来属性访问,很少用CC来访问属性。
python一切皆对象的意思是:如果说对象有爸爸,那么Python里面所有东西都有爸爸;如果对象可以有自己的属性,那么python所有东西都可以有自己的属性。万物皆平等(除了object和type),CC相对于c来说,并没有什么特殊,都是对象而已,都有属性访问问题。
在BB和CC和c的关系中,我们称BB是meta class object,称CC是class object,称c是instance Object。即BB是c的爷爷,CC是c的爸爸。
但在BB和CC的关系中,我们称BB是class object,称CC是instance Object。即BB是CC的爸爸。
但在type和BB的关系中,我们称type是class object,称BB是instance Object。即type是BB的爸爸。
为什么要区分这些呢,下面是两个访问属性的例子,后续的文章也主要是为了区分这两种属性访问方式。
print CC.x # 请问是访问BB中的x,还是AA中的x。即访问爸爸的技能,还是师父的技能。
print c.y # 报错,CC只能遗传CC自己的技能(z)和师父AA的技能(x),不能遗传父亲BB的技能(x和y)
以前我们都是站在c的角度来看待AA的,现在我们要站在CC的角度来看待AA,看看AA这个师傅对CC会有什么影响。 打个不太恰当的比方:
一个对象CC生下来,就拥有了父亲BB的技能(指属性x和y),
但是可以通过后天向师傅AA学习新的技能(指属性x),来增加或覆盖父亲BB的技能,
也可以自己修炼技能(指属性z)来增加或覆盖父亲BB的技能,
CC在生孩子的时候,只能遗传给孩子c,自己CC的技能和师父AA的技能。
2,属性的分类(作者自己的分类,仅供参考)
Python-provided属性和用户属性:Python-provided属性大致就是指__class__、__bases__、__dict__这类属性。
一个属性是否是Python-provided属性根据对象的不同而不同,比如__base__对于classObj是,但给instanceObj强行添加__base__的不算。
用户属性就是用户自己添加的属性,一般出现在各个对象自己的__dict__中。每个对象只可以增加删除修改自己__dict__里面的属性,即InstanceObj中的属性操作,不影响其ClassObj和BaseObj的__dict__中的属性。
描述符(descriptor)、数据描述符(data descriptor)、非数据描述符(non-data descriptor)、普通属性(others):
描述符也是对象,必须拥有__get__方法,__set__和__delete__方法可选,且方法的参数符合一定的规范。
数据描述符同时拥有__get__和__set__。非数据描述符只有__get__。非数据描述符一般就是指方法属性。
3,属性访问中调用的方法:__getattribute__和__getattr__
属性的读操作先是调用__getattribute__方法,(如果过有)找不到就再调用__getattr__方法。第一个问题是__getattribute__和__getattr__是什么?
print a.b.c.d; 这段代码有三次读属性操作。
a.b.c.d = e; 这段代码有两次读属性操作,一次写属性操作。
一般来说,一个读的点操作,就会调用一次__getattribute__方法,当__getattribute__找不到属性时,如果有,就调用并返回__getattr__方法的结果,否则抛出异常。
第二个问题是__getattribute__和__getattr__从哪里来?
它们都从对象的类型中找,以及对象类型的基类中找。反正最后object和type中是一定会含有__getattribute__的。找到第一个则返回。即先在obj.__class__中找,再在obj.__class__.__bases__中找。
'a.__class__==AA、AA.__class__==object、c.__class__==CC、CC.__class__==DD、DD.__base__==BB'
class AA(object):
bar = 123
def foo(self):
print self.bar
def __getattribute__(self, *args, **kwargs):
print("__getattribute__() in AA")
return object.__getattribute__(self, *args, **kwargs)
def __getattr__(self, name):
print("__getattr__() in AA ")
class BB(type):
def __getattribute__(self, *args, **kwargs):
print("__getattribute__() in BB")
return type.__getattribute__(self, *args, **kwargs)
def __getattr__(self, name):
print("__getattr__() in BB ")
class DD(BB):
def __getattr__(self, name):
print("__getattr__() in DD ")
class CC(AA):
__metaclass__ = DD
def __getattribute__(self, *args, **kwargs):
print("__getattribute__() in CC")
return object.__getattribute__(self, *args, **kwargs)
def __getattr__(self, name):
print("__getattr__() in CC ")
a = AA()
c = CC()
print c.attr # __getattribute__() in CC 和 __getattr__() in CC
print CC.attr # __getattribute__() in BB 和 __getattr__() in DD
print a.attr # __getattribute__() in AA 和 __getattr__() in AA
print a.foo() # 调用两次__getattribute__() in AA,一次是为了找'foo',一次是执行 print self.bar
print AA.attr # 抛出异常,因为AA对象的类型,也就是AA.__class__ == type,没有__getattr__方法兜底
print c.__getattribute__('attr')
# 由CC中的__getattribute__负责找'__getattribute__'属性,在CC中找到,并进行调用。
print CC.__getattribute__(c,'attr')
# 由BB中的__getattribute__负责找'__getattribute__'属性,在CC中找到,并进行调用。
object和type中的__getattribute__方法不一样,且会对参数做严格的检查。c用的是object的__getattribute__,
CC用的是type的__getattribute__。所以它们找到并返回的'__getattribute__'属性不一样,
一个是绑定后的,一个是未绑定,所以调用的参数也不一样。
默认的对__getattr__的调用不是在__getattribute__中,所以上面两个代码最终执行找到的'__getattribute__'方法,都会因为没有找到属性而抛出异常。
4,__getattribute__详解,读属性访问优先级
描述符的性质、方法与绑定函数的转换等机制都由__getattribute__方法来实现。下面的步骤讲的是__getattribute__查找的步骤(不考虑__slots__的情况,object和type中的__getattribute__方法有些细微的差别,毕竟一个是针对instanceObj,一个是针对classObj的访问):
1):如果是特殊属性,如Python-provided属性,特殊代码处理并返回。
2):依次在obj.__class__.__dict__和obj.__class__.__bases__.__dict__中查找,
找到第一个匹配的属性,停止查找。如果是数据描述符,则调用并返回它的__get__方法的结果。
否则没找到或找到的不是数据描述符,都进入3。
3):依次在obj.__dict__和obj.__bases__.__dict__中查找,
找到第一个匹配的属性,停止查找。如果是描述符(两种描述符都可以),则调用并返回它的__get__方法的结果。
如果不是描述符则直接返回该对象,如果没有找到,则进入4。(纠正:object的__getattribute__方法中,找到描述符直接返回,并不会调用它的__get__方法)
4):依次在obj.__class__.__dict__和obj.__class__.__bases__.__dict__中查找,
找到第一个匹配的属性,停止查找。如果是描述符(两种描述符都可以),则调用并返回它的__get__方法的结果。
如果不是描述符则直接返回该对象,如果没有找到,则进入5。
5):抛出异常AttributeError。
class DD(object):
'data descriptor'
def __init__(self,name):
self._name = name
def __get__(self, obj, cls=None): # ,', obj:', obj,', cls:', cls
print 'get in DD, name:', self._name
def __set__(self, obj, val):
print 'set in DD, name:', self._name
class Non_DD(object):
'non-data descriptor'
def __init__(self,name):
self._name = name
def __get__(self, obj, cls=None):
print 'get in Non_DD, name:', self._name
class AA(object):
x = DD('aa1')
y = Non_DD('aa2')
z = DD('aa3')
m = DD('aa4')
n = DD('aa5')
class BB(type):
x = DD('bb1')
y = DD('bb2')
z = Non_DD('bb3')
n = Non_DD('bb4')
k = DD('bb5')
t = Non_DD('bb6')
e = 'bb7'
class CC(AA):
__metaclass__ = BB
x = DD('cc1')
y = 'cc2'
z = Non_DD('cc3')
m = 'cc4'
e = Non_DD('cc7')
c = CC()
print '--------c的属性访问调用object中的__getattribute__方法--------'
print c.x # 步骤 2
print c.y # 步骤4
print c.z # 步骤 4
print c.m # 步骤 4
print c.n # 步骤 2
c.__dict__['n'] = "c.__dict__['n']"
print c.n # 步骤 2
c.__dict__['m'] = "c.__dict__['m']"
print c.m # 步骤 3
c.foo = Non_DD('foo in c obj')
print c.foo # 步骤 3
print '--------CC的属性访问调用type中的__getattribute__方法--------'
print CC.x # 步骤 2
print CC.y # 步骤 2
print CC.z # 步骤 3
print CC.m # 步骤 3
print CC.n # 步骤 3
CC.n = 'CC.n'
print CC.n # 步骤 3
print CC.t # 步骤 4
print CC.e # 步骤 3
CC.foo = Non_DD('foo in CC obj')
print CC.foo # 步骤 3
只有第2步中找到的数据描述符,才是真正的数据描述符,才能执行完整的数据描述符机制。
其它步骤中描述符和普通属性的优先级一样,只是会额外调用描述符的get方法。
5,写属性和__setattr__
python写属性都直接调用__setattr__,没有__setattribute__方法。__setattr__中步骤详解(不考虑__slots__的情况):
1):依次在obj.__class__.__dict__和obj.__class__.__bases__.__dict__中查找,
找到第一个匹配的属性,停止查找。如果是数据描述符,则调用并返回它的__set__方法的结果。
否则没找到或找到的不是数据描述符,都进入2。
2):在obj.__dict__中插入该属性,并赋值。效果类似于obj.__dict__['varname'] = value
这里的特殊属性(Python-provided属性)不在特殊。
6,普通方法、类方法和静态方法的实现和装饰器
前面讲过,非数据描述符基本上可以说是指方法。那么非数据描述符的访问处理方式,就是调用并返回它的__get__方法的结果。我们下面看看如何使用装饰器配合非数据描述符的访问机制,来实现区分类方法、静态方法和普通方法。class myclassmethod(object):
def __init__(self,method):
self._classmethod=method
def __get__(self,obj=None,cls=None):
print 'myclassmethod: **** ',obj,cls,' **** '
if cls == None:
cls = type(obj)
def _deco(*args,**kwargs):
return self._classmethod(cls,*args,**kwargs)
return _deco
class mystaticmethod(object):
def __init__(self,method):
self._staticmethod=method
def __get__(self,obj=None,cls=None):
print 'mystaticmethod: **** ',obj,cls,' **** '
def _deco(*args,**kwargs):
return self._staticmethod(*args,**kwargs)
return _deco
class method2bounded_function(object):
def __init__(self,method):
self._normalmethod=method
def __get__(self,obj=None,cls=None):
print 'normal function: **** ',obj,cls,' **** '
def _deco(*args,**kwargs):
if obj!=None:
return self._normalmethod(obj,*args,**kwargs)
elif args and isinstance(args[0],cls): # 对应的是从classObj中直接调用方法
return self._normalmethod(*args,**kwargs)
else:
print 'raise TypeError: unbound method'
return _deco
class AA(object):
@method2bounded_function
def foo1(obj):# 不用self为了和下面的cls做对比
print obj
print 'normal method'
@classmethod
def foo2(cls):
print cls
print 'the built-in classmethod decorator'
@myclassmethod
def foo3(cls):
print cls
print 'my classmethod decorator'
@staticmethod
def bar2():
print 'the built-in staticmethod decorator'
@mystaticmethod
def bar3():
print 'my staticmethod decorator'
a = AA()
print repr(a)
a.foo1() # 调用object中的__getattribute__,传给get的参数:obj和type(obj)
a.foo2()
a.foo3()
a.bar2()
a.bar3()
print repr(AA)
AA.foo1() # 调用type中的__getattribute__,传给get的参数:None和obj
AA.foo1(a)
AA.foo1(123)# get方法,会对参数进行检查
AA.foo2()
AA.foo3()
AA.bar2()
AA.bar3()
装饰器知识点补充:
@classmethod
def foo2(cls):
pass
相当于、等价于:foo2 = classmethod(foo2)
前面忘记讲__getattribute__方法是如何调用非数据描述符的__get__方法。
对于object中的__getattribute__方法,调用Non_DD.__get__(obj,type(obj)),
对于type中的__getattribute__方法,调用Non_DD.__get__(None,obj),它们传给get方法的参数不同。
对于静态方法来说只需要__get__方法将原始参数传给原方法即可,
对于类方法来说,__get__方法需要保证将cls和原始参数传给原方法即可,
对于普通方法来说,__get__方法需要保证将obj和原始参数传给原方法。
object和type中的__getattribute__方法的区别可能有很多,本文暂时只讨论这一个。
可以看到每次返回的都是封装后的方法,所以如果测试方法的id,则每次都不一样,如下所示:
print id(a.foo1) # 代码要一行一行的执行
print id(a.foo1)
print id(a.foo1)
7,MRO(method resolution order)和C3算法
前面说的查找obj.__bases__.__dict__中基类的排序、线性化问题是由C3算法解决的。首先对于序列 [ C1, C2, C3…, CN ] 来说:
头 head = C1,尾 tail = C2C3…CN。
则C3算法:
L[C(B1B2…BN)] = C + merge(L(B1),L(B2),…,L(BN), B1B2…BN),
其中C是本次考虑的类对象,(B1B2…BN)类C的基类列表,按照定义时的顺序。merge函数:
从merge中从左往右,寻找不存在于任何tail中的head,取出来,然后从头再开始寻找,直至为空。否则,C3算法将会拒绝创建类C并抛出一个错误。
MRO需要满足局部优先级和单调性两个条件:
局部优先级::class Z(F,E): pass ,那么 F 的方法应该优先于 E 的方法。
单调性::在C中基类的线性化中,假如C1优先于C2,那么在C的任何子类中基类的线性化中,C1优先于C2。
class __metaclass__(type):
"All classes are metamagically modified to be nicely printed"
__repr__ = lambda cls: cls.__name__
class ex_1(object):
"Serious order disagreement" #From Guido
O = object
class F(O): pass
class E(F): pass
try:
class Z(F,E): pass #creates Z(A,B) in Python 2.2
except TypeError,e:
print 'error: ',TypeError,e
class ex_2(object):
"Serious order disagreement" #From Guido
O = object
class X(O): pass
class Y(O): pass
class A(X,Y): pass
class B(Y,X): pass
try:
class Z(A,B): pass #creates Z(A,B) in Python 2.2
except TypeError,e:
print 'error: ',TypeError,e
class ex_5(object):
"My second example"
# O = object
class O: pass
class F(O): pass
class E(O): pass
class D(O): pass
class C(D,F): pass
class B(E,D): pass
class A(B,C): pass
class ex_6(object):
"My first example"
# O = object
class O: pass
class B(O): pass
class A(B): pass
class D(B): pass
class N(O): pass
class M(N): pass
class Z(A,D,M): pass
class ex_9(object):
# O = object
class O: pass
class A(O): pass
class B(O): pass
class C(O): pass
class D(O): pass
class E(O): pass
class K1(A,B,C): pass
class K2(D,B,E): pass
class K3(D,A): pass
class Z(K1,K2,K3): pass
def merge(seqs):
print '\n\nCPL[%s]=%s' % (seqs[0][0],seqs),
res = []; i=0
while 1:
nonemptyseqs=[seq for seq in seqs if seq]
if not nonemptyseqs: return res
i+=1; print '\n',i,'round: candidates...',
for seq in nonemptyseqs: # find merge candidates among seq heads
cand = seq[0]; print ' ',cand,
nothead=[s for s in nonemptyseqs if cand in s[1:]]
if nothead: cand=None #reject candidate
else: break
if not cand: raise "Inconsistent hierarchy"
res.append(cand)
for seq in nonemptyseqs: # remove cand
if seq[0] == cand: del seq[0]
def mro(C):
"Compute the class precedence list (mro) according to C3"
return merge([[C]]+map(mro,C.__bases__)+[list(C.__bases__)])
def print_mro(C):
print '\nMRO[%s]=%s' % (C,mro(C))
print_mro(ex_5.A)
print_mro(ex_6.Z)
print_mro(ex_9.Z)
ex_5.A举例:
L[O] = O
L[F] = L[F(O)] = FO
L[E] = L[E(O)] = EO
L[D] = L[D(O)] = DO
L[C] = L[C(DF)] = C+L[D]+L[F] = C+DO+FO = CDFO
L[B] = L[B(ED)] = B+L[E]+L[D] = B+EO+DO = BEDO
L[A] = L[A(BC)] = A+L[B]+L[C] = A+BEDO+CDFO
= A[BEDO+CDFO] = AB[EDO+CDFO] = ABE[DO+CDFO]
= ABEC[DO+DFO] = ABECD[O+FO] = ABECDF[O+O]
= ABECDFO
ex_1 和 ex_2 都会被拒绝创建,然后抛出异常。
ex_5的是 A, B, E, C, D, F, O
ex_6的是 Z, A, D, B, M, N, O
记住每次取,是从头开始取,而不是接着取,所以,M的类别会到最后 ,再被取走
切记:MRO的C3方法,不等价于对有向图进行拓扑排序。
8,super
在类的定义中访问基类的属性,可以直接通过指明基类进行访问,也可以通过super类访问,两种方法。指明基类调用相对于super调用的缺点:1)这些写一是死板,如果基类需要改动,初始化代码也需要改动。2)在多继承中父类的代码可能被执行多次。
但使用super访问,初始化不仅完成了所有的基类的调用,而且保证了每一个基类的初始化函数只调用一次。调用基类的顺序是按照MRO中的顺序来的,super(B, self).func的
调用并不一定是调用当前类的父类的func函数,这点需要注意。
super调用有个缺点就是当基类方法的签名不一致时,就比较麻烦了。
混用super类和指明基类对象是一个危险行为,这可能导致应该调用的基类方法没有调用或者一个基类方法被调用多次。
9,__dict__、dir()、__slots__
不是所有的对象都有__dict__属性。例如,你在一个类中添加了__slots__属性,那么这个类的实例将不会拥有__dict__属性,但是dir()仍然可以找到并列出它的实例所有有效属性,所以__dict__不是唯一获取对象属性的方式。
dir()所做的不是查找一个对象的__dict__属性(这个属性有时甚至都不存在),它使用的是对象的继承关系来反馈一个对象的完整的有效属性。
class AA(object):
__slots__ = ('name', 'age')
name = 123
gender = 1259
class BB(AA):
__slots__ = ('height', 'weight')
height = 123
gender = 1259
class CC(AA):
height = 123
gender = 1259
AA中__slots__,name,gender都是只读的,age可写
BB中__slots__,name,gender,height都是只读的,age和weight可写
CC中读写都可以,不过是在自己的__dict__中(不完全正确)。
所有的slot是对instanceObj限制的,不是对classObj限制的。
classObj中定义了__slots__,则instanceObj只能访问__slots__中的属性和已有的类属性,且类属性只读。如果__slots__和类属性冲突,则以类属性为准。
通过AA.newvar = 123 添加的属性都是类属性,即存在于类AA的__dict__中,a = AA(); a.newvar = 123;添加的是实例属性,即存在于实例a的__dict__中。
c = CC()
c.age = 25
但是任何对象的__dict__中竟然都找不到'age':25的键值对!?
10,其他
前面一直说__getattribute__调用描述符的__get__方法。这里需要非常注意的是__get__方法来自哪里?
经过测试可知来自对象的类型类,也就是obj.__class__所指的对象中。如下代码所示:
'----测试get方法的来源---'
def __get__(*args,**kwargs):
print 'get in foo',args,kwargs
def foo(*args,**kwargs):
print 'foo',args,kwargs
class Non_DD(object):
'non-data descriptor'
def __init__(self,name):
self._name = name
def __get__(self, obj, cls=None):
print 'get in Non_DD, name:', self._name
class A(object):
def test(self):
def __get__(*args,**kwargs):
print 'get in test',args,kwargs
print 'test'
foo.__get__ = __get__
A.foo = foo
A.bar = Non_DD('bar in A')
a = A()
A.test(a)
A.foo(a) # 可以看到A对A中的foo和test的访问,都没有调用实例级别的__get__方法。
print A.bar # 可以看到A对A中的bar的访问,调用对象bar的类型级别的__get__方法,即Non_DD中的__get__方法。
a.test()
a.foo() # 可以看到a对A中的foo和test的访问,都没有调用实例级别的__get__方法。
print a.bar # 可以看到a对A中的bar的访问,调用对象bar的类型级别的__get__方法,即Non_DD中的__get__方法。
a.foo = foo
a.foo() # 可以看到a对a中的foo的访问,连foo类型级别的__get__方法都没有调用,即没有对foo进行封装。
a.bar = Non_DD('bar in a')
print a.bar # 可以看到a对a中的bar的访问,连bar类型级别的__get__方法都没有调用,即Non_DD中的__get__方法。
'----测试函数的get属性---'
def foo(*args,**kwargs):
def __get__(*args,**kwargs):
print 'get in foo',args,kwargs
print 'foo',args,kwargs
print foo.__dict__ # 并没有__get__方法
foo.__get__ = __get__
print foo.__dict__ # 有__get__方法
print A.test.__dict__ # 并没有__get__方法
A.test.__get__ = __get__
print A.test.__dict__ # 并没有__get__方法,因为每次都返回的是新的封装后的test对象
'----测试函数的object和type中的__getattribute__---'
A.bar = Non_DD('bar in A')
print A.bar
print type.__getattribute__(A,'bar') # 调用Non_DD的__get__方法
print object.__getattribute__(A,'bar') # 没有调用Non_DD的__get__方法,直接返回Non_DD对象
print object.__getattribute__(A,'bar').__get__(a)
参考链接:
http://www.cnblogs.com/lovemo1314/archive/2011/05/03/2035005.html
(如果有什么说的不对的地方,欢迎大家多多批评指正。)