摘要:如今查找結(jié)果有誤,說明繼承鏈?zhǔn)清e(cuò)誤的,因而極有可能是出錯(cuò)。真相一切都源于裝飾器語法糖。核心思路就是不要更改被裝飾名稱的引用。
簡化版問題本文首發(fā)于我的博客,轉(zhuǎn)載請注明出處
《神坑》系列將會不定期更新一些可遇而不可求的坑
防止他人入坑,也防止自己再次入坑
現(xiàn)有兩個(gè) View 類:
class View(object): def method(self): # Do something... pass class ChildView(View): def method(self): # Do something else ... super(ChildView, self).method()
以及一個(gè)用于修飾該類的裝飾器函數(shù) register——用于裝飾類的裝飾器很常見(如 django.contrib.admin 的 register),通常可極大地減少定義相似類時(shí)的工作量:
class Mixin(object): pass def register(cls): return type( "DecoratedView", (Mixin, cls), {} )
這個(gè)裝飾器為被裝飾類附加上一個(gè)額外的父類 Mixin,以增添自定義的功能。
完整的代碼如下:
class Mixin(object): pass def register(cls): return type( "DecoratedView", (Mixin, cls), {} ) class View(object): def method(self): # Do something... pass @register class ChildView(View): def method(self): # Do something else ... super(ChildView, self).method()
看上去似乎沒什么問題。然而一旦調(diào)用 ChildView().method(),卻會報(bào)出詭異的 無限遞歸 錯(cuò)誤:
# ... File "test.py", line 23, in method super(ChildView, self).method() File "test.py", line 23, in method super(ChildView, self).method() File "test.py", line 23, in method super(ChildView, self).method() RuntimeError: maximum recursion depth exceeded while calling a Python object
【一臉懵逼】
猜想 & 驗(yàn)證從 Traceback 中可以發(fā)現(xiàn):是 super(ChildView, self).method() 在不停地調(diào)用自己——這著實(shí)讓我吃了一驚,因?yàn)?按理說 super 應(yīng)該沿著繼承鏈查找父類,可為什么在這里 super 神秘地失效了呢?
為了驗(yàn)證 super(...).method 的指向,可以嘗試將該語句改為 print(super(ChildView, self).method),并觀察結(jié)果:
>
輸出表明: method 的指向確實(shí)有誤,此處本應(yīng)為 View.method。
super 是 python 內(nèi)置方法,肯定不會出錯(cuò)。那,會不會是 super 的參數(shù)有誤呢?
super 的簽名為 super(cls, instance),宏觀效果為 遍歷 cls 的繼承鏈查找父類方法,并以 instance 作為 self 進(jìn)行調(diào)用。如今查找結(jié)果有誤,說明 繼承鏈?zhǔn)清e(cuò)誤的,因而極有可能是 cls 出錯(cuò)。
因此,有必要探測一下 ChildView 的指向。在 method 中加上一句: print(ChildView):
原來,作用域中的 ChildView 已經(jīng)被改變了。
真相一切都源于裝飾器語法糖。我們回憶一下裝飾器的等價(jià)語法:
@decorator class Class: pass
等價(jià)于
class Class: pass Class = decorator(Class)
這說明:裝飾器會更改該作用域內(nèi)被裝飾名稱的指向。
這本來沒什么,但和 super 一起使用時(shí)卻會出問題。通常情況下我們會將本類的名稱傳給 super(在這里為 ChildView),而本類名稱和裝飾器語法存在于同一作用域中,從而在裝飾時(shí)被一同修改了(在本例中指向了子類 DecoratedView),進(jìn)而使 super(...).method 指向了 DecoratedView 的最近祖先也就是 ChildView 自身的 method 方法,導(dǎo)致遞歸調(diào)用。
解決方案找到了病因,就不難想到解決方法了。核心思路就是:不要更改被裝飾名稱的引用。
如果你只是想在內(nèi)部使用裝飾后的新類,可以在裝飾器方法中使用 DecoratedView,而在裝飾器返回時(shí) return cls,以保持引用不變:
def register(cls): decorated = type( "DecoratedView", (Mixin, cls), {} ) # Do something with decorated return cls
這種方法的缺點(diǎn)是:從外部無法使用 ChildView.another_method 調(diào)用 Mixin 上的方法??扇绻娴挠羞@樣的需求,可以采用另一個(gè)解決方案:
def register(cls): cls.another_method = Mixin.another_method return cls
即通過賦值的方式為 cls 添加 Mixin 上的新方法,缺點(diǎn)是較為繁瑣。
兩種方法各有利弊,要根據(jù)實(shí)際場景權(quán)衡使用。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/38085.html
摘要:一利用動態(tài)屬性處理數(shù)據(jù)源屬性在中,數(shù)據(jù)的屬性和處理數(shù)據(jù)的方法統(tǒng)稱屬性。處理無效屬性名在中,由于關(guān)鍵字被保留,名稱為關(guān)鍵字的屬性是無效的。內(nèi)置函數(shù)列出對象的大多數(shù)屬性。點(diǎn)號和內(nèi)置函數(shù)會觸發(fā)這個(gè)方法。 導(dǎo)語:本文章記錄了本人在學(xué)習(xí)Python基礎(chǔ)之元編程篇的重點(diǎn)知識及個(gè)人心得,打算入門Python的朋友們可以來一起學(xué)習(xí)并交流。 本文重點(diǎn): 1、了解如何利用動態(tài)屬性處理數(shù)據(jù);2、掌握Pyth...
摘要:不像其他屬性,描述符在類級別上創(chuàng)建。當(dāng)所有者類被定義時(shí),每個(gè)描述符對象都是被綁定到一個(gè)不同的類級別屬性的描述符類實(shí)例。這必須返回描述符的值。此外,描述符對有一個(gè)方便的響應(yīng)和請求格式。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python __getattribute__()方法 __getattribute__()方法是...
摘要:變量查找規(guī)則在中一個(gè)變量的查找順序是局部環(huán)境,閉包,全局,內(nèi)建閉包引用了自由變量的函數(shù)。閉包的作用閉包的最大特點(diǎn)是可以將父函數(shù)的變量與內(nèi)部函數(shù)綁定,并返回綁定變量后的函數(shù),此時(shí)即便生成閉包的環(huán)境父函數(shù)已經(jīng)釋放,閉包仍然存在。 導(dǎo)語:本文章記錄了本人在學(xué)習(xí)Python基礎(chǔ)之函數(shù)篇的重點(diǎn)知識及個(gè)人心得,打算入門Python的朋友們可以來一起學(xué)習(xí)并交流。 本文重點(diǎn): 1、掌握裝飾器的本質(zhì)、功...
摘要:裝飾器的使用符合了面向?qū)ο缶幊痰拈_放封閉原則。三簡單的裝飾器基于上面的函數(shù)執(zhí)行時(shí)間的需求,我們就手寫一個(gè)簡單的裝飾器進(jìn)行實(shí)現(xiàn)。函數(shù)體就是要實(shí)現(xiàn)裝飾器的內(nèi)容。類裝飾器的實(shí)現(xiàn)是調(diào)用了類里面的函數(shù)。類裝飾器的寫法比我們裝飾器函數(shù)的寫法更加簡單。 目錄 前言 一、什么是裝飾器 二、為什么要用裝飾器 ...
摘要:尾遞歸優(yōu)化一般遞歸與尾遞歸一般遞歸執(zhí)行可以看到一般遞歸每一級遞歸都產(chǎn)生了新的局部變量必須創(chuàng)建新的調(diào)用棧隨著遞歸深度的增加創(chuàng)建的棧越來越多造成爆棧尾遞歸尾遞歸基于函數(shù)的尾調(diào)用每一級調(diào)用直接返回遞歸函數(shù)更新調(diào)用棧沒有新局部變量的產(chǎn)生類似迭代的 Python尾遞歸優(yōu)化 一般遞歸與尾遞歸 一般遞歸: def normal_recursion(n): if n == 1: ...
閱讀 3436·2023-04-25 22:44
閱讀 949·2021-11-15 11:37
閱讀 1644·2019-08-30 15:55
閱讀 2658·2019-08-30 15:54
閱讀 1096·2019-08-30 13:45
閱讀 1444·2019-08-29 17:14
閱讀 1866·2019-08-29 13:50
閱讀 3424·2019-08-26 11:39