這篇文章主要介紹了解密Python中的描述符(descriptor),本文詳細講解了描述符(descriptor)的作用、訪問描述符、對描述符賦值、刪除描述符等內容,需要的朋友可以參考下
Python中包含了許多內建的語言特性,它們使得代碼簡潔且易於理解。這些特性包括列表/集合/字典推導式,屬性(property)、以及裝飾器(decorator)。對於大部分特性來說,這些“中級”的語言特性有著完善的文檔,並且易於學習。
但是這裡有個例外,那就是描述符。至少對於我來說,描述符是Python語言核心中困擾我時間最長的一個特性。這裡有幾點原因如下:
1.有關描述符的官方文檔相當難懂,而且沒有包含優秀的示例告訴你為什麼需要編寫描述符(我得為Raymond Hettinger辯護一下,他寫的其他主題的Python文章和視頻對我的幫助還是非常大的)
2.編寫描述符的語法顯得有些怪異
3.自定義描述符可能是Python中用的最少的特性,因此你很難在開源項目中找到優秀的示例
但是一旦你理解了之後,描述符的確還是有它的應用價值的。這篇文章告訴你描述符可以用來做什麼,以及為什麼應該引起你的注意。
一句話概括:描述符就是可重用的屬性
在這裡我要告訴你:從根本上講,描述符就是可以重復使用的屬性。也就是說,描述符可以讓你編寫這樣的代碼:
代碼如下:
f = Foo()
b = f.bar
f.bar = c
del f.bar
而在解釋器執行上述代碼時,當發現你試圖訪問屬性(b = f.bar)、對屬性賦值(f.bar = c)或者刪除一個實例變量的屬性(del f.bar)時,就會去調用自定義的方法。
讓我們先來解釋一下為什麼把對函數的調用偽裝成對屬性的訪問是大有好處的。
property——把函數調用偽裝成對屬性的訪問
想象一下你正在編寫管理電影信息的代碼。你最後寫好的Movie類可能看上去是這樣的:
代碼如下:
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
你開始在項目的其他地方使用這個類,但是之後你意識到:如果不小心給電影打了負分怎麼辦?你覺得這是錯誤的行為,希望Movie類可以阻止這個錯誤。 你首先想到的辦法是將Movie類修改為這樣:
代碼如下:
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
但這行不通。因為其他部分的代碼都是直接通過Movie.budget來賦值的——這個新修改的類只會在init方法中捕獲錯誤的數據,但對於已經存在的類實例就無能為力了。如果有人試著運行m.budget = -100,那麼誰也沒法阻止。作為一個Python程序員同時也是電影迷,你該怎麼辦?
幸運的是,Python的property解決了這個問題。如果你從未見過property的用法,下面是一個示例:
代碼如下:
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
我們用@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方法,然後搜索整個工程中的源代碼,將m.budget = value這樣的代碼替換為m.set_budget(value)。太蛋疼了!!
因此,property讓我們將自定義的代碼同變量的訪問/設定聯系在了一起,同時為你的類保持一個簡單的訪問屬性的接口。干得漂亮!
property的不足
對property來說,最大的缺點就是它們不能重復使用。舉個例子,假設你想為rating,runtime和gross這些字段也添加非負檢查。下面是修改過的新類:
代碼如下:
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
可以看到代碼增加了不少,但重復的邏輯也出現了不少。雖然property可以讓類從外部看起來接口整潔漂亮,但是卻做不到內部同樣整潔漂亮。
描述符登場(最終的大殺器)
這就是描述符所解決的問題。描述符是property的升級版,允許你為重復的property邏輯編寫單獨的類來處理。下面的示例展示了描述符是如何工作的(現在還不必擔心NonNegative類的實現):
代碼如下:
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 = va