[英]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 中:
type
和object
每次在 object 上查找屬性時,Python 都會遵循如下過程:
是object中實際數據的直接一部分嗎? 如果是這樣,請使用它並停止。
它直接是對象的class的一部分嗎? 如果是這樣,請堅持執行第 4 步。
否則,檢查對象的 class 是否有__getattr__
和__getattribute__
覆蓋,查看 MRO 中的基類等。(當然,這是一個巨大的簡化。)
如果在第 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__`.
Test()
) 時(概念上)會發生什么? Test()
使用函數調用語法,但它不是 function。為了弄清楚應該發生什么,我們將調用轉換為Test.__class__.__call__(Test)
。 (我們在這里直接使用__class__
,因為使用type
翻譯 function 調用 - 要求type
對自身進行分類 - 最終會陷入無休止的遞歸。)
Test.__class__
是type
,所以這變成了type.__call__(Test)
。
type
直接包含一個__call__
( type
是它自己的 class,還記得嗎?),所以它是直接使用的——我們不通過__get__
描述符調用 go。 我們調用 function, Test
為self
,沒有其他的 arguments。(我們現在有一個 function,所以我們不需要再次翻譯 function 調用語法。我們可以- 給定一個 function func
, func.__class__.__call__.__get__(func)
給我們一個未命名的內置“方法包裝器”類型的實例,它在調用時做與func
相同的事情。在方法包裝器上重復循環創建一個單獨的方法包裝器,它仍然做同樣的事情。)
這將嘗試調用Test.__new__(Test)
(因為self
已綁定到Test
)。 Test.__new__
未在Test
中明確定義,但由於Test
是 class,因此我們不查看Test
的 class( type
),而是查看Test
的基數( object
)。
object.__new__(Test)
存在,並且做了神奇的內置東西來為 class 的新實例分配 memory,使得可以為該實例分配屬性(即使Test
是Test
的子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.