使用者:Xyy23330121/Python/類的方法與類實例
我們之前已經提到過 __init__ 方法,這裡將詳細講解創建實例的過程。
def __new__(cls, value="0", context=None):
self = object.__new__(cls)
...
if isinstance(value, str):
...
self._sign = 1
...
return self
可以看出,自定義的 __new__ 方法,它滿足以下特徵:
- 第一步就是用 object(所有類的父類)定義的 __new__ 方法,把結果賦值給變量 self。
- 之後是對 self 創建各種屬性,由於使用的變量名稱是 self,所以語法和 __init__(self) 中完全一致。
- 最後 return self
本方法是創建實例的第一步。它會在使用時創建一個新實例。
本方法默認為靜態方法、類方法(而無需用裝飾器),它會將所屬的類自動傳入第一個參數。
在自定義該方法時,一般會使用 super().__new__ 訪問父類定義的 __new__ 方法,並修改父類 __new__ 方法返回的結果。
此函數[2]會返回一個特殊對象。任何對該對象的方法都會被重定向到 type 的父類中定義的方法。比如以下示例:
class C:
def method(self): print("C.method()")
class D(C):
def method(self): print("D.method()")
def usemethod(self):
self.method()
super().method()
obj = D()
obj.usemethod()
輸出為:
D.method()
C.method()
從而,在子類中也可以調用已經被子類作用域所覆蓋的、父類的方法。
該方法查找父類方法的順序,和子類繼承父類方法的順序是一致的(但跳過了調用super()的子類本身)。
如果 __new__ 方法在構造對象期間被發起調用,並返回了一個 cls 的實例。則新實例的 __init__ 方法將會被調用。調用時,會傳入所有傳入 __new__ 方法的參數。比如以下示例:
class test:
def __new__(cls, value):
print(value)
return object.__new__(test)
def __init__(self, value):
print(value)
a = test("传入的参数")
該實例輸出兩遍 传入的参数,可見傳入 __new__ 的 value 參數也被傳入了 __init__ 的 value 參數。
__init__ 方法直接修改 __new__ 方法返回的實例,只要它返回除了None以外的任何值,就會導致錯誤。
獲取實例屬性時,調用的是其所屬類的 __getattribute__() 方法和 __getattr__() 方法。
具體來講,下面兩者(在最終沒報錯的情況下)是等價的:
a = ins.att
try:
a = type(ins).__getattribute__(ins, 'att')
except AttributeError:
a = type(ins).__getattr__(ins, 'att')
我們希望確認過程中調用的到底是ins.__getattr__('att')還是type(ins).__getattr__(ins, 'att'),因此做以下實驗:
class test:
def __getattr__(self, name):
print('__getattr__', end=' ')
return 0
def __getattribute__(self, name):
print('__getattribute__', end=' ')
return super().__getattribute__(name)
ins = test()
print('以下方法被依次调用:', end='')
ins.att
print() #输出:以下方法被依次调用:__getattribute__ __getattr__
print('以下方法被依次调用:', end='')
try:
ins.__getattribute__('att')
except AttributeError:
ins.__getattr__('att')
print() #输出:以下方法被依次调用:__getattribute__ __getattribute__ __getattribute__ __getattr__
print('以下方法被依次调用:', end='')
try:
type(ins).__getattribute__(ins, 'att')
except AttributeError:
type(ins).__getattr__(ins, 'att')
print() #输出:以下方法被依次调用:__getattribute__ __getattr__
不難發現,第一組和第三組的行為是一致的。而第二組的邏輯則類似於:
try:
_func = type(ins).__getattribute__(ins, '__getattribute__')
_func('att')
except AttributeError:
_func = type(ins).__getattribute__(ins, '__getattr__')
_func('att')
此方法默認會自動返回實例的內部屬性,或者報錯 AttributeError。
此方法默認會直接報錯 AttributeError。
在自定義上述兩個方法時,一定要注意避免無限遞歸。
這裡給出幾個避免出錯的要點:
1. 儘可能不要修改 __getattribute__ 對已有屬性的行為
如果對這些行為進行修改,則該對象的一切屬性你都將讀不到。
class test:
def __getattribute__(self, name):
return 0
a = test()
print(a.__dict__) #输出:0
最好使用形如下面的方法:
class test:
def __getattribute__(self, name):
try:
# 这样就不会改变对已有属性的行为
return super().__getattribute__(name)
except AttributeError:
return 0
特別的。如果像上面一樣、想要實現的功能只靠自定義__getattr__也能實現,則建議讀者不要自定義 __getattribute__:
class test:
def __getattr__(self, name):
return 0
2. 不要在 __getattribute__ 中用常規方法獲取任何屬性內容
如果用常規方法獲取屬性內容,則會造成死循環。
class test:
default = 0
def __getattribute__(self, name):
try:
# 这样就不会改变对已有属性的行为
return super().__getattribute__(name)
except AttributeError:
return self.default
a = test()
print(a.not_defined) #报错:无限循环
建議改用:
class test:
default = 0
def __getattribute__(self, name):
try:
# 这样就不会改变对已有属性的行为
return super().__getattribute__(name)
except AttributeError:
return super().__getattribute__('default')
3. 儘可能不要在 __getattr__ 中用常規方法獲取任何不能通過 __getattribute__ 獲取的屬性內容
原因和上面類似。但總體來講,自定義 __getattr__ 總是更安全的。比如下面的代碼不會報錯:
class test:
default = 0
def __getattr__(self, name):
if name == 'a': return self.b # 最终会调用 __getattr__(self, 'b')
if name == 'b': return self.default # 在 __getattribute__ 时就输出了结果,所以不会报错
raise AttributeError
與 獲取實例屬性 類似。在設置實例屬性時,同樣也調用了方法。
具體來講,下面兩者(在最終沒報錯的情況下)是等價的:
ins.att = a
del ins.att
type(ins).__setattr__(ins, 'att', a)
type(ins).__delattr__(ins, 'att')
在默認情況下,這兩個方法都應該返回 None。
該方法用於支持 dir(self) 的操作。它會返回實例中、屬性與方法名稱的字符串組成的列表。
我們之前學習「私有量」的時候提到過 property。property 是一種描述器。
只要設置了下面方法中的一個,該類型就被 Python 視作是一種描述器。
其中,instance 是描述器屬性所在的實例,而 owner 是 instance 所屬的類型。
其中,instance 是描述器屬性所在的實例,而 value 是要賦的值。
其中,instance 是描述器屬性所在的實例。
描述器如果不作為類的屬性、而作為一般的變量使用時,不會有 __getattribute__ 等方法來調用描述器方法。
我們看以下示例:
class ReadOnly:
def __init__(self, value):
self.__value = value
def __get__(self, instance, owner=None):
print(instance.name, "and", owner.clsname)
return self.__value
def __set__(self, instance, value):
raise AttributeError("readonly attribute")
def __delete__(self, instance):
raise AttributeError("readonly attribute")
class C:
clsname = "Class C"
def __init__(self, name):
self.name = name
x = ReadOnly(0)
c = C("Instance c")
output = c.x #输出:Instance c and Class C
print(output) #输出:0
c.x = 2 #报错:AttributeError: readonly attribute
在創建描述器時,還可以添加一個可選的屬性。該屬性應當設置為描述器屬性所在的實例類型。該屬性會被 inspect 模塊所使用,有助於動態類屬性的運行時內省(?)。
為了代碼的易於擴展,有時需要自定義子類創建的行為。
在創建子類時會調用父類的此方法,其中, cls 參數會被自動傳入新的子類。
這裡的參數是在創建類的時候,通過「關鍵詞參數」方法傳入的,比如:
class test:
def __init_subclass__(cls, arg=0):
print(arg)
pass
class test1(test, arg="传入的参数"):
pass
以上示例的輸出是传入的参数。如果使用位置參數,則會與「父類的繼承」產生混淆。
此方法是子類被創建完畢後,對子類作修改。而非從頭創建子類。此方法返回任何值都會u報錯。
random.Random[3]類就有此方法,其代碼節選如下:
def __init_subclass__(cls, /, **kwargs):
for c in cls.__mro__:
if '_randbelow' in c.__dict__:
# just inherit it
break
if 'getrandbits' in c.__dict__:
cls._randbelow = cls._randbelow_with_getrandbits
break
if 'random' in c.__dict__:
cls._randbelow = cls._randbelow_without_getrandbits
break
random.Random 類對於隨機數有不同的處理。有的方法使用隨機整數以規避浮點誤差,而有的方法使用隨機浮點數。其中,使用隨機整數的方法調用的是 _randbelow 方法。
此處 __init_subclass__ 的處理,在子類定義了 getrandbits (隨機整數方法)的情況下,讓 _randbelow 變為 _randbelow_with_getrandbits 方法,再在 _randbelow_with_getrandbits 中調用 getrandbits 方法。從而實現了隨機整數方法的調用。
如果子類沒有定義 getrandbits,則查詢是否定義了 random (隨機浮點數方法)。如果有,讓 _randbelow 變為 _randbelow_without_getrandbits 方法,再在 _randbelow_without_getrandbits 方法中調用 random 方法,並把結果轉化為整數。
如果子類兩者都沒有定義,則認為子類沒有修改隨機數生成器,此時按繼承順序繼續查找父類的方法。
通過這樣的處理,子類可以只定義 random 方法就修改 random.Random 中所有類型隨機數的生成,也可以附加定義 getrandbits 來為整數作進一步優化。增加了代碼的可擴展性。
在創建類的同時創建屬性時會調用此方法。我們看以下示例:
class AttrTest:
def __set_name__(self, owner, name):
print(self, owner, name)
def __str__(self):
return "AttrTest 的实例"
__repr__ = __str__
attr = AttrTest()
class test:
x = attr #在创建类的同时创建属性
test.y = attr #在创建类之后,再创建属性
attr.__set_name__(test,"z") #手动调用该方法
print(test.x, test.y)
print(test.z)
輸出為:
AttrTest 的实例 <class '__main__.test'> x
AttrTest 的实例 <class '__main__.test'> z
AttrTest 的实例 AttrTest 的实例
Traceback (most recent call last):
File "...", ..., in <module>
print(test.z)
^^^^^^
AttributeError: type object 'test' has no attribute 'z'