python如何计算分子描述符_Python——描述符(descriptor)解密
本文由 極客范 - 慕容老匹夫 翻譯自 Chris Beaumont。歡迎加入極客翻譯小組,同我們一道翻譯與分享。轉載請參見文章末尾處的要求。
Python中包含了許多內(nèi)建的語言特性,它們使得代碼簡潔且易于理解。這些特性包括列表/集合/字典推導式,屬性(property)、以及裝飾器(decorator)。對于大部分特性來說,這些“中級”的語言特性有著完善的文檔,并且易于學習。
但是這里有個例外,那就是描述符。至少對于我來說,描述符是Python語言核心中困擾我時間最長的一個特性。這里有幾點原因如下:
有關描述符的官方文檔相當難懂,而且沒有包含優(yōu)秀的示例告訴你為什么需要編寫描述符(我得為Raymond Hettinger辯護一下,他寫的其他主題的Python文章和視頻對我的幫助還是非常大的)
編寫描述符的語法顯得有些怪異
自定義描述符可能是Python中用的最少的特性,因此你很難在開源項目中找到優(yōu)秀的示例
但是一旦你理解了之后,描述符的確還是有它的應用價值的。這篇文章告訴你描述符可以用來做什么,以及為什么應該引起你的注意。
一句話概括:描述符就是可重用的屬性
在這里我要告訴你:從根本上講,描述符就是可以重復使用的屬性。也就是說,描述符可以讓你編寫這樣的代碼:
Python
f = Foo()
b = f.bar
f.bar = c
del f.bar
1
2
3
4
f=Foo()
b=f.bar
f.bar=c
delf.bar
而在解釋器執(zhí)行上述代碼時,當發(fā)現(xiàn)你試圖訪問屬性(b = f.bar)、對屬性賦值(f.bar = c)或者刪除一個實例變量的屬性(del f.bar)時,就會去調(diào)用自定義的方法。
讓我們先來解釋一下為什么把對函數(shù)的調(diào)用偽裝成對屬性的訪問是大有好處的。
property——把函數(shù)調(diào)用偽裝成對屬性的訪問
想象一下你正在編寫管理電影信息的代碼。你最后寫好的Movie類可能看上去是這樣的:
Python
class Movie(object):
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類修改為這樣:
Python
class Movie(object):
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__方法中捕獲錯誤的數(shù)據(jù),但對于已 經(jīng)存在的類實例就無能為力了。如果有人試著運行m.budget = -100,那么誰也沒法阻止。作為一個Python程序員同時也是電影迷,你該怎么辦?
幸運的是,Python的property解決了這個問題。如果你從未見過property的用法,下面是一個示例:
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"
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
exceptValueError:
print"Woops. Not allowed"
964000
Woops.Notallowed
我們用@property裝飾器指定了一個getter方法,用@budget.setter裝飾器指定了一個setter方法。當我們這么做時,每當有 人試著訪問budget屬性,Python就會自動調(diào)用相應的getter/setter方法。比方說,當遇到m.budget = value這樣的代碼時就會自動調(diào)用budget.setter。
花點時間來欣賞一下Python這么做是多么的優(yōu)雅:如果沒有property,我們將不得不把所有的實例屬性隱藏起來,提供大量顯式的類似 get_budget和set_budget方法。像這樣編寫類的話,使用起來就會不斷的去調(diào)用這些getter/setter方法,這看起來就像臃腫的 Java代碼一樣。更糟的是,如果我們不采用這種編碼風格,直接對實例屬性進行訪問。那么稍后就沒法以清晰的方式增加對非負數(shù)的條件檢查——我們不得不重 新創(chuàng)建set_budget方法,然后搜索整個工程中的源代碼,將m.budget = value這樣的代碼替換為m.set_budget(value)。太蛋疼了!!
因此,property讓我們將自定義的代碼同變量的訪問/設定聯(lián)系在了一起,同時為你的類保持一個簡單的訪問屬性的接口。干得漂亮!
property的不足
對property來說,最大的缺點就是它們不能重復使用。舉個例子,假設你想為rating,runtime和gross這些字段也添加非負檢查。下面是修改過的新類:
Python
class Movie(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
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
可以看到代碼增加了不少,但重復的邏輯也出現(xiàn)了不少。雖然property可以讓類從外部看起來接口整潔漂亮,但是卻做不到內(nèi)部同樣整潔漂亮。
描述符登場(最終的大殺器)
這就是描述符所解決的問題。描述符是property的升級版,允許你為重復的property邏輯編寫單獨的類來處理。下面的示例展示了描述符是如何工作的(現(xiàn)在還不必擔心NonNegative類的實現(xiàn)):
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"
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
fromweakrefimportWeakKeyDictionary
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)
printm.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)
exceptValueError:
print"Woops, negative value"
964000
Woops,negative value
這里引入了一些新的語法,我們一條條的來看:
NonNegative是一個描述符對象,因為它定義了__get__,__set__或__delete__方法。
Movie類現(xiàn)在看起來非常清晰。我們在類的層面上創(chuàng)建了4個描述符,把它們當做普通的實例屬性。顯然,描述符在這里為我們做非負檢查。
訪問描述符
當解釋器遇到print m.buget時,它就會把budget當作一個帶有__get__ 方法的描述符,調(diào)用Movie.budget.__get__方法并將方法的返回值打印出來,而不是直接傳遞m.budget來打印。這和你訪問一個 property相似,Python自動調(diào)用一個方法,同時返回結果。
__get__接收2個參數(shù):一個是點號左邊的實例對象(在這里,就是m.budget中的m),另一個是這個實例的類型(Movie)。在一些Python文檔中,Movie 被稱作描述符的所有者(owner)。如果我們需要訪問Movie.budget,Python將會調(diào)用 Movie.budget.__get__(None, Movie)。可以看到,第一個參數(shù)要么是所有者的實例,要么是None。這些輸入?yún)?shù)可能看起來很怪,但是這里它們告訴了你描述符屬于哪個對象的一部 分。當我們看到NonNegative類的實現(xiàn)時這一切就合情合理了。
對描述符賦值
當解釋器看到m.rating = 100時,Python識別出rating是一個帶有__set__方法的描述符,于是就調(diào)用Movie.rating.__set__(m, 100)。和__get__一樣,__set__的第一個參數(shù)是點號左邊的類實例(m.rating = 100中的m)。第二個參數(shù)是所賦的值(100)。
刪除描述符
為了說明的完整,這里提一下刪除。如果你調(diào)用del m.budget,Python就會調(diào)用Movie.budget.__delete__(m)。
NonNegative類是如何工作的?
帶著前面的困惑,我們終于要揭示NonNegative類是如何工作的了。每個NonNegative的實例都維護著一個字典,其中保存著所有者實 例和對應數(shù)據(jù)的映射關系。當我們調(diào)用m.budget時,__get__方法會查找與m相關聯(lián)的數(shù)據(jù),并返回這個結果(如果這個值不存在,則會返回一個默 認值)。__set__采用的方式相同,但是這里會包含額外的非負檢查。我們使用WeakKeyDictionary來取代普通的字典以防止內(nèi)存泄露—— 我們可不想僅僅因為它在描述符的字典中就讓一個無用?的實例一直存活著。
使用描述符會有一點別扭。因為它們作用于類的層次上,每一個類實例都共享同一個描述符。這就意味著對不同的實例對象而言,描述符不得不手動地管理?不同的狀態(tài),同時需要顯式的將類實例作為第一個參數(shù)準確傳遞給__get__、__set__以及__delete__方法。
我希望這個例子解釋清楚了描述符可以用來做什么——它們提供了一種方法將property的邏輯隔離到單獨的類中來處理。如果你發(fā)現(xiàn)自己正在不同的property之間重復著相同的邏輯,那么本文也許會成為一個線索供你思考為何用描述符重構代碼是值得一試的。
秘訣和陷阱
把描述符放在類的層次上(class level)
為了讓描述符能夠正常工作,它們必須定義在類的層次上。如果你不這么做,那么Python無法自動為你調(diào)用__get__和__set__方法。
Python
class Broken(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)
X is <__main__.nonnegative object at>, 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__.nonnegative objectat0x10432c250>,Yis5
可以看到,訪問類層次上的描述符y可以自動調(diào)用__get__。但是訪問實例層次上的描述符x只會返回描述符本身,真是魔法一般的存在啊。
確保實例的數(shù)據(jù)只屬于實例本身
你可能會像這樣編寫NonNegative描述符:
Python
class BrokenNonNegative(object):
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 invalid assignment
這么做看起來似乎能正常工作。但這里的問題就在于所有Foo的實例都共享相同的bar,這會產(chǎn)生一些令人痛苦的結果:
Python
class Foo(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.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.bar is5
g.bar is5
Settingf.bar to10
f.bar is10
g.bar is10
這就是為什么我們要在NonNegative中使用數(shù)據(jù)字典的原因。__get__和__set__的第一個參數(shù)告訴我們需要關心哪一個實例。NonNegative使用這個參數(shù)作為字典的key,為每一個Foo實例單獨保存一份數(shù)據(jù)。
Python
class Foo(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.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.bar is5
g.bar is5
Settingf.bar to10
f.bar is10
g.bar is5
這就是描述符最令人感到別扭的地方(坦白的說,我不理解為什么Python不讓你在實例的層次上定義描述符,并且總是需要將實際的處理分發(fā)給__get__和__set__。這么做行不通一定是有原因的)
注意不可哈希的描述符所有者
NonNegative類使用了一個字典來單獨保存專屬于實例的數(shù)據(jù)。這個一般來說是沒問題的,除非你用到了不可哈希(unhashable)的對象:
Python
class MoProblems(list): #you can't use lists as dictionary keys
x = NonNegative(5)
m = MoProblems()
print m.x # womp womp
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
in ()
3
4 m = MoProblems()
----> 5 print m.x # womp womp
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
---------------------------------------------------------------------------
TypeErrorTraceback(most recent call last)
in()
3
4m=MoProblems()
---->5printm.x# womp womp
in__get__(self,instance,owner)
9# instance = x
10# owner = type(x)
--->11returnself.data.get(instance,self.default)
12
13def__set__(self,instance,value):
TypeError:unhashable type:'MoProblems'
因為MoProblems的實例(list的子類)是不可哈希的,因此它們不能為MoProblems.x用做數(shù)據(jù)字典的key。有一些方法可以規(guī)避這個問題,但是都不完美。最好的方法可能就是給你的描述符加標簽了。
Python
class Descriptor(object):
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__ []
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
classFoo(list):
x=Descriptor('x')
y=Descriptor('y')
f=Foo()
f.x=5
printf.x
__set__
__get__[]
5
這種方法依賴于Python的方法解析順序(即,MRO)。我們給Foo中的每個描述符加上一個標簽名,名稱和我們賦值給描述符的變量名相同,比如x = Descriptor(‘x’)。之后,描述符將特定于實例的數(shù)據(jù)保存在f.__dict__['x']中。這個字典條目通常是當我們請求f.x時 Python給出的返回值。然而,由于Foo.x 是一個描述符,Python不能正常的使用f.__dict__[‘x’],但是描述符可以安全的在這里存儲數(shù)據(jù)。只是要記住,不要在別的地方也給這個描 述符添加標簽。
Python
class Foo(object):
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>
5
__get__ <__main__.foo object at>
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
printf.x
f.y=4#oh no!
printf.x
__set__
__get__<__main__.foo objectat0x10432c810>
5
__get__<__main__.foo objectat0x10432c810>
4
我不喜歡這種方式,因為這樣的代碼很脆弱也有很多微妙之處。但這個方法的確很普遍,可以用在不可哈希的所有者類上。David Beazley在他的書中用到了這個方法。
在元類中使用帶標簽的描述符
由于描述符的標簽名和賦給它的變量名相同,所以有人使用元類來自動處理這個簿記(bookkeeping)任務。
Python
class Descriptor(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
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
classDescriptorOwner(type):
def__new__(cls,name,bases,attrs):
# find all descriptors, auto-set their labels
forn,vinattrs.items():
ifisinstance(v,Descriptor):
v.label=n
returnsuper(DescriptorOwner,cls).__new__(cls,name,bases,attrs)
classFoo(object):
__metaclass__=DescriptorOwner
x=Descriptor()
f=Foo()
f.x=10
printf.x
__set__
__get__.Label=x
10
我不會去解釋有關元類的細節(jié)——參考文獻中David Beazley已經(jīng)在他的文章中解釋的很清楚了。?需要指出的是元類自動的為描述符添加標簽,并且和賦給描述符的變量名字相匹配。
盡管這樣解決了描述符的標簽和變量名不一致的問題,但是卻引入了復雜的元類。雖然我很懷疑,但是你可以自行判斷這么做是否值得。
訪問描述符的方法
描述符僅僅是類,也許你想要為它們增加一些方法。舉個例子,描述符是一個用來回調(diào)property的很好的手段。比如我們想要一個類的某個部分的狀態(tài)發(fā)生變化時就立刻通知我們。下面的大部分代碼是用來做這個的:
Python
class CallbackProperty(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):
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 notinself.callbacks:
self.callbacks[instance]=[]
self.callbacks[instance].append(callback)
classBankAccount(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)
這是一個很有吸引力的模式——我們可以自定義回調(diào)函數(shù)用來響應一個類中的狀態(tài)變化,而且完全無需修改這個類的代碼。這樣做可真是替人分憂解難呀。現(xiàn)在,我 們所要做的就是調(diào)用ba.balance.add_callback(ba, low_balance_warning),以使得每次balance變化時low_balance_warning都會被調(diào)用。
但是我們是如何做到的呢?當我們試圖訪問它們時,描述符總是會調(diào)用__get__。就好像add_callback方法是無法觸及的一樣!其實關鍵在于利用了一種特殊的情況,即,當從類的層次訪問時,__get__方法的第一個參數(shù)是None。
Python
class CallbackProperty(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):
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 notinself.callbacks:
self.callbacks[instance]=[]
self.callbacks[instance].append(callback)
classBankAccount(object):
balance=CallbackProperty(0)
deflow_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
Balance is5000
You are now poor
Balance is99
結語
希望你現(xiàn)在對描述符是什么和它們的適用場景有了一個認識。前進吧騷年!
參考文獻
總結
以上是生活随笔為你收集整理的python如何计算分子描述符_Python——描述符(descriptor)解密的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 控制是否展示_非线性控制(四)描述函数法
- 下一篇: python中requests的常用方法