描述符的定义
描述符是Python中一种特殊的语法,它是对多个属性利用相同存取逻辑的一种方式。描述符是实现了特定协议的类,这个协议包括实现__get__,__set__和__delete__方法,不过也可以只实现一部分协议。
描述符的简单例子
class Quantity(object):
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, val):
instance.__dict__[self.name] = val
class Obj(object):
weight = Quantity("weight")
price = Quantity("price")
def __init__(self, weight, price):
self.weight = weight
self.price = price
obj = Obj(1,2)
print obj.weight, obj.__dict__
输出为:
1 {'price': 2, 'weight': 1}
在以上的代码中,Quantity就是一个描述符,它实现了__get__、__set__方法,它其实是一个类属性。在访问类的对象时,会优先访问同名描述符的对象,并调用相应的__get__和__set__方法。
在__get__函数中,参数instance是类的对象,owner是类本身;__set__函数中,instance是类对象,val则是传入的值。描述符中存储了对象成员的名称,通过instance的__dict__来访问和存取相应的对象成员。
有人会觉得上图中给描述符命名的方法略显繁琐,毕竟名称可能会输入错误,那么有没有不用手动给描述符传入名称的初始化方法呢?当然有咯!直接上代码:
class Quantity(object):
count=0
def __init__(self):
self.name = '{}'.format(Quantity.count)
Quantity.count += 1
def __get__(self, instance, owner):
return getattr(instance, self.name)
def __set__(self, instance, val):
setattr(instance, self.name, val)
class Obj(object):
weight = Quantity()
price = Quantity()
def __init__(self, weight, price):
self.weight = weight
self.price = price
obj = Obj(1,2)
print obj.weight, obj.__dict__, Obj.__dict__
输出为:
1 {'1': 2, '0': 1} {'__module__': '__main__', 'weight': <__main__.Quantity object at 0x03C79450>, 'price': <__main__.Quantity object at 0x03C79470>, '__dict__': <attribute '__dict__' of 'Obj' objects>, '__weakref__': <attribute '__weakref__' of 'Obj' objects>, '__doc__': None, '__init__': <function __init__ at 0x03C7DD30>}
从中可以看到,我们能给描述符传入任意的名称,当我们访问obj的weight时,会触发Obj的weight描述符的__get__和__set__函数,实际操作的则是obj __dict__中的‘0’号对象。
另外,通过查看Obj的__dict__对象,我们也能了解其实描述符就是一种类属性。只不过它的函数能传入类的instance作为参数,以此来访问类对象的属性而已。
覆盖型描述符与非覆盖型描述符
描述符分为覆盖型和非覆盖型的,覆盖型与非覆盖型的区别在于是否实现了__set__函数。
实现了__set__函数的话,我们对类对象的属性赋值时,就会优先访问它相应描述符的__set__函数;没有实现__set__函数的话,对类对象成员进行赋值则会将描述符遮盖:
class Quantity(object):
count=0
def __init__(self):
self.name = '{}'.format(Quantity.count)
Quantity.count += 1
def __get__(self, instance, owner):
print "666"
return 2
def __set__(self, instance, val):
setattr(instance, self.name, val)
class Obj(object):
weight = Quantity()
price = Quantity()
def __init__(self, weight, price):
pass
obj = Obj(1,2)
obj.weight = 3
print obj.weight, obj.__dict__
输出为:
666
2 {'0': 3}
可以看到,当存在覆盖型描述符时,对weight的赋值会直接调用weight描述符的__set__方法,实际更改的是__dict__中的"0"号对象。
让我们再看看非覆盖型的:
class Quantity(object):
count=0
def __init__(self):
self.name = '{}'.format(Quantity.count)
Quantity.count += 1
def __get__(self, instance, owner):
print "666"
return 2
class Obj(object):
weight = Quantity()
price = Quantity()
def __init__(self, weight, price):
pass
obj = Obj(1,2)
print obj.weight
obj.weight = 3
print obj.weight, obj.__dict__
输出为:
666
2
3 {'weight': 3}
可以看到,当我们只是访问weight时,会调用描述符的__get__方法;而当我们为weight赋值时,程序会在对象的__dict__里创建一个属性,并覆盖描述符。
类的方法也是一种描述符
值得一提的是,类中定义的方法其实也是一种描述符。方法自己是function对象,定义了__get__函数(没有定义__set__,因此它们是非覆盖型描述符),当我们调用类对象的方法成员时,__get__函数调用,并返回一个绑定的方法对象(bound method),该对象的__func__成员即是真正引用的原始函数。通过调用绑定方法对象的__call__方法,来调用__func__,最终将函数执行起来。
最后感谢刘佳卉大佬建议我用Markdown来写博客。