Descriptor
python在2.2版本中引入了descriptor功能,引入了一些新的概念,比如classmethod, staticmethod, super,Property等,这些新功能都是基于descriptor而实现的。
Descriptor代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class RevealAccess:
"""创建一个Descriptor类,用来打印出访问它的操作信息"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print('Updating' , self.name)
self.val = val
#使用Descriptor
class MyClass(object):
#生成一个Descriptor实例,赋值给类MyClass的x属性
x = RevealAccess(10, 'var "x"')
y = 5 #普通类属性
a = MyClass()
print(a.x)
a.x = 1
print(a.y)
print(MyClass.x)
|
执行以上程序会输出如下结果:
1
2
3
4
5
6
|
Retrieving var "x"
10
Updating var "x"
5
Retrieving var "x"
1
|
Descriptor定义
descriptor可以说是一个绑定了特定访问方法的类属性,这些访问方法是重写了descriptor protocol中的三个方法,分别是get, __set__, del方法。如果三个中任一一个方法在对象中定义了,就说这个对象是一个descriptor对象,可以把这个对象赋值给其它属性。descriptor protocol可以看成是一个有三个方法的接口。
默认的查找属性的顺序
例如,对于操作a.x,会一个查找链从a.__dict[‘x’](实例的字典),再到type(a).__dict__[‘x’](类的字典),再到type(a)的父类的字典等等。代码如下:
1
2
3
4
5
6
7
8
|
class A:
def __init__(self):
self.x = 1
a = A()
print("dir(a)的值: ",dir(a))
print("dir(A)的值: ",dir(A))
print("type(a)的值: ",type(a))
print("dir(type(a))的值: ",dir(type(a))) #dir(type(a))与dir(A)结果是一样的
|
执行以上程序会输出如下结果:
1
2
3
4
|
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__', 'x']
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__']
type(a)的值: <class '__main__.A'>
dir(type(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__']
|
可以看出类和实例的字典属性的值的内容是不一样的,因为实例中有绑定属性x的存在。type(a)返回就是实例a的类型,类A。
如果这个需要被查找的属性是一个定义了descriptor协议方法的对象,那么python就不会按照默认的查找方式,而是调用descriptor协议中定义的方法去做处理。
Descriptor Protocol(协议)
有下面这三个方法
object.get(self, instance, owner):return value
object.set(self, instance, value):return None
object.delete(self, instance): return None
只要对象重写任何上面的一个方法,对象就被看作是descriptor,就可以不去采用默认的查找属性的顺序。
如果一个对象同时定义了get,__set方法,被看作是data descriptor;只定义了get__,被称为non-data descriptor。注意 ,descriptor的实例是一定类属性(class attribute)。
Descriptor调用方法
可以直接使用descriptor实例进行方法调用,如d.__get__(obj)。
一般是在属性访问的时候自动被调用,例如obj.d是在obj实例的字典属性中查找d变量,如果d定义了get方法和set方法,是一个data descriptor,则根据上面提到的优先级,会自动去调用 d.__get__(obj)。
下面的代码展示了简单的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
class Des:
def __init__(self, init_value):
self.value = init_value
def __get__(self, instance, typ):
print('call __get__', instance, typ)
return self.value
def __set__(self, instance, value):
print('call __set__', instance, value)
self.value = value
def __delete__(self, instance):
prin('call __delete__', instance)
class Widget:
t = Des(1)
def main():
w = Widget()
print(type(w.t))
w.t = 1
print(w.t, Widget.t)
del w.t
if __name__=='__main__':
main()
|
运行结果如下:
1
2
3
4
5
6
|
('call __get__', <__main__.Widget object at 0x02868570>, <class '__main__.Widget'>)
<type 'int'>
('call __set__', <__main__.Widget object at 0x02868570>, 1)
('call __get__', <__main__.Widget object at 0x02868570>, <class '__main__.Widget'>)
1 ('call __get__', None, <class '__main__.Widget'>)
('call __delete__', <__main__.Widget object at 0x02868570>)
|
从输出结果可以看到,对于这个三个特殊函数,形参instance是descriptor实例所在的类的实例(w), 而形参owner就是这个类(widget)
w.t等价于 Pro.get(t, w, Widget).而Widget.t等价于 Pro.get(t, None, Widget)
descriptor注意事项
需要注意的是, descriptor的实例一定是类的属性,因此使用的时候需要自行区分实例。比如下面这个例子,保证属性不超过一定的阈值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class MaxValDes(object):
def __init__(self, inti_val, max_val):
self.value = inti_val
self.max_val = max_val
def __get__(self, instance, typ):
return self.value
def __set__(self, instance, value):
self.value= min(self.max_val, value)
class Widget(object):
a = MaxValDes(0, 10)
if __name__ == '__main__':
w0 = Widget()
print 'inited w0', w0.a
w0.a = 123
print 'after set w0',w0.a
w1 = Widget()
print 'inited w1', w1.a
|
运行结果如下:
1
2
3
|
inited w0 0
after set w0 10
inited w1 10
|
可以看到,对w0.a的赋值符合预期,但是w1.a的值却不是0,而是同w0.a一样。这就是因为,a是类Widget的类属性, Widget的实例并没有’a’这个属性,可以通过dict查看。
那么要怎么修改才符合预期呢,看下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class MaxValDes(object):
def __init__(self, attr, max_val):
self.attr = attr
self.max_val = max_val
def __get__(self, instance, typ):
return instance.__dict__[self.attr]
def __set__(self, instance, value):
instance.__dict__[self.attr] = min(self.max_val, value)
class Widget(object):
a = MaxValDes('a', 10)
b = MaxValDes('b', 12)
def __init__(self):
self.a = 0
self.b = 1
if __name__ == '__main__':
w0 = Widget()
print 'inited w0', w0.a, w0.b
w0.a = 123
w0.b = 123
print 'after set w0',w0.a, w0.b
w1 = Widget()
print 'inited w1', w1.a, w1.b
|
运行结果如下:
1
2
3
|
inited w0 0 1
after set w0 10 12
inited w0 0 1
|
可以看到,运行结果比较符合预期,w0、w1两个实例互不干扰。上面的代码中有两点需要注意:
第一:第7、10行都是通过instance.dict来取值、赋值,而不是调用getattr、setattr,否则会递归调用,死循环。
第二:现在类和类的实例都拥有‘a’属性,不过w0.a调用的是类属性‘a’.
基于Descriptor实现的功能
descriptor协议是简单而又强大的,一些新特性就是利用descriptor功能封装成一个独立的函数调用,如:
Property
绑定和非绑定方法
静态方法
类方法
super
property
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class C:
def getX(self):
print('get x')
return self.__x
def setX(self, value):
print('set x', value)
self.__x = value
def delX(self):
print('del x')
del self.__x
x = property(getX, setX, delX, "This is 'x' property.")
c = C()
c.x = 1 # set x 1
c.x # get x
del c.x # del x
|
运行结果如下:
1
2
3
|
set x 1
get x
del x
|
调用proprety()是一种创建data descriptor的一种简洁的方式,函数结构如下:
property(fget=None, fset=None, fdel=None, doc=None) #返回的是property对象,可以赋值给某属性,propety方法有四个参数,只要对没有进行赋值的参数进行访问就会报错。
c 是 C 的一个实例, x是C中定义的一个property属性:
当你引用c.x时, python调用 fget 方法取值给你.
当你为c.x赋值: x.attrib=value 时, python调用 fset方法, 并且value值做为fset方法的参数,
当你执行del c.x 时, python调用fdel方法,
当你传过去的名为 doc 的参数即为该属性的文档字符串.
又如,我们定义一个只读property属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Rect(object):
def __init__(self, width, heigth):
self.width = width
self.heigth = heigth
def getArea(self):
return self.width * self.heigth
area = property(getArea, doc='area of the rectangle')
a = Rect(2,3)
print(a.area) # 6
a.area = 3
del a.area
|
只需要传入fget参数就可以,运行如下:
1
2
3
4
5
|
6
Traceback (most recent call last):
File "main.py", line 12, in <module>
a.area = 3
AttributeError: can't set attribute
|
属性area为只读,任何重新绑定和删除的操作都会报错。这是因为我们只定义了fget方法。
properties所做的事情与那些特殊方法getattr, __setattr__, \_delattr__ 等是极其相似的,不过同样的工作它干起来更简单更快捷。
关于property()方法的几点声明:
1.property()函数的四个参数,应该为methods(带self参数的那种),而不是function。
2.当你使用类去访问属性的时候,property设置的函数是不会被调用的。只有用实例去访问才会调用。
函数和方法,绑定与非绑定
Python的面向对象特性是基于函数的,函数的实现是需要使用到non-data descriptor的功能。
在类定义中,方法是由def或者lambda声明的。和一般函数不同的是,方法的第一个参数是self对象。
为了支持方法的调用,在访问方法属性的时候,functions使用相应的get方法。这就意味着所有的函数都是non-data descriptor,用于根据类或者对象的调用来返回unbound或者bound的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
>>>class D(object):
def f(self,x):
retunr x
>>>d = D()
>>>D.__dict__['f']
<function f at 0x02164A30>
>>>D.f
<unbound method D.f>
>>>d.f
<bound method D.f of <__main__.D object at 0x021B1070>>
>>>D.f(d,3)
3
>>>d.f(3)
3
>>>D.f(3)
Traceback
TypeError
|
可以看到方法在字典中存放的类型其实是函数对象,bound和unbound方法是两个不同的类型。内部的实现其实是一个同一个对象,不同的是这个对象的im_self属性是否被赋值,或者是设为None。
super
在支持多继承的语言中,讨论谁是父类,感觉意义不大,尤其是像之类mro的菱形问题,父类是谁就更说不清了。需要强调的是super不会返回父类,它返回的是代理对象。代理对象就是利用委托(delegation)使用别的对象的方法来实现功能的对象。
super返回的是一个定制了getattribute方法的对象,是一个代理对象,它可以访问MRO中的方法。形式如下:
super(cls, instance-or-subclass).method(*args, **kw)
可以转化为:
right-method-in-the-MRO-applied-to(instance-or-subclass, *args, **kw)
需要注意的是,第二个参数instnce-or-subclass可以是第一个参数的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
>>>class B(object):
def __repr__(self):
return '<instance of %s>' % self.__class__.__name__
>>>class C(B):
pass
>>>class D(C):
pass
>>>d = D()
>>>print super(C,d).__repr__
<bound methond D.__repr__ of <instance of D>>
>>>print super(C,D).__repr__
<unbound methond D.__repr>
>>>print super(C,d).__repr__()
<instance of D>
>>>print super(C,D).__repr__()
Traceback
TypeError
|
如果返回了非绑定的方法,调用的时候需要加上第一个self参数。
通过descriptor的实现,可以说super也是一个non-data descriptor类。也就是实现了get(self, obj, objtyp=None)的类。假设descr是C类的一个descriptor,C.descr实现上调用的是descr.__get__(None, C);如果是实例来调用,c.descr调用的是descr.__get__(c, type©)。
转载请注明本网址。