Python - 高級教程 - 數(shù)據(jù)模型(3)
在上一章節(jié)中头岔,我們了解了模塊 module
的導(dǎo)入和使用。
本章將主要說明 類 相關(guān)內(nèi)容劲赠,關(guān)于 類涛目,大家都不陌生,尤其是在 python
中凛澎,萬物皆類
霹肝。
下一章節(jié),將主要講解類的初始化過程和類的多繼承問題塑煎。
python 中的 類
在 python
中沫换,整數(shù)int
的實(shí)現(xiàn)方式如上所示。這里的 1
并不只是字面上的 1
最铁,其實(shí)它也是 <class 'int'>
的實(shí)例讯赏,如果使用 dir(1)
來查看的話,可以更明顯的看出它的屬性冷尉。
>>> dir(1)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
我們知道漱挎,type()
內(nèi)置方法,可以用來查看python
中實(shí)例的類型雀哨。
>>> a = 1
>>> b = type(a)
>>> b
<class 'int'>
>>> c = type(b)
>>> c
<class 'type'>
>>> d = type(c)
>>> d
<class 'type'>
可以很清晰的看到:
整數(shù)類型 a 的類型為 b : <class 'int'>
而 b 的類型 c: <class 'type'>
但是從 c:class 'type'>
開始磕谅,無論我們再如何的去查詢 c 的類型,也只能查找到 type
類雾棺。
>>> object
<class 'object'>
>>> type(object)
<class 'type'>
在python
中膊夹,<class type>
作為所有對象的父類而存在,當(dāng)我們創(chuàng)建類時垢村,所默認(rèn)繼承的object
割疾,也是一個類<class 'object'>
嚎卫,而它也是<class 'type'>
類型嘉栓。
那么我們創(chuàng)建自定義類或者使用系統(tǒng)內(nèi)置類的時候,它又是如何工作的呢拓诸。
類的創(chuàng)建
首先讓我們來看一個很簡單的例子侵佃。在 python
中,創(chuàng)建一個類需要使用關(guān)鍵字 class
奠支。
class Human(object):
name = ""
sex = ""
age = ""
這是我們創(chuàng)建的 Human
類馋辈,它繼承自object
,包含了3個屬性倍谜,name,sex,age
即 姓名/性別/年齡迈螟。
Note: 就算我們不顯示的繼承 object 父類叉抡,python
也會默認(rèn)繼承。
>>> class Human(object):
... name = ""
... sex = ""
... age = ""
...
>>> type(Human)
<type 'type'>
>>> Human
<class '__main__.Human'>
使用自定義類Human1
區(qū)創(chuàng)建一個實(shí)例human1
答毫。
>>> Human1 = type("Human1", (object,), dict(name="", age="", sex=""))
>>> human1 = Human1()
>>> human1
<__main__.Human1 object at 0x10eecb550>
>>> type(human1)
<class '__main__.Human1'>
>>> type(Human1)
<class 'type'>
Human1
與 Human
在使用上來說并無區(qū)別褥民。
類的屬性
要想了解類,我們需要先了解一下類的關(guān)鍵屬性洗搂。
使用 dir()
查看類的屬性消返。
>>> dir(a)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age']
class
類的類型,這里我使用 type(a)==a.__class__
耘拇,結(jié)果為 True
撵颊,說明 type
取得值也為 __class__
。這里可以使用這個類型區(qū)創(chuàng)建與 a
類型相同的對象惫叛。
>>> a.__class__
<class '__main__.Human'>
>>> type(a.__class__)
<class 'type'>
>>> type(a)==a.__class__
True
delattr
實(shí)現(xiàn)了此方法的類倡勇,當(dāng) del()
方法調(diào)用時,實(shí)際上是調(diào)用的此方法挣棕。詳細(xì)看下面的例子译隘。
>>> a.__delattr__
<method-wrapper '__delattr__' of Human object at 0x1032b5240>
>>> a.__delattr__() # 這里說明 需要一個參數(shù)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: expected 1 arguments, got 0
>>> a.age
11
>>> del(a.age) # 使用 del()
>>> a.age
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Human' object has no attribute 'age'
>>> a.age = 11
>>> a.__delattr__("age") # 使用 __delattr__ 與 del() 的效果相同。
>>> a.age
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Human' object has no attribute 'age'
dict
存儲了當(dāng)前類的成員變量洛心。
>>> a.__dict__
{}
>>> a.age = 1
>>> a.__dict__
{'age': 1}
dir
實(shí)現(xiàn)了此方法后固耘,使用內(nèi)置函數(shù)dir()
時,返回的為此方法词身。
>>> b = a.__dir__()
>>> b.sort()
>>> b
['__age__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> c = dir(a)
>>> c.sort()
>>> c
['__age__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>>
doc
該對象的文檔字符串厅目,沒有則為 None
;不會被子類繼承法严。
>>> a.__doc__
>>> type(a.__doc__)
<class 'NoneType'>
eq(self, other); le, lt, ne, gt, ge
以上這些被稱為“富比較”方法损敷。運(yùn)算符號與方法名稱的對應(yīng)關(guān)系如下:
- x<y 調(diào)用 x.lt(y);
- x<=y 調(diào)用 x.le(y)
- x==y 調(diào)用 x.eq(y)
- x!=y 調(diào)用 x.ne(y)
- x>y 調(diào)用 x.gt(y)
- x>=y 調(diào)用 x.ge(y)。
如果指定的參數(shù)對沒有相應(yīng)的實(shí)現(xiàn)深啤,富比較方法可能會返回單例對象 NotImplemented
拗馒。
按照慣例,成功的比較會返回 False
或 True
溯街。不過實(shí)際上這些方法可以返回任意值诱桂,因此如果比較運(yùn)算符是要用于布爾值判斷(例如作為 if 語句的條件),Python
會對返回值調(diào)用 bool()
以確定結(jié)果為真還是假呈昔。
>>> a.__eq__
<method-wrapper '__eq__' of Human object at 0x1032b5240>
>>> a.__eq__(1)
NotImplemented
>>> a.__eq__(a)
True
由于我們沒有顯示的實(shí)現(xiàn) Human
與 int
的比較挥等,所以會有 NotImplemented
的提示。
format(self, format_spec)
通過 format()
內(nèi)置函數(shù)堤尾、擴(kuò)展肝劲、格式化字符串字面值 的求值以及 str.format()
方法調(diào)用以生成一個對象的“格式化”字符串表示。 format_spec
參數(shù)為包含所需格式選項(xiàng)描述的字符串。 format_spec
參數(shù)的解讀是由實(shí)現(xiàn) __format__()
的類型決定的辞槐,不過大多數(shù)類或是將格式化委托給某個內(nèi)置類型掷漱,或是使用相似的格式化選項(xiàng)語法。
getattr(self, name)
獲取名稱為 name
的屬性的值榄檬。當(dāng)使用 getattr(object, name)
獲取 object
的 name
的屬性的值時切威,將會在調(diào)用 __getattribute__
發(fā)生 AttributeError
時才會調(diào)用此方法。
>>> getattr(a, 'age')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Human' object has no attribute 'age'
>>> a.age = 11
>>> getattr(a, 'age')
11
當(dāng)默認(rèn)屬性訪問因引發(fā) AttributeError
而失敗時被調(diào)用 (可能是調(diào)用__getattribute__()
時由于 name
不是一個實(shí)例屬性或 self
的類關(guān)系樹中的屬性而引發(fā)了 AttributeError
丙号;或者是對 name
特性屬性調(diào)用 __get__()
時引發(fā)了 AttributeError
先朦。此方法應(yīng)當(dāng)返回(找到的)屬性值或是引發(fā)一個 AttributeError
異常。
請注意如果屬性是通過正常機(jī)制找到的犬缨,
__getattr__()
就不會被調(diào)用喳魏。
(這是在__getattr__()
和__setattr__()
之間故意設(shè)置的不對稱性。)
這既是出于效率理由也是因?yàn)椴贿@樣設(shè)置的話__getattr__()
將無法訪問實(shí)例的其他屬性怀薛。
要注意至少對于實(shí)例變量來說刺彩,你不必在實(shí)例屬性字典中插入任何值(而是通過插入到其他對象)就可以模擬對它的完全控制
setattr(self, name, value)
此方法在一個屬性被嘗試賦值時被調(diào)用。這個調(diào)用會取代正常機(jī)制(即將值保存到實(shí)例字典)枝恋。 name
為屬性名稱创倔, value
為要賦給屬性的值。
>>> getattr(a, 'test')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Human' object has no attribute 'test'
>>> setattr(a, 'test', 'test111')
>>> getattr(a, 'test')
'test111'
如果 __setattr__()
想要賦值給一個實(shí)例屬性焚碌,它應(yīng)該調(diào)用同名的基類方法畦攘,例如 object.__setattr__(self, name, value)
。
getattribute(self, name)
此方法會無條件地被調(diào)用以實(shí)現(xiàn)對類實(shí)例屬性的訪問十电。優(yōu)先級上來說知押,__getattribute__ > __getattr__
。
如果類還定義了__getattr__()
鹃骂,則后者不會被調(diào)用台盯,除非 __getattribute__()
顯式地調(diào)用它或是引發(fā)了 AttributeError
。
如果找到了的話畏线,應(yīng)當(dāng)返回屬性值静盅,否則引發(fā)一個 AttributeError
異常综膀。
而為了避免此方法中的無限遞歸锄码,它的實(shí)現(xiàn)應(yīng)該總是調(diào)用具有相同名稱的基類方法來訪問它所需要的任何屬性,例如 object.__getattribute__(self, name)
匕得。
>>> a.__getattribute__('test')
'test111'
hash
通過內(nèi)置函數(shù) hash()
調(diào)用以對哈希集的成員進(jìn)行操作杯矩,屬于哈希集的類型包括 set
栈虚、frozenset
以及 dict
袖外。
__hash__()
應(yīng)該返回一個整數(shù)史隆。對象比較結(jié)果相同所需的唯一特征屬性是其具有相同的哈希值;
建議的做法是把參與比較的對象全部組件的哈希值混在一起曼验,即將它們打包為一個元組并對該元組做哈希運(yùn)算
class Human(object):
def __hash__(self):
return hash(self.age, self,name, self.sex)
hash()
會從一個對象自定義的__hash__()
方法返回值中截?cái)酁?Py_ssize_t
的大小泌射。通常對 64 位構(gòu)建為 8 字節(jié)粘姜,對 32 位構(gòu)建為 4 字節(jié)。如果一個對象的__hash__()
必須在不同位大小的構(gòu)建上進(jìn)行互操作熔酷,請確保檢查全部所支持構(gòu)建的寬度孤紧。做到這一點(diǎn)的簡單方法是使用python -c "import sys; print(sys.hash_info.width)"
。
在默認(rèn)情況下拒秘,str 和 bytes 對象的 hash() 值會使用一個不可預(yù)知的隨機(jī)值“加鹽”号显。 雖然它們在一個單獨(dú) Python 進(jìn)程中會保持不變,但它們的值在重復(fù)運(yùn)行的 Python 間是不可預(yù)測的躺酒。
這種做法是為了防止以下形式的拒絕服務(wù)攻擊:通過仔細(xì)選擇輸入來利用字典插入操作在最壞情況下的執(zhí)行效率即 O(n^2) 復(fù)雜度押蚤。詳情見 http://www.ocert.org/advisories/ocert-2011-003.html
改變哈希值會影響集合的迭代次序。Python 也從不保證這個次序不會被改變(通常它在 32 位和 64 位構(gòu)建上是不一致的)羹应。
repr(self)
由 repr()
內(nèi)置函數(shù)調(diào)用以輸出一個對象的“官方”字符串表示揽碘。如果可能,這應(yīng)類似一個有效的 Python
表達(dá)式园匹,能被用來重建具有相同取值的對象(只要有適當(dāng)?shù)沫h(huán)境)雳刺。如果這不可能,則應(yīng)返回形式如 <...some useful description...>
的字符串裸违。返回值必須是一個字符串對象掖桦。如果一個類定義了__repr__()
但未定義 __str__()
,則在需要該類的實(shí)例的“非正式”字符串表示時也會使用__repr__()
供汛。
str(self)
通過 str(object)
以及內(nèi)置函數(shù) format()
和 print()
調(diào)用以生成一個對象的“非正式”或格式良好的字符串表示滞详。返回值必須為一個 字符串 對象。
此方法與 object.__repr__()
的不同點(diǎn)在于 __str__()
并不預(yù)期返回一個有效的 Python
表達(dá)式:可以使用更方便或更準(zhǔn)確的描述信息紊馏。
內(nèi)置類型 object
所定義的默認(rèn)實(shí)現(xiàn)會調(diào)用 object.__repr__()
料饥。
>>> print(a)
<__main__.Human object at 0x1032b5240>
>>> str(a)
'<__main__.Human object at 0x1032b5240>'
bytes(self)
通過 bytes 調(diào)用以生成一個對象的字節(jié)串表示。這應(yīng)該返回一個 bytes 對象朱监。
new(cls[,...])
調(diào)用以創(chuàng)建一個 cls 類的新實(shí)例岸啡。
__new__()
是一個靜態(tài)方法 (因?yàn)槭翘乩阅悴恍枰@式地聲明),它會將所請求實(shí)例所屬的類作為第一個參數(shù)赫编。其余的參數(shù)會被傳遞給對象構(gòu)造器表達(dá)式 (對類的調(diào)用)巡蘸。__new__()
的返回值應(yīng)為新對象實(shí)例 (通常是 cls 的實(shí)例)。
典型的實(shí)現(xiàn)會附帶適宜的參數(shù)使用 super().__new__(cls[, ...])
擂送,通過超類的 __new__()
方法來創(chuàng)建一個類的新實(shí)例悦荒,然后根據(jù)需要修改新創(chuàng)建的實(shí)例再將其返回。
如果 __new__()
在構(gòu)造對象期間被發(fā)起調(diào)用并且它返回了一個實(shí)例或 cls
的子類嘹吨,則新實(shí)例的 __init__()
方法將以 __init__(self[, ...])
的形式被發(fā)起調(diào)用搬味,其中 self
為新實(shí)例而其余的參數(shù)與被傳給對象構(gòu)造器的參數(shù)相同。
如果 __new__()
未返回一個 cls
的實(shí)例,則新實(shí)例的 __init__()
方法就不會被執(zhí)行碰纬。
__new__()
的目的主要是允許不可變類型的子類 (例如int, str 或 tuple
) 定制實(shí)例創(chuàng)建過程萍聊。它也常會在自定義元類中被重載以便定制類創(chuàng)建過程。
class StrSub(str):
def __new__(cls, test):
print("__new__ begin")
print(cls)
print(test)
print("__new__ over")
return super().__new__(cls, test)
if __name__ == "__main__":
ss = StrSub("test")
__new__ begin
<class '__main__.StrSub'>
test
__new__ over
init(self[, ...])
在實(shí)例 (通過 __new__()
) 被創(chuàng)建之后悦析,返回調(diào)用者之前調(diào)用寿桨。其參數(shù)與傳遞給類構(gòu)造器表達(dá)式的參數(shù)相同。一個基類如果有 __init__()
方法强戴,則其所派生的類如果也有 __init__()
方法亭螟,就必須地調(diào)用它以確保實(shí)例基類部分的正確初始化;例如:
super().__init__([args...])
.
因?yàn)閷ο笫怯?
__new__()
和__init__()
協(xié)作構(gòu)造完成的 (由__new__()
創(chuàng)建骑歹,并由__init__()
定制)媒佣,所以__init__()
返回的值只能是None
,否則會在運(yùn)行時引發(fā)TypeError
陵刹。
class StrSub(str):
def __init__(self, test):
print("__init__ begin")
self.test = test
print(self.test)
print("__init__ over")
def __new__(cls, test):
print("__new__ begin")
print(cls)
print(test)
print("__new__ over")
return super().__new__(cls, test)
if __name__ == "__main__":
ss = StrSub("test")
__new__ begin
<class '__main__.StrSub'>
test
__new__ over
__init__ begin
test
__init__ over
del(self)
在實(shí)例將被銷毀時調(diào)用默伍。
如果一個基類具有 __del__()
方法,則其所派生的類如果也有 __del__()
方法衰琐,就必須地調(diào)用它以確保實(shí)例基類部分的正確清除也糊。
對象重生
__del__()
方法可以 (但不推薦!) 通過創(chuàng)建一個該實(shí)例的新引用來推遲其銷毀。這被稱為羡宙。
__del__()
是否會在重生的對象將被銷毀時再次被調(diào)用是由具體實(shí)現(xiàn)決定的 狸剃;
當(dāng)前的CPython
實(shí)現(xiàn)只會調(diào)用一次。
當(dāng)解釋器退出時不會確保為仍然存在的對象調(diào)用 __del__()
方法狗热。
del x
并不直接調(diào)用 x.__del__()
:
-
del x
會將 x 的引用計(jì)數(shù)減 1 -
x.__del__()
僅會在 x 的引用計(jì)數(shù)變?yōu)榱銜r被調(diào)用钞馁。
slots
__slots__
允許我們顯式地聲明數(shù)據(jù)成員(例如特征屬性)并禁止創(chuàng)建 __dict__
和 __weakref__
(除非是在 slots 中顯式地聲明或是在父類中可用。)
相比使用 __dict__
此方式可以顯著地節(jié)省空間匿刮。 屬性查找速度也可得到顯著的提升僧凰。
例子:
>>> class Human(object):
... __slots__ = ["age"]
...
>>> a = Human()
>>> a.age
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: age
>>> a.age = 1
>>> a.age
1
>>> a.test = 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Human' object has no attribute 'test'
當(dāng)我們定義了 __slots__
后,我們就無法向這個 類 中添加不存在這個 __slots__
中的屬性了熟丸。
例如上面训措,我們將 __slots__ = ["age"]
加入到Human
中后,當(dāng)我們添加 age
屬性時光羞,是可以添加成功的绩鸣;但是當(dāng)添加 test=2
的屬性時,__slots__
則阻止了我們繼續(xù)添加屬性纱兑,并拋出了 AttributeError
呀闻。
類的初始化和繼承
屬性相關(guān)的內(nèi)容,先說到這里潜慎,下一章我們重點(diǎn)討論類的初始化和繼承先關(guān)的問題捡多。
大家也可以配合官方文檔食用蓖康,效果更佳。
https://docs.python.org/zh-cn/3/reference/datamodel.html#objects-values-and-types