Python描述符(descriptor)
定义:
如果一个类,它含有__get__,__set__,__del__中的任何一个,那么都可以说这个类是一个描述符。
对于一个类的属性访问,默认行为是从类的字典中set,get,del属性,查找顺序是a.__dict__['x'], 然后type(a).__dict__['x']。那如果定义了描述符,__set__,__get__,__del__会覆盖这些属性,直接访问描述符中的这些方法。
class myDescriptor(object):
def __get__(self,instance,cls):
return (self,instance,cls)
def __set__(self,instance,value):
print '%r %r %r' % (self,instance,value)
class Article(object):
art = myDescriptor()
a = Article()
a.art = 10
print a.art
输出:
<__main__.myDescriptor object at 0x0000000002F04748> <__main__.Article object at 0x0000000002F04780> 10
(<__main__.myDescriptor object at 0x0000000002F04748>, <__main__.Article object at 0x0000000002F04780>, <class '__main__.Article'>)
可以看出,a.art,a.art=10,并没有直接访问,a.__dict__['art'],而是直接调用了myDescriptor.__get__和__set__方法。
通过这种重载方式,可以定义许多特殊的行为。
作用1:
Python类中的方法其实就是描述符实现的。
类中的方法在命名空间中还是以普通函数的形式存在的,如:
class Test(object):
def t(self,a):
print a
te=Test()
te.t(1)
Test.t(te,1)
t方法其实等价于Test.__dict__['t']函数。
在看上面的例子,一个 def 定义的 Python 函数或一个 lambda 表达式,除了拥有__call__ 实现外,还拥有__get__ 实现。也就是说,所有 Python 函数都默认是一个描述符。这个描述符的特性在“类”这一对象上下文之外没有任何意义,但只要到了类中,就会和其他描述符所表现的一样,将 my_instance.my_method 重载为 MyClass.__dict__['my_method'].__get__(my_instance, MyClass) 。“绑定 self 参数” 这个过程正是 __get__ 的行为,也就是说te.t(1)和Test.t(te,1)是等价的。
也就是说,类中的方法是通过描述符实现的。
作用二:property
Property类本身就是一个描述符,property会返回新式类的一个property属性,它可以将方法的访问变成属性的直接访问,比较方便。
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
Property定义了描述的方法。
property类有setter,getter,deleter属性,如果没有setter,那么它就只有只读属性。
class Article(object):
def __init__(self,a,b):
self.a = a
self.b = b
self._c = None
@property
def c(self):
return self._c
@c.setter
def c(self,value):
if value < 0:
raise ValueError('the value must be bigger than 0')
else:
self._c = value
a = Article(2,3)
a.x = 100
print a.x
a.x = -100
输出:
100
Traceback (most recent call last):
File "C:\Python27\ths.py", line 41, in <module>
a.c = -100
File "C:\Python27\ths.py", line 32, in c
raise ValueError('the value must be bigger than 0')
ValueError: the value must be bigger than 0
上面代码中,访问c就直接像访问属性那样即可,也就是说你执行a.x其实执行了property的__get__方法。 且在为属性赋值的时候,如果小于0就会抛出一个异常。
作用三:自定义描述符
我们可以通过自定义描述符,从而实现一些特定的功能,比如延迟属性等等。
python对象的延迟初始化是指,当它第一次被创建时才进行初始化,或者保存第一次创建的结果,然后每次调用的时候直接返回该结果。
延迟初始化主要用于提高性能,避免浪费计算,并减少程序的内存需求。
下面的这个例子是werkzeug的一个描述器,它继承自父类property,并自定义了__set__和__get__方法。它就实现了延迟属性。
class _Missing(object):
def __repr__(self):
return 'no value'
def __reduce__(self):
return '_missing'
_missing = _Missing()
class cached_property(property):
"""A decorator that converts a function into a lazy property. The
function wrapped is called the first time to retrieve the result
and then that calculated result is used the next time you access
the value::
class Foo(object):
@cached_property
def foo(self):
# calculate something important here
return 42
The class has to have a `__dict__` in order for this property to
work.
"""
# implementation detail: A subclass of python's builtin property
# decorator, we override __get__ to check for a cached value. If one
# choses to invoke __get__ by hand the property will still work as
# expected because the lookup logic is replicated in __get__ for
# manual invocation.
def __init__(self, func, name=None, doc=None):
self.__name__ = name or func.__name__
self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__
self.func = func
def __set__(self, obj, value):
obj.__dict__[self.__name__] = value
def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.__name__, _missing)
if value is _missing:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value
class Foo(object):
@cached_property
def foo(self):
print 'start calcute'
# calculate something important here
return 42
f = Foo()
print f.foo start calcute ,42
print f.foo 42
第一次调用方法时将值存储起来,当第二次调用的时候就直接返回存储起来的值。这个例子是非常典型的例子,即写一个property的子类,并重构__get__,__set__等方法。__name__,__module__等属性也可以重构。
另外的例子是Django中model模型的定义:
class Category(models.Model):
name = models.CharField(max_length=150,unique=True,verbose_name=u'类名')
alias = models.CharField(max_length=150,verbose_name=u'英文名称')
status = models.IntegerField(default=0,choices=STATUS.items(),verbose_name=u'状态')
#Automatically set the field to now when the object is first created.
create_time = models.DateTimeField(auto_now_add=True)
属性name其实就是一个描述器,只有在执行SQL查询时才会初始化。
微信分享/微信扫码阅读