成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

神坑·Python 裝飾類無限遞歸

spacewander / 1447人閱讀

摘要:如今查找結(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.adminregister),通常可極大地減少定義相似類時(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

相關(guān)文章

  • Python中的動態(tài)屬性和特性

    摘要:一利用動態(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...

    scola666 評論0 收藏0
  • [譯] 屬性訪問、特性和描述符 2

    摘要:不像其他屬性,描述符在類級別上創(chuàng)建。當(dāng)所有者類被定義時(shí),每個(gè)描述符對象都是被綁定到一個(gè)不同的類級別屬性的描述符類實(shí)例。這必須返回描述符的值。此外,描述符對有一個(gè)方便的響應(yīng)和請求格式。 注:原書作者 Steven F. Lott,原書名為 Mastering Object-oriented Python __getattribute__()方法 __getattribute__()方法是...

    CloudwiseAPM 評論0 收藏0
  • Python中的函數(shù)裝飾器和閉包

    摘要:變量查找規(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ì)、功...

    caozhijian 評論0 收藏0
  • Python】一文弄懂python裝飾器(附源碼例子)

    摘要:裝飾器的使用符合了面向?qū)ο缶幊痰拈_放封閉原則。三簡單的裝飾器基于上面的函數(shù)執(zhí)行時(shí)間的需求,我們就手寫一個(gè)簡單的裝飾器進(jìn)行實(shí)現(xiàn)。函數(shù)體就是要實(shí)現(xiàn)裝飾器的內(nèi)容。類裝飾器的實(shí)現(xiàn)是調(diào)用了類里面的函數(shù)。類裝飾器的寫法比我們裝飾器函數(shù)的寫法更加簡單。 目錄 前言 一、什么是裝飾器 二、為什么要用裝飾器 ...

    liuchengxu 評論0 收藏0
  • Python開啟尾遞歸優(yōu)化!

    摘要:尾遞歸優(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: ...

    junnplus 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<