使用者:Xyy23330121/Python/類的方法與類實例
實例創建[編輯 | 編輯原始碼]
我們之前已經提到過 __init__
方法,這裡將詳細講解創建實例的過程。
object.__new__(cls[, ...])[編輯 | 編輯原始碼]
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__
方法返回的結果。
super(type, object_or_type=None)[編輯 | 編輯原始碼]
此函數[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()
的子類本身)。
object.__init__(self[, ...])[編輯 | 編輯原始碼]
如果 __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
以外的任何值,就會導致錯誤。
實例屬性[編輯 | 編輯原始碼]
獲取實例屬性[編輯 | 編輯原始碼]
object.__getattribute__(self, name)[編輯 | 編輯原始碼]
此方法是調用實例屬性時使用的方法。自定義此方法會影響對已有屬性的讀取。
該方法應該返回找到的屬性值,或引發 AttributeError
。
由於只要使用 self.name
訪問屬性就會調用該方法。自定義該方法時,為了避免無限遞歸,應當注意不要使用 self.name
的方式來訪問實例的屬性,而是使用其基類的方法 object.__getattribute__(self, name)
來訪問實例屬性。
object.__getattr__(self, name)[編輯 | 編輯原始碼]
當 __getattribute__
方法引發 AttributeError
時,會調用此方法再次嘗試獲取屬性。因此,自定義此方法不會影響對已有屬性的讀取。
該方法應該返回找到的屬性值,或引發 AttributeError
。
由於本方法是在 __getattribute__
方法之後調用,只要 __getattribute__
方法能找到對應的屬性,本方法是可以使用 self.name
來讀取屬性的。
設置實例屬性[編輯 | 編輯原始碼]
object.__setattr__(self, name, value)[編輯 | 編輯原始碼]
此方法用於支持 self.name = value
的操作。和獲取實例屬性時不同,設置實例屬性只有這一個函數。
類似 __getattribute__
,在自定義該方法時,應當注意不要使用 self.name = value
的形式。如果有需要,就調用基類的方法 object.__setattr__(self, name, value)
。
object.__delattr__(self, name)[編輯 | 編輯原始碼]
此方法用於支持 del self.name
的操作。
實例屬性與方法列表[編輯 | 編輯原始碼]
object.__dir__(self)[編輯 | 編輯原始碼]
該方法用於支持 dir(self)
的操作。它會返回實例中、屬性與方法名稱的字符串組成的列表。
inspect 模塊[編輯 | 編輯原始碼]
inspect 模塊列出了所有可獲得的內置特殊屬性。詳情請參見 Python 文檔。
描述器[編輯 | 編輯原始碼]
我們之前學習「私有量」的時候提到過 property。property 是一種描述器。以下所示的方法僅當它作為類的屬性時才會起作用。
這是因為獲取、設置或刪除類的屬性時,類的 __getattribute__ 等方法會嘗試調用描述器的方法。描述器如果不作為類的屬性、而作為一般的變量使用時,不會有 __getattribute__ 等方法來調用描述器方法。
只要設置了下面方法中的一個,該類型就是一種描述器。
object.__get__(self, instance, owner=None)[編輯 | 編輯原始碼]
其中,instance 是描述器屬性所在的實例,而 owner 是 instance 所屬的類型。
object.__set__(self, instance, value)[編輯 | 編輯原始碼]
其中,instance 是描述器屬性所在的實例,而 value 是要賦的值。
object.__delete__(self, instance)[編輯 | 編輯原始碼]
其中,instance 是描述器屬性所在的實例。
我們看以下示例:
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
object.__objclass__[編輯 | 編輯原始碼]
在創建描述器時,還可以添加一個可選的屬性。該屬性應當設置為描述器屬性所在的實例類型。該屬性會被 inspect 模塊所使用,有助於動態類屬性的運行時內省。
子類創建[編輯 | 編輯原始碼]
為了代碼的易於擴展,有時需要自定義子類創建的行為。
object.__init_subclass__(cls[, ...])[編輯 | 編輯原始碼]
在創建子類時會調用父類的此方法,其中, 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 來為整數作進一步優化。增加了代碼的可擴展性。
object.__set_name__(self, owner, name)[編輯 | 編輯原始碼]
在創建類的同時創建屬性時會調用此方法。我們看以下示例:
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'