簡體   English   中英

顯式調用 __call__ 有效並使用 __init__

[英]Explicit call to __call__ works and uses __init__

我正在學習 Python 3.X 中的重載,為了更好地理解該主題,我編寫了以下在 3.X 中有效但在 2.X 中無效的代碼。 我預計下面的代碼會失敗,因為我沒有為 class Test定義__call__ 但令我驚訝的是,它有效並打印出"constructor called" 演示

class Test: 
    def __init__(self):
        print("constructor called")
#Test.__getitem__()  #error as expected

Test.__call__() #this works in 3.X(but not in 2.X) and prints "constructor called"! WHY THIS DOESN'T GIVE ERROR in 3.x?

所以我的問題是這段代碼如何/為什么在 3.x 中有效,但在 2.x 中無效。 我的意思是我想知道正在發生的事情背后的機制。

更重要的是,為什么在我使用__call__時使用__init__

在 3.x 中:

關於屬性查找, typeobject

每次在 object 上查找屬性時,Python 都會遵循如下過程:

  1. 是object中實際數據的直接一部分嗎? 如果是這樣,請使用它並停止。

  2. 它直接是對象的class的一部分嗎? 如果是這樣,請堅持執行第 4 步。

  3. 否則,檢查對象的 class 是否有__getattr____getattribute__覆蓋,查看 MRO 中的基類等。(當然,這是一個巨大的簡化。)

  4. 如果在第 2 步或第 3 步中發現了某些內容,請檢查它是否具有__get__ 如果是,請查找(是的,這意味着從步驟 1 開始為該對象上名為__get__的屬性),調用它,並使用它的返回值。 否則,使用直接返回的內容。

函數自動有一個__get__ 它用於實現方法綁定。 類是對象; 這就是為什么可以在其中查找屬性的原因。 即: class Test: block 的目的是定義一個數據類型 該代碼創建一個名為 object 的Test ,它表示已定義的數據類型

但由於Test class 是一個 object,它必須是某個 class 的實例。class 稱為type ,並具有內置實現。

>>> type(Test)
<class 'type'>

請注意, type(Test)不是 function 調用。 相反,名稱type被預定義為引用 class,在用戶代碼中創建的每個其他 class 都是(默認情況下)的一個實例。

換句話說, type是默認的元類:類的 class。

>>> type
<class 'type'>

有人會問,class這個type屬於什么? 答案出奇地簡單 -本身

>>> type(type) is type
True

由於上面的例子調用了type ,我們得出結論type是可調用的。 要可調用,它必須具有__call__屬性,並且它可以:

>>> type.__call__
<slot wrapper '__call__' of 'type' objects>

當使用單個參數調用type時,它會查找參數的 class(大致相當於訪問參數的__class__屬性)。 當用三個 arguments 調用時,它會創建一個新的type實例,即一個新的 class。

字體是如何工作type

因為這是在語言的核心挖掘(為對象分配 memory),所以不太可能在純 Python 中實現它,至少對於參考 C 實現(我不知道發生了什么魔法在此處的 PyPy 中)。 但是我們可以像這樣大約model type class:

def _validate_type(obj, required_type, context):
    if not isinstance(obj, required_type):
        good_name = required_type.__name__
        bad_name = type(obj).__name__
        raise TypeError(f'{context} must be {good_name}, not {bad_name}')

class type:
    def __new__(cls, name_or_obj, *args):
        # __new__ implicitly gets passed an instance of the class, but
        # `type` is its own class, so it will be `type` itself.
        if len(args) == 0: # 1-argument form: check the type of an existing class.
            return obj.__class__
        # otherwise, 3-argument form: create a new class.
        try:
            bases, attrs = args
        except ValueError:
            raise TypeError('type() takes 1 or 3 arguments')
        _validate_type(name, str, 'type.__new__() argument 1')
        _validate_type(bases, tuple, 'type.__new__() argument 2')
        _validate_type(attrs, dict, 'type.__new__() argument 3')

        # This line would not work if we were actually implementing
        # a replacement for `type`, as it would route to `object.__new__(type)`,
        # which is explicitly disallowed. But let's pretend it does...
        result = super().__new__()
        # Now, fill in attributes from the parameters.
        result.__name__ = name_or_obj
        # Assigning to `__bases__` triggers a lot of other internal checks!
        result.__bases__ = bases
        for name, value in attrs.items():
            setattr(result, name, value)
        return result
    del __new__.__get__ # `__new__`s of builtins don't implement this.

    def __call__(self, *args):
        return self.__new__(self, *args)
    # this, however, does have a `__get__`.

當我們調用 class ( Test() ) 時(概念上)會發生什么?

  1. Test()使用函數調用語法,但它不是 function。為了弄清楚應該發生什么,我們將調用轉換為Test.__class__.__call__(Test) (我們在這里直接使用__class__ ,因為使用type翻譯 function 調用 - 要求type對自身進行分類 - 最終會陷入無休止的遞歸。)

  2. Test.__class__type ,所以這變成了type.__call__(Test)

  3. type直接包含一個__call__type是它自己的 class,還記得嗎?),所以它是直接使用的——我們不通過__get__描述符調用 go。 我們調用 function, Testself ,沒有其他的 arguments。(我們現在有一個 function,所以我們不需要再次翻譯 function 調用語法。我們可以- 給定一個 function funcfunc.__class__.__call__.__get__(func)給我們一個未命名的內置“方法包裝器”類型的實例,它在調用時做與func相同的事情。在方法包裝器上重復循環創建一個單獨的方法包裝器,它仍然做同樣的事情。)

  4. 這將嘗試調用Test.__new__(Test) (因為self已綁定到Test )。 Test.__new__未在Test中明確定義,但由於Test是 class,因此我們不查看Test的 class( type ),而是查看Test的基數( object )。

  5. object.__new__(Test)存在,並且做了神奇的內置東西來為 class 的新實例分配 memory,使得可以為該實例分配屬性(即使TestTest的子object ,這是不允許的) ,並將其__class__設置為Test

類似地,當我們調用type時,相同的邏輯鏈將type(Test)變成type.__class__.__call__(type, Test)變成type.__call__(type, Test) ,然后轉發到type.__new__(type, Test) 這一次, type中直接有一個__new__屬性,所以這不會回退到查找object 相反,將name_or_obj設置為Test ,我們只需返回Test.__class__ ,即type 並使用單獨的name, bases, attrs屬性 arguments, type.__new__而不是創建type的實例。

最后:當我們顯式調用Test.__call__()時會發生什么?

如果在 class 中定義了一個__call__ ,它就會被使用,因為它是直接找到的。 但是,這將失敗,因為沒有足夠的 arguments:描述符協議未被使用,因為屬性是直接找到的,因此self未綁定,因此缺少該參數。

如果沒有定義__call__方法,那么我們查看Test的 class,即type 那里有一個__call__ ,所以 rest 像上一節中的步驟 3-5 一樣繼續。

在 Python 3.x 中,每個 class 隱含地是內置 class object的孩子。 至少在 CPython 實現中, object class 有一個在其元類type中定義的__call__方法。

這意味着Test.__call__()Test()完全相同,並將返回一個新的Test object,調用您的自定義__init__方法。

在 Python 中,2.x 類默認是舊式類,不是object的子類。 因為__call__沒有定義。 您可以通過使用新樣式類在 Python 2.x 中獲得相同的行為,這意味着通過在 object 上顯式顯示 inheritance:

# Python 2 new style class
class Test(object):
    ...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM