![](/img/trans.png)
[英]python - Accessing superclass's class attribute in super().__init__()
[英]Reason for calling super class constructor using superclass.__init()__ instead of superclass()
我是Python的初學者,並且使用Lutz的書來理解Python中的OOPS。 這個問題可能是基本的,但我將不勝感激。 我研究了SO,找到了“如何”的答案,但沒有找到“為什么”的答案。
當我從書上知道,如果Sub
繼承了Super
然后一個不需要調用父類的( Super
的) __init__()
方法。
例:
class Super:
def __init__(self,name):
self.name=name
print("Name is:",name)
class Sub(Super):
pass
a = Sub("Harry")
a.name
上面的代碼確實將屬性name
分配給對象a
。 它還會按預期打印name
。
但是,如果我將代碼修改為:
class Super:
def __init__(self,name):
print("Inside Super __init__")
self.name=name
print("Name is:",name)
class Sub(Super):
def __init__(self,name):
Super(name) #Call __init__ directly
a = Sub("Harry")
a.name
上面的代碼無法正常工作。 通過罰款,我的意思是,雖然Super.__init__()
不會被調用(從打印語句看到的),沒有連接到屬性a
。 當我運行a.name
,出現錯誤, AttributeError: 'Sub' object has no attribute 'name'
我在SO上對此主題進行了研究,並找到了有關在python中調用鏈調用父構造函數的修復程序,以及為什么超類__init__方法不會自動調用嗎?
這兩個線程討論如何修復它,但是沒有提供原因。
問題: 為什么我需要使用Super.__init__(self, name)
或super(Sub, self).__init__(name)
而不是直接調用Super(name)
來調用Super
的__init__
?
在Super.__init__(self, name)
和Super(name)
,我們看到調用了Super的__init__()
(從打印語句中看到),但是僅在Super.__init__(self, name)
我們看到了屬性附屬於Sub
類。
Super(name)
不會自動將self
(子)對象傳遞給Super
嗎? 現在,您可能會問我如何知道self
被自動傳遞? 如果將Super(name)
修改為Super(self,name)
,則會收到一條錯誤消息,指出TypeError: __init__() takes 2 positional arguments but 3 were given
。 據我了解, self
是自動傳遞的。 因此,有效地,我們最終兩次通過self
。
我不知道為什么即使Super.__init__()
運行, Super(name)
也不將name
屬性附加到Sub
。 我將不勝感激。
作為參考,以下是根據我對SO的研究得出的代碼的工作版本:
class Super:
def __init__(self,name):
print("Inside __init__")
self.name=name
print("Name is:",name)
class Sub(Super):
def __init__(self,name):
#Super.__init__(self, name) #One way to fix this
super(Sub, self).__init__(name) #Another way to fix this
a = Sub("Harry")
a.name
PS:我在Anaconda發行版下使用Python-3.6.5
。
當我從書上知道,如果子繼承了超級然后一個不需要調用父類的(
Super
的)__init__()
方法。
這是誤導。 的確,您不需要調用超類的__init__
方法,但是,如果不這樣做,則__init__
任何操作都不會發生。 對於普通班級,所有這些都需要完成。 有時很有用,通常是在未將類設計為繼承自此類的情況下,如下所示:
class Rot13Reader:
def __init__(self, filename):
self.file = open(filename):
def close(self):
self.file.close()
def dostuff(self):
line = next(file)
return codecs.encode(line, 'rot13')
想象一下,您需要此類的所有行為,但要使用字符串而不是文件。 唯一的方法是跳過open
:
class LocalRot13Reader(Rot13Reader):
def __init__(self, s):
# don't call super().__init__, because we don't have a filename to open
# instead, set up self.file with something else
self.file = io.StringIO(s)
在這里,我們要避免在超類中分配self.file
。 在你的情況,因為幾乎所有的類你曾經打算寫你不想要避免self.name
在超分配。 這就是為什么即使Python 允許您不調用超類的__init__
,您也幾乎總是調用它的原因。
注意,這里的__init__
沒有什么特別的。 例如,我們可以重寫dostuff
以調用基類的版本,然后執行其他操作:
def dostuff(self):
result = super().dostuff()
return result.upper()
…或我們可以重寫close
並有意不調用基類:
def close(self):
# do nothing, including no super, because we borrowed our file
唯一的區別是,避免調用基類的充分理由在普通方法中比在__init__
更常見。
問題:為什么我需要使用
Super.__init__(self, name)
或super(Sub, self).__init__(name)
而不是直接調用Super(name)
來調用Super's __init__
?
因為這些功能有很大不同。
Super(name)
構造一個新的Super
實例,在其上調用__init__(name)
,並將其返回給您。 然后,您將忽略該值。
特別是, Super.__init__
確實會被調用一次,但是調用它的self
是新的Super
實例,在Super(name)
情況下,您只是將其丟棄,而它是您自己的self
在super(Sub, self).__init__(name)
情況下。
因此,在第一種情況下,它將在其他一些被丟棄的對象上設置name
屬性,而沒有人在您的對象上設置它,這就是為什么self.name
稍后引發AttributeError
。
如果您在兩個類的__init__
方法中都添加了一些內容以顯示所涉及的實例,則可能有助於您理解這一點:
class Super:
def __init__(self,name):
print(f"Inside Super __init__ for {self}")
self.name=name
print("Name is:",name)
class Sub(Super):
def __init__(self,name):
print(f"Inside Sub __init__ for {self}")
# line you want to experiment with goes here.
如果最后一行是super().__init__(name)
, super(Sub, self).__init__name)
或Super.__init__(self, name)
,您將看到以下內容:
Inside Sub __init__ for <__main__.Sub object at 0x10f7a9e80>
Inside Super __init__ for <__main__.Sub object at 0x10f7a9e80>
請注意,在兩種情況下,它都是同一個對象,即地址為0x10f7a9e80的Sub
。
…但是如果最后一行是Super(name)
:
Inside Sub __init__ for <__main__.Sub object at 0x10f7a9ea0>
Inside Super __init__ for <__main__.Super object at 0x10f7a9ec0>
現在,我們有兩個不同的對象,它們的地址分別為0x10f7a9ea0和0x10f7a9ec0,並且類型不同。
如果您對所有魔術的外觀都感到好奇,那么Super(name)
執行以下操作(簡化一些,並跳過一些步驟1 ):
_newobj = Super.__new__(Super)
if isinstance(_newobj, Super):
Super.__init__(_newobj, name)
…而super(Sub, self).__init__(name)
則是這樣的:
_basecls = magically_find_next_class_in_mro(Sub)
_basecls.__init__(self, name)
附帶說明一下,如果一本書告訴您使用super(Sub, self).__init__(name)
或Super.__init__(self, name)
,則可能是為Python 2寫的過時的書。
在Python 3中,您只需執行以下操作:
super().__init__(name)
:按方法解析順序調用正確的下一個超類。 您幾乎總是想要這個。 super(Sub, self).__init__(name)
:調用正確的下一個超類-除非您犯了一個錯誤並在那里使Sub
錯誤。 僅當您要編寫必須在2.7和3.x中運行的雙版本代碼時,才需要此代碼。 Super.__init__(self, name)
:調用Super
,無論它是否是正確的下一個超類。 僅當方法解析順序錯誤並且需要解決時,才需要此方法。 2 如果您想了解更多,這些都在文檔中,但這可能有點令人生畏:
super
, __new__
以及所有相關功能的原始介紹對我理解所有這些非常有幫助。 我不確定這是否會對那些已經不了解老式Python類的人有幫助,但是它寫得不錯,Guido(顯然)知道他在說什么,所以值得一讀。
1.這種解釋中最大的_baseclass
是, super
實際上返回了一個代理對象,該代理對象的行為類似於_baseclass
綁定到self
,其方式與方法綁定的方式相同,可用於綁定諸如__init__
方法。 如果您知道方法的工作原理,那么這是有用的/有趣的知識,但如果您不知道方法的話,可能只是額外的困惑。
2.…或如果您正在使用不支持super
(或正確的方法解析順序)的舊式類。 沒有老式類的Python 3永遠不會出現這種情況。 但是,不幸的是,您會在許多tkinter示例中看到它,因為最好的教程仍然是Effbot,它是為Python 2.3編寫的,當時Tkinter都是老式類,並且從未進行過更新。
Super(name)
不是對超類__init__
的“直接調用”。 畢竟,您叫Super
,而不是Super.__init__
。
Super.__init__
接受未初始化的Super
實例並對其進行初始化。 Super
創建並初始化了一個與要初始化的實例完全獨立的新實例(然后立即將新實例扔掉了)。 您要初始化的實例未更改。
Super(name)
實例化super的新實例。 想想這個例子:
def __init__(self, name):
x1 = Super(name)
x2 = Super("some other name")
assert x1 is not self
assert x2 is not self
為了在當前實例上顯式調用Super
的構造函數,您必須使用以下語法:
def __init__(self, name):
Super.__init__(self, name)
現在,如果您是初學者,也許您不想進一步閱讀。
如果這樣做,您會發現有充分的理由使用super(Sub, self).__init__(name)
(或Python 3中的super().__init__(name)
)而不是Super.__init__(self, name)
。
只要您確定Super
實際上是您的超類Super.__init__(self, name)
可以正常工作。 但實際上,您永遠都不知道。
您可能具有以下代碼:
class Super:
def __init__(self):
print('Super __init__')
class Sub(Super):
def __init__(self):
print('Sub __init__')
Super.__init__(self)
class Sub2(Super):
def __init__(self):
print('Sub2 __init__')
Super.__init__(self)
class SubSub(Sub, Sub2):
pass
您現在可以期望SubSub()
最終調用上述所有構造函數,但不會:
>>> x = SubSub()
Sub __init__
Super __init__
>>>
要更正它,您必須執行以下操作:
class Super:
def __init__(self):
print('Super __init__')
class Sub(Super):
def __init__(self):
print('Sub __init__')
super().__init__()
class Sub2(Super):
def __init__(self):
print('Sub2 __init__')
super().__init__()
class SubSub(Sub, Sub2):
pass
現在可以正常工作:
>>> x = SubSub()
Sub __init__
Sub2 __init__
Super __init__
>>>
原因是Sub
SubSub
類被聲明為Super
,如果SubSub
類中有多個繼承,Python的MRO將繼承建立為: SubSub
繼承自Sub
, Sub
繼承自Sub2
, Sub2
繼承自Super
, Super
繼承自object
。
您可以測試:
>>> SubSub.__mro__
(<class '__main__.SubSub'>, <class '__main__.Sub'>, <class '__main__.Sub2'>, <class '__main__.Super'>, <class 'object'>)
現在,每個類的構造函數中的super()
調用都會在MRO中找到下一個類,以便可以調用該類的構造函數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.