Python描写叙述符(descriptor)解密
Python中包括了很多内建的语言特性,它们使得代码简洁且易于理解。这些特性包括列表/集合/字典推导式,属性(property)、以及装饰器(decorator)。对于大部分特性来说,这些“中级”的语言特性有着完好的文档。而且易于学习。
可是这里有个例外,那就是描写叙述符。
至少对于我来说。描写叙述符是Python语言核心中困扰我时间最长的一个特性。
这里有几点原因例如以下:
- 有关描写叙述符的官方文档相当难懂,并且没有包括优秀的演示样例告诉你为什么须要编写描写叙述符(我得为Raymond Hettinger辩护一下。他写的其它主题的Python文章和视频对我的帮助还是非常大的)
- 编写描写叙述符的语法显得有些怪异
- 自己定义描写叙述符可能是Python中用的最少的特性,因此你非常难在开源项目中找到优秀的演示样例
可是一旦你理解了之后,描写叙述符的确还是有它的应用价值的。
这篇文章告诉你描写叙述符能够用来做什么,以及为什么应该引起你的注意。
一句话概括:描写叙述符就是可重用的属性
在这里我要告诉你:从根本上讲。描写叙述符就是能够反复使用的属性。
也就是说,描写叙述符能够让你编写这种代码:
b = f.bar
f.bar = c
del f.bar
1
2
3
4
|
f=
Foo()
b =f.bar
f.bar=
c
del f.bar
|
而在解释器运行上述代码时,当发现你试图訪问属性(b = f.bar)、对属性赋值(f.bar = c)或者删除一个实例变量的属性(del f.bar)时,就会去调用自己定义的方法。
让我们先来解释一下为什么把对函数的调用伪装成对属性的訪问是大有优点的。
property——把函数调用伪装成对属性的訪问
想象一下你正在编写管理电影信息的代码。你最后写好的Movie类可能看上去是这种:
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
1
2
3
4
5
6
7
8
9
10
|
classMovie(object):
def__init__(self,title,rating,runtime,budget,gross):
self.title=
title
self.rating=
rating self.runtime=
runtime
self.budget=
budget self.gross=
gross defprofit(self):
returnself.gross-
self.budget |
你開始在项目的其它地方使用这个类,可是之后你意识到:假设不小心给电影打了负分怎么办?你认为这是错误的行为,希望Movie类能够阻止这个错误。
你首先想到的办法是将Movie类改动为这样:
def __init__(self, title, rating, runtime, budget, gross):
self.title = title
self.rating = rating
self.runtime = runtime
self.gross = gross
if budget < 0:
raise ValueError("Negative value not allowed: %s" % budget)
self.budget = budget
def profit(self):
return self.gross - self.budget
1
2
3
4
5
6
7
8
9
10
11
12
|
classMovie(object):
def__init__(self,title,rating,runtime,budget,gross):
self.title=
title
self.rating=
rating self.runtime=
runtime
self.gross=
gross ifbudget
<0:
raiseValueError("Negative value not allowed: %s"%
budget) self.budget=
budget defprofit(self):
returnself.gross-
self.budget |
但这行不通。由于其它部分的代码都是直接通过Movie.budget来赋值的——这个新改动的类仅仅会在__init__方法中捕获错误的数据,但对于已经存在的类实例就无能为力了。
假设有人试着执行m.budget = -100,那么谁也没法阻止。作为一个Python程序猿同一时候也是电影迷,你该怎么办?
幸运的是。Python的property攻克了这个问题。
假设你从未见过property的使用方法。以下是一个演示样例:
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"
964000
Woops. Not allowed
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
28
29
30
31
32
|
classMovie(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
defbudget(self):
returnself._budget
@budget.setter
defbudget(self,value):
ifvalue
<0:
raiseValueError("Negative value not allowed: %s"%
value) self._budget=
value defprofit(self):
returnself.gross-
self.budget
m =Movie('Casablanca',97,102,964000,1300000)
printm.budget #
calls m.budget(), returns result
try:
m.budget=
-100 # calls budget.setter(-100), and raises ValueError
except ValueError:
print"Woops. Not allowed"
964000
Woops.Not
allowed |
我们用@property装饰器指定了一个getter方法,用@budget.setter装饰器指定了一个setter方法。
当我们这么做时,每当有人试着訪问budget属性。Python就会自己主动调用对应的getter/setter方法。例如说,当遇到m.budget = value这种代码时就会自己主动调用budget.setter。
花点时间来赞赏一下Python这么做是多么的优雅:假设没有property,我们将不得不把全部的实例属性隐藏起来。提供大量显式的类似get_budget和set_budget方法。像这样编写类的话。使用起来就会不断的去调用这些getter/setter方法。这看起来就像臃肿的Java代码一样。更糟的是。假设我们不採用这种编码风格,直接对实例属性进行訪问。那么稍后就没法以清晰的方式添加对非负数的条件检查——我们不得不又一次创建set_budget方法,然后搜索整个project中的源码,将m.budget = value这种代码替换为m.set_budget(value)。太蛋疼了!!
因此,property让我们将自己定义的代码同变量的訪问/设定联系在了一起,同一时候为你的类保持一个简单的訪问属性的接口。干得美丽!
property的不足
对property来说。最大的缺点就是它们不能反复使用。举个样例。如果你想为rating,runtime和gross这些字段也加入非负检查。以下是改动过的新类:
def __init__(self, title, rating, runtime, budget, gross):
self._rating = None
self._runtime = None
self._budget = None
self._gross = None
self.title = title
self.rating = rating
self.runtime = runtime
self.gross = gross
self.budget = budget
#nice
@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
#ok
@property
def rating(self):
return self._rating
@rating.setter
def rating(self, value):
if value < 0:
raise ValueError("Negative value not allowed: %s" % value)
self._rating = value
#uhh...
@property
def runtime(self):
return self._runtime
@runtime.setter
def runtime(self, value):
if value < 0:
raise ValueError("Negative value not allowed: %s" % value)
self._runtime = value
#is this forever?
@property
def gross(self):
return self._gross
@gross.setter
def gross(self, value):
if value < 0:
raise ValueError("Negative value not allowed: %s" % value)
self._gross = value
def profit(self):
return self.gross - self.budget
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
classMovie(object):
def__init__(self,title,rating,runtime,budget,gross):
self._rating=
None
self._runtime=
None self._budget=
None
self._gross=
None
self.title=
title self.rating=
rating
self.runtime=
runtime self.gross=
gross
self.budget=
budget
#nice
@property
defbudget(self):
returnself._budget
@budget.setter
defbudget(self,value):
ifvalue
<0:
raiseValueError("Negative value not allowed: %s"%
value) self._budget=
value #ok
@property
defrating(self):
returnself._rating
@rating.setter
defrating(self,value):
ifvalue
<0: raiseValueError("Negative
value not allowed: %s"% value)
self._rating=
value
#uhh...
@property
defruntime(self):
returnself._runtime
@runtime.setter
defruntime(self,value):
ifvalue
<0:
raiseValueError("Negative value not allowed: %s"%
value) self._runtime=
value #is this forever?
@property
defgross(self):
returnself._gross
@gross.setter
defgross(self,value):
ifvalue
<0: raiseValueError("Negative
value not allowed: %s"% value)
self._gross=
value
defprofit(self):
returnself.gross-
self.budget |
能够看到代码添加了不少。但反复的逻辑也出现了不少。尽管property能够让类从外部看起来接口整洁美丽。可是却做不到内部相同整洁美丽。
描写叙述符登场(终于的大杀器)
这就是描写叙述符所解决的问题。
描写叙述符是property的升级版。同意你为反复的property逻辑编写单独的类来处理。
以下的演示样例展示了描写叙述符是怎样工作的(如今还不必操心NonNegative类的实现):
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"
964000
Woops, negative value
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
fromweakref
importWeakKeyDictionary classNonNegative(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)
returnself.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
ifvalue
<0:
raiseValueError("Negative value not allowed: %s"%
value) self.data[instance]=
value classMovie(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
defprofit(self):
returnself.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"
964000
Woops,negative
value |
这里引入了一些新的语法,我们一条条的来看:
NonNegative是一个描写叙述符对象,由于它定义了__get__,__set__或__delete__方法。
Movie类如今看起来很清晰。我们在类的层面上创建了4个描写叙述符。把它们当做普通的实例属性。
显然,描写叙述符在这里为我们做非负检查。
訪问描写叙述符
当解释器遇到print m.buget时。它就会把budget当作一个带有__get__ 方法的描写叙述符,调用Movie.budget.__get__方法并将方法的返回值打印出来。而不是直接传递m.budget来打印。这和你訪问一个property相似,Python自己主动调用一个方法,同一时候返回结果。
__get__接收2个參数:一个是点号左边的实例对象(在这里,就是m.budget中的m),还有一个是这个实例的类型(Movie)。在一些Python文档中。Movie被称作描写叙述符的全部者(owner)。假设我们须要訪问Movie.budget,Python将会调用Movie.budget.__get__(None,
Movie)。
能够看到,第一个參数要么是全部者的实例。要么是None。这些输入參数可能看起来非常怪。可是这里它们告诉了你描写叙述符属于哪个对象的一部分。当我们看到NonNegative类的实现时这一切就合情合理了。
对描写叙述符赋值
当解释器看到m.rating = 100时,Python识别出rating是一个带有__set__方法的描写叙述符。于是就调用Movie.rating.__set__(m, 100)。
和__get__一样。__set__的第一个參数是点号左边的类实例(m.rating = 100中的m)。第二个參数是所赋的值(100)。
删除描写叙述符
为了说明的完整,这里提一下删除。假设你调用del m.budget,Python就会调用Movie.budget.__delete__(m)。
NonNegative类是怎样工作的?
带着前面的困惑,我们最终要揭示NonNegative类是怎样工作的了。每一个NonNegative的实例都维护着一个字典,当中保存着全部者实例和相应数据的映射关系。
当我们调用m.budget时,__get__方法会查找与m相关联的数据,并返回这个结果(假设这个值不存在,则会返回一个默认值)。__set__採用的方式同样。可是这里会包括额外的非负检查。
我们使用WeakKeyDictionary来代替普通的字典以防止内存泄露——我们可不想只由于它在描写叙述符的字典中就让一个无用 的实例一直存活着。
使用描写叙述符会有一点别扭。由于它们作用于类的层次上,每个类实例都共享同一个描写叙述符。
这就意味着对不同的实例对象而言,描写叙述符不得不手动地管理 不同的状态,同一时候须要显式的将类实例作为第一个參数准确传递给__get__、__set__以及__delete__方法。
我希望这个样例解释清楚了描写叙述符能够用来做什么——它们提供了一种方法将property的逻辑隔离到单独的类中来处理。假设你发现自己正在不同的property之间反复着同样的逻辑。那么本文或许会成为一个线索供你思考为何用描写叙述符重构代码是值得一试的。
秘诀和陷阱
把描写叙述符放在类的层次上(class level)
为了让描写叙述符可以正常工作。它们必须定义在类的层次上。
假设你不这么做,那么Python无法自己主动为你调用__get__和__set__方法。
y = NonNegative(5)
def __init__(self):
self.x = NonNegative(0) # NOT a good descriptor
b = Broken()
print "X is %s, Y is %s" % (b.x, b.y)
X is <__main__.NonNegative object at 0x10432c250>, Y is 5
1
2
3
4
5
6
7
8
9
|
classBroken(object):
y=
NonNegative(5) def__init__(self):
self.x=
NonNegative(0) # NOT a good descriptor
b =Broken()
print"X is %s, Y is %s"
% (b.x,b.y) Xis
<__main__.NonNegativeobject at0x10432c250>,Y is5 |
能够看到,訪问类层次上的描写叙述符y能够自己主动调用__get__。
可是訪问实例层次上的描写叙述符x仅仅会返回描写叙述符本身。真是魔法一般的存在啊。
确保实例的数据仅仅属于实例本身
你可能会像这样编写NonNegative描写叙述符:
def __init__(self, default):
self.value = default
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if value < 0:
raise ValueError("Negative value not allowed: %s" % value)
self.value = value
class Foo(object):
bar = BrokenNonNegative(5)
f = Foo()
try:
f.bar = -1
except ValueError:
print "Caught the invalid assignment"
Caught the invalid assignment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
classBrokenNonNegative(object):
def__init__(self,default):
self.value=
default def__get__(self,instance,owner):
returnself.value
def__set__(self,instance,value):
ifvalue
<0:
raiseValueError("Negative value not allowed: %s"%
value) self.value=
value classFoo(object):
bar=
BrokenNonNegative(5)
f =Foo()
try:
f.bar=
-1 exceptValueError:
print"Caught the invalid assignment"
Caught the invalidassignment
|
这么做看起来似乎能正常工作。但这里的问题就在于全部Foo的实例都共享同样的bar,这会产生一些令人痛苦的结果:
bar = BrokenNonNegative(5)
f = Foo()
g = Foo()
print "f.bar is %s\ng.bar is %s" % (f.bar, g.bar)
print "Setting f.bar to 10"
f.bar = 10
print "f.bar is %s\ng.bar is %s" % (f.bar, g.bar) #ouch
f.bar is 5
g.bar is 5
Setting f.bar to 10
f.bar is 10
g.bar is 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
classFoo(object):
bar=
BrokenNonNegative(5)
f =Foo()
g=
Foo() print"f.bar is %s\ng.bar is %s"%
(f.bar,g.bar)
print "Setting f.bar to 10"
f.bar=
10
print "f.bar is %s\ng.bar is %s"%
(f.bar,g.bar) #ouch f.baris
5
g.baris
5 Settingf.barto
10
f.baris
10 g.baris
10 |
这就是为什么我们要在NonNegative中使用数据字典的原因。__get__和__set__的第一个參数告诉我们须要关心哪一个实例。NonNegative使用这个參数作为字典的key,为每个Foo实例单独保存一份数据。
bar = NonNegative(5)
f = Foo()
g = Foo()
print "f.bar is %s\ng.bar is %s" % (f.bar, g.bar)
print "Setting f.bar to 10"
f.bar = 10
print "f.bar is %s\ng.bar is %s" % (f.bar, g.bar) #better
f.bar is 5
g.bar is 5
Setting f.bar to 10
f.bar is 10
g.bar is 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
classFoo(object):
bar=
NonNegative(5)
f =Foo()
g=
Foo()
print "f.bar is %s\ng.bar is %s"%
(f.bar,g.bar) print"Setting f.bar to 10"
f.bar=
10 print"f.bar is %s\ng.bar is %s"%
(f.bar,g.bar) #better
f.baris
5 g.baris
5
Setting f.barto
10 f.baris
10
g.baris
5 |
这就是描写叙述符最令人感到别扭的地方(坦白的说。我不理解为什么Python不让你在实例的层次上定义描写叙述符。而且总是须要将实际的处理分发给__get__和__set__。
这么做行不通一定是有原因的)
注意不可哈希的描写叙述符全部者
NonNegative类使用了一个字典来单独保存专属于实例的数据。这个一般来说是没问题的,除非你用到了不可哈希(unhashable)的对象:
x = NonNegative(5)
m = MoProblems()
print m.x # womp womp
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-8-dd73b177bd8d> in <module>()
3
4 m = MoProblems()
----> 5 print m.x # womp womp
<ipython-input-3-6671804ce5d5> in __get__(self, instance, owner)
9 # instance = x
10 # owner = type(x)
---> 11 return self.data.get(instance, self.default)
12
13 def __set__(self, instance, value):
TypeError: unhashable type: 'MoProblems'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
classMoProblems(list): #you
can't use lists as dictionary keys
x=
NonNegative(5)
m =MoProblems()
printm.x #
womp womp ---------------------------------------------------------------------------
TypeError Traceback
(mostrecent call last) <ipython-input-8-dd73b177bd8d>in
<module>()
3
4m
=MoProblems()
---->5
printm.x # womp womp
<ipython-input-3-6671804ce5d5>in
__get__(self,instance,owner) 9 # instance = x
10 # owner = type(x)
--->11
returnself.data.get(instance,self.default)
12
13
def__set__(self,instance,value): TypeError:unhashable
type:'MoProblems' |
由于MoProblems的实例(list的子类)是不可哈希的,因此它们不能为MoProblems.x用做数据字典的key。有一些方法能够规避这个问题,可是都不完美。最好的方法可能就是给你的描写叙述符加标签了。
def __init__(self, label):
self.label = label
def __get__(self, instance, owner):
print '__get__', instance, owner
return instance.__dict__.get(self.label)
def __set__(self, instance, value):
print '__set__'
instance.__dict__[self.label] = value
class Foo(list):
x = Descriptor('x')
y = Descriptor('y')
f = Foo()
f.x = 5
print f.x
__set__
__get__ [] <class '__main__.Foo'>
5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
classDescriptor(object):
def__init__(self,label):
self.label=
label
def__get__(self,instance,owner):
print'__get__',instance,owner
returninstance.__dict__.get(self.label)
def__set__(self,instance,value):
print'__set__'
instance.__dict__[self.label]=
value
class Foo(list):
x=
Descriptor('x')
y=
Descriptor('y')
f =Foo()
f.x=
5
print f.x
__set__
__get__[]<class'__main__.Foo'>
5
|
这样的方法依赖于Python的方法解析顺序(即,MRO)。
我们给Foo中的每一个描写叙述符加上一个标签名。名称和我们赋值给描写叙述符的变量名同样,比方x = Descriptor(‘x’)。
之后。描写叙述符将特定于实例的数据保存在f.__dict__['x']中。这个字典条目一般是当我们请求f.x时Python给出的返回值。然而,因为Foo.x 是一个描写叙述符,Python不能正常的使用f.__dict__[‘x’]。可是描写叙述符能够安全的在这里存储数据。
仅仅是要记住,不要在别的地方也给这个描写叙述符加入标签。
x = Descriptor('y')
f = Foo()
f.x = 5
print f.x
f.y = 4 #oh no!
print f.x
__set__
__get__ <__main__.Foo object at 0x10432c810> <class '__main__.Foo'>
5
__get__ <__main__.Foo object at 0x10432c810> <class '__main__.Foo'>
4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
classFoo(object):
x=
Descriptor('y')
f =Foo()
f.x=
5
print f.x
f.y=
4 #oh no! printf.x
__set__
__get__<__main__.Fooobject
at0x10432c810><class'__main__.Foo'>
5
__get__<__main__.Fooobject
at0x10432c810><class'__main__.Foo'>
4
|
我不喜欢这种方式,由于这种代码非常脆弱也有非常多微妙之处。但这种方法的确非常普遍。能够用在不可哈希的全部者类上。David Beazley在他的书中用到了这种方法。
在元类中使用带标签的描写叙述符
因为描写叙述符的标签名和赋给它的变量名同样。所以有人使用元类来自己主动处理这个簿记(bookkeeping)任务。
def __init__(self):
#notice we aren't setting the label here
self.label = None
def __get__(self, instance, owner):
print '__get__. Label = %s' % self.label
return instance.__dict__.get(self.label, None)
def __set__(self, instance, value):
print '__set__'
instance.__dict__[self.label] = value
class DescriptorOwner(type):
def __new__(cls, name, bases, attrs):
# find all descriptors, auto-set their labels
for n, v in attrs.items():
if isinstance(v, Descriptor):
v.label = n
return super(DescriptorOwner, cls).__new__(cls, name, bases, attrs)
class Foo(object):
__metaclass__ = DescriptorOwner
x = Descriptor()
f = Foo()
f.x = 10
print f.x
__set__
__get__. Label = x
10
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
28
29
30
31
32
|
classDescriptor(object):
def__init__(self):
#notice we aren't setting the label here
self.label=
None
def__get__(self,instance,owner):
print'__get__. Label = %s'%
self.label
returninstance.__dict__.get(self.label,None)
def__set__(self,instance,value):
print'__set__'
instance.__dict__[self.label]=
value
class DescriptorOwner(type):
def__new__(cls,name,bases,attrs):
# find all descriptors, auto-set their labels
forn,v
inattrs.items():
ifisinstance(v,Descriptor):
v.label=
n
returnsuper(DescriptorOwner,cls).__new__(cls,name,bases,attrs)
class Foo(object):
__metaclass__=
DescriptorOwner
x=
Descriptor()
f =Foo()
f.x=
10
print f.x
__set__
__get__.Label
=x
10
|
我不会去解释有关元类的细节——參考文献中David Beazley已经在他的文章中解释的非常清楚了。 须要指出的是元类自己主动的为描写叙述符加入标签。而且和赋给描写叙述符的变量名字相匹配。
虽然这样攻克了描写叙述符的标签和变量名不一致的问题。可是却引入了复杂的元类。
虽然我非常怀疑,可是你能够自行推断这么做是否值得。
訪问描写叙述符的方法
描写叙述符不过类。或许你想要为它们添加一些方法。举个样例,描写叙述符是一个用来回调property的非常好的手段。比方我们想要一个类的某个部分的状态发生变化时就立马通知我们。以下的大部分代码是用来做这个的:
"""A property that will alert observers when upon updates"""
def __init__(self, default=None):
self.data = WeakKeyDictionary()
self.default = default
self.callbacks = WeakKeyDictionary()
def __get__(self, instance, owner):
return self.data.get(instance, self.default)
def __set__(self, instance, value):
for callback in self.callbacks.get(instance, []):
# alert callback function of new value
callback(value)
self.data[instance] = value
def add_callback(self, instance, callback):
"""Add a new function to call everytime the descriptor updates"""
#but how do we get here?
!?!
if instance not in self.callbacks:
self.callbacks[instance] = []
self.callbacks[instance].append(callback)
class BankAccount(object):
balance = CallbackProperty(0)
def low_balance_warning(value):
if value < 100:
print "You are poor"
ba = BankAccount()
# will not work -- try it
#ba.balance.add_callback(ba, low_balance_warning)
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
28
29
30
31
32
33
34
|
classCallbackProperty(object):
"""A property that will alert observers when upon updates"""
def__init__(self,default=None):
self.data=
WeakKeyDictionary() self.default=
default
self.callbacks=
WeakKeyDictionary()
def__get__(self,instance,owner):
returnself.data.get(instance,self.default)
def__set__(self,instance,value):
forcallback
inself.callbacks.get(instance,[]): # alert callback function of new value
callback(value)
self.data[instance]=
value defadd_callback(self,instance,callback):
"""Add a new function to call everytime the descriptor updates"""
#but how do we get here?!?!
ifinstance
notin self.callbacks: self.callbacks[instance]=
[]
self.callbacks[instance].append(callback)
class BankAccount(object):
balance=
CallbackProperty(0) deflow_balance_warning(value):
ifvalue
<100: print"You are poor"
ba=
BankAccount() # will not work -- try it
#ba.balance.add_callback(ba, low_balance_warning)
|
这是一个非常有吸引力的模式——我们能够自己定义回调函数用来响应一个类中的状态变化,并且全然无需改动这个类的代码。这样做可真是替人分忧解难呀。如今,我们所要做的就是调用ba.balance.add_callback(ba, low_balance_warning)。以使得每次balance变化时low_balance_warning都会被调用。
可是我们是怎样做到的呢?当我们试图訪问它们时,描写叙述符总是会调用__get__。就好像add_callback方法是无法触及的一样!事实上关键在于利用了一种特殊的情况,即,当从类的层次訪问时,__get__方法的第一个參数是None。
"""A property that will alert observers when upon updates"""
def __init__(self, default=None):
self.data = WeakKeyDictionary()
self.default = default
self.callbacks = WeakKeyDictionary()
def __get__(self, instance, owner):
if instance is None:
return self
return self.data.get(instance, self.default)
def __set__(self, instance, value):
for callback in self.callbacks.get(instance, []):
# alert callback function of new value
callback(value)
self.data[instance] = value
def add_callback(self, instance, callback):
"""Add a new function to call everytime the descriptor within instance updates"""
if instance not in self.callbacks:
self.callbacks[instance] = []
self.callbacks[instance].append(callback)
class BankAccount(object):
balance = CallbackProperty(0)
def low_balance_warning(value):
if value < 100:
print "You are now poor"
ba = BankAccount()
BankAccount.balance.add_callback(ba, low_balance_warning)
ba.balance = 5000
print "Balance is %s" % ba.balance
ba.balance = 99
print "Balance is %s" % ba.balance
Balance is 5000
You are now poor
Balance is 99
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
classCallbackProperty(object):
"""A property that will alert observers when upon updates"""
def__init__(self,default=None):
self.data=
WeakKeyDictionary() self.default=
default
self.callbacks=
WeakKeyDictionary()
def__get__(self,instance,owner):
ifinstance
isNone:
returnself
returnself.data.get(instance,self.default)
def__set__(self,instance,value):
forcallback
inself.callbacks.get(instance,[]): # alert callback function of new value
callback(value)
self.data[instance]=
value defadd_callback(self,instance,callback):
"""Add a new function to call everytime the descriptor within instance updates"""
ifinstance
notin self.callbacks:
self.callbacks[instance]=
[] self.callbacks[instance].append(callback)
classBankAccount(object):
balance=
CallbackProperty(0)
def low_balance_warning(value):
ifvalue
<100:
print"You are now poor"
ba =BankAccount()
BankAccount.balance.add_callback(ba,low_balance_warning)
ba.balance=
5000
print "Balance is %s"%
ba.balance ba.balance=
99
print "Balance is %s"%
ba.balance Balanceis
5000
You are nowpoor
Balanceis
99 |
结语
希望你如今对描写叙述符是什么和它们的适用场景有了一个认识。前进吧骚年!
Python 内置装饰器
Python内置的装饰器有三个:staticmethod、classmethod和property,起作用分别为:把类中定义的实例方法变成静态方法、类方法和属性方法。
@staticmethod
使用staticmethod装饰类的方法后,能够使用c.f()或者c().f()去调用。不须要传入self。
@classmethod
须要传入类对象,能够使用c.f()或者c().f()去调用。并将该class对象(不是class的实例对象)隐式地当作第一个參数传入。
staticmethod和classmethod的差别
staticmethod,classmethod相当于全局方法,一般用在抽象类或父类中。
一般与详细的类无关。类方法须要额外的类变量cls,当有子类继承时,调用类方法传入的类变量cls是子类,而不是父类。类方法和静态方法都能够通过类对象和类的实例对象訪问定义方式,传入的參数,调用方式都不同样。
@property
把函数方法变成属性,下面是经典样例:
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
if __name__ == "__main__":
c = C()
print c.x
c.x = 1
print c.x
del c.x
print c.x
參考文献
- Encapsulation With Descriptors by Luciano Ramalho
- Python 3 Metaprogramming by David Beazley
- Python’s class development toolkit by Raymond Hettinger
原文链接: Chris Beaumont 翻译: 极客范-慕容老匹夫
Python描写叙述符(descriptor)解密的更多相关文章
- Shell下的通配符、特殊符号和文件描写叙述符
一:通配符 * 代表『 0 个到无穷多个』随意字符 演示样例:找出 /etc/ 底下以 cron 为开头的文件名称的文件 [root@instructor Desktop]# ls /etc/cron ...
- 每天进步一点点——Linux中的文件描写叙述符与打开文件之间的关系
转载请说明出处:http://blog.csdn.net/cywosp/article/details/38965239 1. 概述 在Linux系统中一切皆能够看成是文件,文件又可分为:普通 ...
- openssl之BIO系列之12---文件描写叙述符(fd)类型BIO
文件描写叙述符(fd)类型BIO ---依据openssl doc\crypto\bio_s_fd.pod翻译和自己的理解写成 (作者:DragonKing Mailwzhah@263.net 公布于 ...
- 【c++】虚函数描写叙述符override
在C++11中为了帮助程序猿写继承结构复杂的类型,引入了虚函数描写叙述符override,假设派生类在虚函数声明时使用了override描写叙述符,那么该函数必须重载其基类中的同名函数,否则代码将无法 ...
- linux c 通过文件描写叙述符获取文件名称
在linux中每一个被打开的文件都会在/proc/self/fd/文件夹中有记录,当中(/proc/self/fd/文件描写叙述符号:这个文件是符号文件)的文件就是文件描写叙述符所相应的文件. 而re ...
- 进程与进程描写叙述符(task_struct)
一. 进程 进程(Process) 计算机中的程序关于某数据集合上的一次执行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础.在早期面向进程设计的计算机结构中,进程是程序的基本执行实体: ...
- Linux 文件描写叙述符设置为非堵塞的方法
通过fcntl设置文件描写叙述符属性 fcntl即F_SETFL,F_GETFL的使用,设置文件的flags,堵塞设置成非堵塞,非堵塞设置成堵塞(这连个在server开发中能够封装为基本函数) 1.获 ...
- 【python】描述符descriptor
开始看官方文档,各种看不懂,只看到一句Properties, bound and unbound methods, static methods, and class methods are all ...
- python理解描述符(descriptor)
Descriptor基础 python中的描述符可以用来定义触发自动执行的代码,它像是一个对象属性操作(访问.赋值.删除)的代理类一样.前面介绍过的property是描述符的一种. 大致流程是这样的: ...
随机推荐
- linux 进程命令小结
top:动态查看进程的变化 ps:将某个时间点的进程运行情况选取下来 ps -l :仅查看自己的bash相关进程 ps -aux :查看系统所有的进程 pstree :列出目前系统上面所有的进程树的相 ...
- 《深入理解Android2》读书笔记(五)
接上篇<深入理解Android2>读书笔记(四) startActivity Am void run() throws RemoteException { try { printMessa ...
- ubuntu下基于Anaconda使用Tensorflow
为了在ubuntu下利用Anaconda使用tensorflow,但在利用conda安装tensorflow,不能在终端,spyder和notebook中直接使用,需要我们进行一定的配置. 1.安装A ...
- go chapter 8 - 初始化对象
http://blog.haohtml.com/archives/14239 struct定义的属性如果是小写开头的,那么该属性不是public的,不能跨包调用 (implicit assignmen ...
- linux——(1)初识linux
linux有窗口管理员环境和纯文本界面环境,同时linux默认提供6个Terminal来让用户登录.crtl+alt+F1-6可自由切换.其中如果窗口管理员环境处于运行状态,那么可以按crtl+alt ...
- 连接LilyPad之Windows平台的驱动
连接LilyPad之Windows平台的驱动 LilyPad和其他的Arduino控制板的不同之处是它是为电子织物和可穿戴设计的.那么,它的大小就必须要紧凑.所以,它并没有板载其他大多数板子都具有的U ...
- Web应用主动侦测工具Skipfish
Web应用主动侦测工具Skipfish Skipfish是Kali Linux附带的一个主动Web应用侦测工具.该工具会首先尽可能获取所有网站路径,进行访问,然后根据返回的内容,检测是否存在漏洞. ...
- POJ - 1835 宇航员(模拟题)
问题描述: 宇航员在太空中迷失了方向,在他的起始位置现在建立一个虚拟xyz坐标系,称为绝对坐标系,宇航员正面的方向为x轴正方向,头顶方向为z轴正方向,则宇航员的初始状态如下图所示: 现对六个方向分别标 ...
- BZOJ 3997 [TJOI2015]组合数学(单调DP)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3997 [题目大意] 给出一个网格图,其中某些格子有财宝,每次从左上角出发,只能向下或右 ...
- File I/O知识点
问题1:File类的作用? 解答:File 类用于访问文件或目录的属性.File类位于java.io包中. 问题2:流?及流的分类? 解答:流是指一连串流动的字符,是以先进先出的方式发送信息的通道.程 ...