Python 描述符
Python 描述符
什么是描述符(Descriptor)
定义了 __get__()
,__set__()
,__delete__()
中任意一个描述符协议的对象成为描述符。简单来说,描述符就是可重用的属性。描述符具有诸多优点,诸如:保护属性不受修改、属性类型检查和自动更新某个依赖属性的值等。
descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None
__get__
方法接受两个参数,一个是实例对象,另一个是实例的类型。__set__
方法接受两个参数,一个是实例对象,另一个是数值。而 __delete__
方法包含一个实例对象参数,并在实例对象被删除时调用。
__dict__
(每个对象均具备该属性)字典类型,存放本对象的属性,key(键)即为属性名,value(值)即为属性的值,形式为 {attr_key : attr_value}
Python 对象的属性控制默认是这样的,从对象的字典 __dict__
中获取 get,设置 set ,删除 delete ,比如:对于实例 a
,a.x
的查找顺序为 a.__dict__['x']
,然后是type(a).__dict__['x']
.如果还是没找到就往上级(父类)中查找。具体顺序见
属性访问顺序
。
描述符会改变这种默认的控制行为,如果属性 x
是一个描述符,那么访问 a.x
时不再从字典 __dict__
中读取,而是调用描述符的 __get__()
方法,对于设置和删除也是同样的原理。
property 属性
通过 property
将函数调用伪装成属性调用。
#!/usr/bin/env python
class Movie(object):
def __init__(self, title, rating, runtime, budget, gross):
self._budget = None
self.title = title
self.rating = rating
self.runtime = runtime
self.gross = gross
self.budget = budget
@property
def budget(self):
return self._budget
@budget.setter
def budget(self, value):
if value < 0:
raise ValueError("Negative value not allowed: %s" % value)
self._budget = value
def profit(self):
return self.gross - self.budget
m = Movie('Casablanca', 97, 102, 964000, 1300000)
print m.budget # calls m.budget(), returns result
try:
m.budget = -100 # calls budget.setter(-100), and raises ValueError
except ValueError:
print "Woops. Not allowed"
# results
964000
Woops. Not allowed
当试图访问 budget 属性,Python 就会自动调用相应的 getter/setter
方法。比方说,当遇到 m.budget = value
这样的代码时就会自动调用 budget.setter
。
property
将自定义的代码同变量的访问/设定联系在了一起,同时使类保持一个简单的访问属性的接口。
但是当使用 property
对参数进行检查,需要对每个属性进行检查无法对代码进行复用。
描述符
#!/usr/bin/env python
from weakref import WeakKeyDictionary
class NonNegative(object):
"""A descriptor that forbids negative values"""
def __init__(self, default):
self.default = default
self.data = WeakKeyDictionary()
def __get__(self, instance, owner):
# we get here when someone calls x.d, and d is a NonNegative instance
# instance = x
# owner = type(x)
return self.data.get(instance, self.default)
def __set__(self, instance, value):
# we get here when someone calls x.d = val, and d is a NonNegative instance
# instance = x
# value = val
if value < 0:
raise ValueError("Negative value not allowed: %s" % value)
self.data[instance] = value
class Movie(object):
#always put descriptors at the class-level
rating = NonNegative(0)
runtime = NonNegative(0)
budget = NonNegative(0)
gross = NonNegative(0)
def __init__(self, title, rating, runtime, budget, gross):
self.title = title
self.rating = rating
self.runtime = runtime
self.budget = budget
self.gross = gross
def profit(self):
return self.gross - self.budget
m = Movie('Casablanca', 97, 102, 964000, 1300000)
print m.budget # calls Movie.budget.__get__(m, Movie)
m.rating = 100 # calls Movie.budget.__set__(m, 100)
try:
m.rating = -1 # calls Movie.budget.__set__(m, -100)
except ValueError:
print "Woops, negative value"
# results
964000
Woops, negative value
当函数调用 m.budget
时,会将其视为具有 __get__
方法的描述符,然后调用 Moive.budget.__get__
返回结果。