描述器

當一個物件擁有get()方法(必要),以及選擇性的set()、delete()方法時,它可以作為描述器(Descriptor):def get(self, instance, owner)def set(self, instance, value)def delete(self, instance)
在Python中,所謂描述器,是用來描述特性的取得、設定、刪除該如何處理的物件,也就是說,當描述器實際是某個類別的特性成員時,對於類別特性的取得、設定或刪除,將會交由描述器來決定如何處理(除了那些內建特性,如class等特性之外)。例如:class Descriptor: def get(self, instance, owner): print(self, instance, owner) def set(self, instance, value): print(self, instance, value) def delete(self, instance): print(self, instance)class Some: x = Descriptor()

在上例中,如果這麼執行:s = Some()s.xs.x = 10del s.x
其實相當於這麼作:s = Some()Some.dict['x'].get(s, Some);Some.dict['x'].set(s, 10);Some.dict['x'].delete(s);
如果這麼作的話:Some.x
則相當於這麼作:Some.dict['x'].get(None, Some)
特性名稱空間 中談過特性搜尋的順序,依其中描述整理一下的話,特性的尋找順序是:在實例的dict中尋找是否有相符的特性名稱
在產生實例的類別dict中尋找是否有相符的特性名稱
如果實例有定義getattr(),則看getattr()如何處理
如果實例沒有定義getattr(),則丟出AttributeError

如果加上描述器,則尋找的順序是:在產生實例的類別dict中尋找是否有相符的特性名稱。如果找到 且實際是個描述器實例(也就是具有get()方法),且具有set()或delete()方法,若為取值,則傳回get ()方法的值,若為設值,則呼叫set()(沒有這個方法則丟出AttributeError),若為刪除特性,則呼叫delete()(沒有這個方法則丟出AttributeError),如果描述器僅具有get(),則先進行第2步
在實例的dict中尋找是否有相符的特性名稱
在產生實例的類別dict中尋找是否有相符的特性名稱。如果不是描述器則直接傳回特性值。如果是個描述器(此時一定是僅具有get()方法),則傳回get()的值
如果實例有定義getattr(),則看getattr()如何處理
如果實例沒有定義getattr(),則丟出AttributeError

以上的流程可以作個簡單的驗證:>>> class Desc:... def get(self, instance, owner):... print('instance', instance, 'owner', owner)... def set(self, instance, value):... print('instance', instance, 'value', value)...>>> class X:... x = Desc()...>>> x = X()>>> x.xinstance <main.X object at 0x01E01C10> owner <class 'main.X'>>>> x.x = 10instance <main.X object at 0x01E01C10> value 10>>> x.dict['x'] = 10>>> x.xinstance <main.X object at 0x01E01C10> owner <class 'main.X'>>>> x.dict['x']10>>> del x.xTraceback (most recent call last): File "<stdin>", line 1, in <module>AttributeError: delete>>>
除了get()方法之外,還具有set()或delete()方法或兩者兼具的描述器,稱之為資料描述器(Data descriptor),其行為與僅有get()方法的非資料描述器(Non-data descriptor)不同。例如以下為非資料描述器的行為:>>> class Desc:... def get(self, instance, owner):... print('instance', instance, 'owner', owner)...>>> class X:... x = Desc()...>>> x = X()>>> x.xinstance <main.X object at 0x01E01FD0> owner <class 'main.X'>>>> x.x = 10>>> x.x10>>> del x.x>>> x.xinstance <main.X object at 0x01E01FD0> owner <class 'main.X'>>>>
簡而言之,資料描述器可以讓你攔截對實例作特性的取得、設定與刪除行為,而非資料描述器可以讓你在攔截透過實例取得類別特性時的行為。回顧 property() 函式 的內容,對於實例作特性的取得、設定與刪除,都會被轉呼叫為所指定的函式,可想而知的,這是一種資料描述器的行為,若要自行實作property()函式的行為,則可以如下:def prop(getter, setter, deleter): class PropDesc: def get(self, instance, owner): return getter(instance) def set(self, instance, value): setter(instance, value) def delete(self, instance): deleter(instance) return PropDesc()

如此,property() 函式 中使用property()函式的例子,就可以改用以上的prop()函式,class Ball: def init(self, radius): if radius <= 0: raise ValueError('必須是正數') self.__radius = radius def getRadius(self): return self.__radius def setRadius(self, radius): self.__radius = radius def delRadius(self): del self.__radius radius = prop(getRadius, setRadius, delRadius)

靜態方法、類別方法 中討論過,類別的實例在操作類別所定義的方法時,方法的第一個參數都會被綁定為實例,透過實例所操作的這些方法稱之為綁定方法(Bound method)。例如:>>> class Some:... def doSome(self):... print('something...', self)...>>> s = Some()>>> s.doSome()something... <main.Some object at 0x01DA1C50>>>> s.doSome<bound method Some.doSome of <main.Some object at 0x01DA1C50>>>>> Some.doSome('arguments')something... arguments>>> Some.doSome<function doSome at 0x01D303D8>>>>
很顯然地,透過實例所操作的方法,與原先定義在Some類別上的函式是不同的。事實上,你可以這麼操作:>>> Some.dict['doSome'].get(s, Some)()something... <main.Some object at 0x01DA1C50>>>> Some.dict['doSome'].get(s, Some)<bound method Some.doSome of <main.Some object at 0x01DA1C50>>>>> Some.dict['doSome'].get(None, Some)()Traceback (most recent call last): File "<stdin>", line 1, in <module>TypeError: doSome() takes exactly 1 positional argument (0 given)>>> Some.dict['doSome'].get(None, Some)('arguments')something... arguments>>> Some.dict['doSome'].get(None, Some)<function doSome at 0x01D303D8>>>>
顯然地,Some類別上的doSome特性所參考的物件,具有get()方法,也就是說doSome特性實際上是個描述器,在Python類別中定義的函式,實際上是個特性名稱參考至一個非資料描述器。假設你有個類別如下:class Some: def doSome(self, arg): print(self, arg)s = Some()s.doSome(10)Some.doSome(10, 20)

可以嘗試自行使用描述器來「模擬」上面的Some類別doSome的行為,以大致可以了解Python中對於綁定方法的原理:class DoSomeDesc: def doSome(self, arg): print(self, arg) def get(self, instance, owner): if instance: return lambda arg: DoSomeDesc.doSome(instance, arg) else: return DoSomeDesc.doSome class Some: doSome = DoSomeDesc() s = Some()s.doSome(10)Some.doSome(10, 20)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容