類的定義
類的定義的語法
class ClassName:
<statement-1>
.
.
.
<statement-N>
示例代碼
class Door:
def __init__(self, number, status):
self.number = number
self.status = status
類的實體化
class Door:
def __init__(self, number, status):
self.number = number
self.status = status
door = Door(1001, 'open')
door.number
door.status
- 創建物件使用
類名(__init__ 函式除第一個引數外的引數串列) - 創建物件的時候實際執行了
__init__函式 __init__函式并不會創建物件
函式創建及初始化的程序
- 首先創建物件
- 物件作為self引數傳遞給
__init__函式 - 回傳self
作用域
類變數
示例代碼
In [1]: class A:
...: NAME = 'A' # 類的直接下級作用域 叫做類變數
...: def __init__(self, name):
...: self.name = name # 關聯到實體的變數 叫做實體變數
...:
In [2]: a = A('a')
In [3]: a.NAME
Out[3]: 'A'
In [4]: a.name
Out[4]: 'a'
In [5]: A.NAME
Out[5]: 'A'
In [6]: A.name
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-6-61c1cc534250> in <module>()
----> 1 A.name
AttributeError: type object 'A' has no attribute 'name'
In [7]: a2 = A('a2')
In [8]: a2.NAME
Out[8]: 'A'
In [9]: a2.NAME = 'A2' # 給示例a2的類變數NAME賦值
In [10]: a2.NAME
Out[10]: 'A2'
In [11]: a.NAME
Out[11]: 'A'
In [12]: A.NAME # 類變數沒有變化
Out[12]: 'A'
In [13]: a2.xxx = 3
In [14]: a2.xxx # 賦值之后a2多了xxx屬性
Out[14]: 3
In [15]: A.NAME = 'AA' # 直接修改類的類變數
In [16]: A.NAME
Out[16]: 'AA'
In [17]: a.NAME # 對應的實體的類變數也發生了改變
Out[17]: 'AA'
In [18]: a2.NAME # a2的類變數在之前的賦值被覆寫了,因此改變類變數的并不會影響a2
Out[18]: 'A2'
所以
- 類變數對類和實體都可見
- 所有實體共享類變數
- 當給實體的類變數賦值時,相當于動態的給這個實體增加了一個屬性,覆寫了類變數
屬性查找順序
__dict__: 實體變數的字典__class__: 得到實體對應的類- 先查找
__dict__在查找__class__
代碼
In [1]: class A:
...: NAME = 'A'
...: def __init__(self, name):
...: self.name = name
...:
In [2]: a = A('a')
In [3]: a.NAME
Out[3]: 'A'
In [4]: a.__class__.NAME
Out[4]: 'A'
In [5]: a.__dict__
Out[5]: {'name': 'a'}
In [6]: a.__class__ # a.__class__表示實體對應的類
Out[6]: __main__.A
In [7]: a.NAME = 'AA'
In [8]: a.__dict__ # 覆寫類變數之后__dict__增加了一個鍵值對
Out[8]: {'NAME': 'AA', 'name': 'a'}
In [9]: a.__dict__['NAME'] = 'AAA' # 可以直接修改__dict__
In [10]: a.__dict__
Out[10]: {'NAME': 'AAA', 'name': 'a'}
In [11]: a.__class__.NAME
Out[11]: 'A'
In [12]: a.__class__.__dict__
Out[12]:
mappingproxy({'NAME': 'A',
'__dict__': <attribute '__dict__' of 'A' objects>,
'__doc__': None,
'__init__': <function __main__.A.__init__>,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'A' objects>})
類裝飾器
引數是一個類,并且回傳一個類的函式就可以是一個類裝飾器,
類裝飾器通常用于給類增加屬性,如果增加方法,則都是類級的方法,
代碼1:給類增加屬性
函式方法增加:定義set_name函式給類F增加一個NAME屬性
In [1]: class F:
...: pass
...:
In [2]: def set_name(cls, name): # 給cls增加屬性NAME=name
...: cls.NAME = name
...: return cls
...:
In [3]: F1 = set_name(F, 'F') # 回傳F本身,并且F1指向F
In [4]: F1.NAME
Out[4]: 'F'
In [5]: f1 = F1()
In [6]: f1.NAME
Out[6]: 'F'
In [7]: F1.__dict__
Out[7]:
mappingproxy({'NAME': 'F',
'__dict__': <attribute '__dict__' of 'F' objects>,
'__doc__': None,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'F' objects>})
In [8]: f1.__dict__
Out[8]: {}
In [9]: f1.__class__
Out[9]: __main__.F
In [10]: F.__dict__ # 本質上增加的還是類F
Out[10]:
mappingproxy({'NAME': 'F',
'__dict__': <attribute '__dict__' of 'F' objects>,
'__doc__': None,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'F' objects>})
對set_name函式進行柯里化,實作帶引數的類裝飾器
In [2]: def set_name(name): # 傳入引數name
...: def wrap(cls): # 裝飾器是wrap
...: cls.NAME = name
...: return cls
...: return wrap
...:
In [3]: @set_name('G')
...: class G:
...: pass
...:
In [4]: G.NAME
Out[4]: 'G'
In [5]: class G:
...: pass
...:
In [6]: G = set_name('G')(G) # 裝飾器的函式呼叫方法
In [7]: G.NAME
Out[7]: 'G'
代碼2:給類增加方法
類裝飾器get_name給類H增加一個方法__get_name__
In [1]: def get_name(cls):
...: def _get_name(self):
...: return cls.__name__
...: cls.__get_name__ = _get_name # 給cls增加__get_name__指向_get_name
...: return cls
...:
In [2]: @get_name
...: class H:
...: pass
...:
In [3]: h = H()
In [4]: h.__get_name__()
Out[4]: 'H'
In [5]: H.__dict__
Out[5]:
mappingproxy({'__dict__': <attribute '__dict__' of 'H' objects>,
'__doc__': None,
'__get_name__': <function __main__.get_name.<locals>._get_name>,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'H' objects>})
類方法/靜態方法
方法的定義都是類級的,但是有的方法使用實體呼叫,有的方法使用類來呼叫
- 類方法:當一個方法,被classmethod裝飾時, 第一個引數會變成類本身, 這樣的方法叫類方法
- 當一個方法, 被staticmethod裝飾的時候,不會自動傳遞第一個引數, 這樣的方法叫靜態方法
代碼
class I:
def print(self): # 實體方法
print('instance method')
@classmethod
def class_print(cls): # 類方法
print(id(cls))
print('class method')
@staticmethod
def static_print(): # 靜態方法
print('static method')
def xxx_print(): # 一個普通方法
print('this is a function')
- 實體方法只能由實體呼叫
- 類方法可以被類和實體使用,并且被實體使用時,傳入的第一個引數還是類
- 靜態方法可以被類和實體使用,都不會傳入第一個引數
- 類中的普通方法,因為沒有傳入self,因此只能被類使用,實體無法使用
- 各種方法根據首參來決定,
訪問控制
雙下劃線
- 所有雙下劃線開始,非雙下劃線結尾的成員,都是私有成員
- 嚴格的說, Python里沒有真正私有成員
- Python的私有成員是通過改名實作的:_類名 + 帶雙下劃綫的屬性
- 除非真的有必要,并且清除明白的知道會有什么后果,否則不要通過改名規則修改私有成員
In [1]: class Door:
...: def __init__(self, number, status):
...: self.number = number
...: self.__status = status # 雙下劃線開始, 非雙下劃綫結尾的都是私有的, 在類外部無法訪問
...: def open(self):
...: self.__status = 'opening'
...: def close(self):
...: self.__status = 'closed'
...: def status(self):
...: return self.__status
...: def __set_number(self, number): # # 雙下滑先開始, 非雙下劃線結尾的方法也是私有方法
...: self.number = number
...:
In [2]: door = Door(1001, 'closed')
In [3]: door.__status # 無法訪問私有屬性
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-3-d55234f04e7f> in <module>()
----> 1 door.__status
AttributeError: 'Door' object has no attribute '__status'
In [4]: door.__dict__ # door物件含有的屬性_Door__status
Out[4]: {'_Door__status': 'closed', 'number': 1001}
In [5]: door.__status = 'hahaha' # 給物件創建了新的屬性,并沒有修改到__status
In [6]: door.__status
Out[6]: 'hahaha'
In [7]: door.__dict__
Out[7]: {'_Door__status': 'closed', '__status': 'hahaha', 'number': 1001}
In [8]: door.status()
Out[8]: 'closed'
In [9]: door.open()
In [10]: door.status()
Out[10]: 'opening'
In [11]: door.__set_number(1002)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-11-888a73f63746> in <module>()
----> 1 door.__set_number(1002)
AttributeError: 'Door' object has no attribute '__set_number'
In [12]: door._Door__status
Out[12]: 'opening'
In [13]: door._Door__status = 'hehehe' # _類名 + 帶雙下劃綫的屬性的方式直接修改私有成員
In [14]: door.status()
Out[14]: 'hehehe'
單下劃線
- 單下劃線是一種慣用法, 人為標記此成員為私有, 但是解釋器不不做任何處理
In [1]: class A:
...: def __init__(self):
...: self._a = 3
...:
In [2]: a = A()
In [3]: a._a
Out[3]: 3
In [4]: a._a = 4
In [5]: a._a
Out[5]: 4
In [6]: a.__dict__
Out[6]: {'_a': 4}
property裝飾器
引入property裝飾器
class Door:
def __init__(self, number):
self.__number = number
def get_number(self):
return self.__number
def set_number(self, number):
self.__number = number
當把number屬性變成私有屬性__number之后,無法直接訪問得到,只能通過get_number和set_number兩個函式訪問__number屬性,
如果既能限制引數訪問,又可以用類似屬性這樣簡單的方式來訪問類的變數,這個時候就可以使用property裝飾器了,
- Python內置的
@property裝飾器就是負責把一個方法變成屬性呼叫的
property裝飾器使用
class Door:
def __init__(self, number):
self.__number = number
# property 裝飾器會把一個僅有self引數的函式,變成一個屬性, 屬性的值,為方法的回傳值
@property
def number(self):
return self.__number
# property setter 裝飾器, 可以把一個方法轉化為對此賦值,但此方法有一定要求
# 1.同名 2.必須接收兩個引數 self 和 value, value為所賦的值
@number.setter
def number(self, number):
self.__number = number
@number.deleter
def number(self):
print('cannot remove number property')
door = Door(1001)
door.number # 回傳1001
door.number = 1002
door.number # 回傳1002
del door.number # 輸出cannot remove number property
繼承
單繼承
- 在類名后加括號 括號中是繼承串列, 稱之為父類或者基類或者超類
- 繼承一個明顯的好處就是可以獲取父類的屬性和方法
class Base:
PUBLIC_CLASS_VAR = 'PUBLIC_CLASS_VAR'
__PRIVATE_CLASS_VAR = 'PRIVATE_CLASS_VAR'
def __init__(self):
self.public_instance_var = 'public_instance_var'
self.__private_instance_var = 'private__instance_var'
@classmethod
def public_class_method(cls):
return 'public_class_method'
@classmethod
def __private_class_method(cls):
return 'private_class_method'
@staticmethod
def public_static_method():
return 'public static method'
@staticmethod
def __private_static_method():
return 'private static method'
def public_instance_method(self):
return 'public_instance_method'
def __private_instance_method(self):
return 'private_instance_method'
class Sub(Base):
pass
sub = Sub()
sub.__dict__
# 輸出
{'_Base__private_instance_var': 'private__instance_var',
'public_instance_var': 'public_instance_var'}
- 凡是公有的都能繼承
- 凡是私有的都不能繼承
- 原來是什么,繼承過來還是什么
方法重寫
- 當子類和父類有同名成員的時候, 子類的成員會覆寫父類的同名成員
- 當父類含有一個帶引數的初始化方法的時候,子類一定需要一個初始化方法,并且在初始化方法中呼叫父類的初始化方法
- super方法:super(type, obj) =》type:類名,obj:傳遞給后續方法的第一個引數
class Base:
def __init__(self):
self.__a = 4
def print(self):
print('Base.print')
@classmethod
def cls_print(cls):
print('Base.cls_print')
class Sub(Base):
def print(self): ## 當子類和父類有同名成員的時候, 子類的成員會覆寫父類的同名成員
print('Sub.print')
@classmethod
def cls_print(cls):
print('Sub.cls_print')
def foo(self):
# 呼叫父類的print
super().print()
# super(Sub, self).print()
@classmethod
def cls_foo(cls):
#cls.cls_print()
#Base.cls_print()
super().cls_print()
class SubSub(Sub):
def print(self):
print('SubSub.print')
@classmethod
def cls_print(cls):
print('SubSub.cls_print')
def foo(self):
# 呼叫Base的print
super(SubSub, self).print()
# 代理 TYPE 的父類的方法, 并且使用 obj 系結 第一個引數 指定呼叫誰的直接父類, 第二個引數指定當呼叫時,傳遞什么作為方法的第一個引數
super(Sub, self).print()
super(SubSub, SubSub).cls_print() # 類方法的時候可以傳遞類,也可以傳遞實體self
@classmethod
def cls_foo(cls):
# Base.cls_print()
super(Sub, cls).cls_print()
多繼承與MRO
等效的類定義
class A:
pass
class A(object):
pass
class A():
passs
多繼承
- 在繼承串列里存在多個類的時候表示多繼承
- 多繼承會把繼承串列里的所有公有成員都繼承過來
class A:
def method(self):
print('method of A')
class B:
def method(self):
print('method of B')
class C(A, B):
pass
c = C()
c.method() # 輸出method of A
MRO
定義一個多繼承,如下
class A:
def method(self):
print('method of A')
class B:
def method(self):
print('method of B')
class C(A, B):
pass
class E(A):
def method(self):
print('method of E')
class F(E, A):
pass
F().method() # 輸出method of E
如果定義類G繼承自(A, E),如下
class G(A, E): # 在定義的時候會直接報錯
pass
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-51-dcac33a3d00c> in <module>()
----> 1 class G(A, E):
2 pass
TypeError: Cannot create a consistent method resolution
order (MRO) for bases E, A
報錯顯示:Cannot create a consistent method resolution order (MRO) for bases E, A
方法決議順序(MRO)不滿足報錯
分析基類E,A的MRO
>>> A.__mro__
(__main__.A, object)
>>> E.__mro__
(__main__.E, __main__.A, object)
>>> F.__mro__
(__main__.F, __main__.E, __main__.A, object)
所以,mro序列就是繼承的先后順序
那么G類的mro序列應該就是(G, A, E, object),Python通過C3演算法來確定多繼承的時候是否滿足mro的兩個原則:
- 本地優先: 自己定義或重寫的方法優先,按照繼承串列,從左到右查找
- 單調性:所有子類,也要滿足查找順序
C3演算法的主要作用是:在多繼承時判斷屬性來自于哪個類,無法判斷時拋出TypeError
C3演算法
class B(O) :則B的mro序列為: [B, O]
class B(A1, A2, ..., An) :則B的mro序列為: [B] + merge(mro(A1), mro(A2), ..., mro(An), [A1, A2, ..., An, O])
merge操作就是C3演算法的核心,merge步驟如下:
* 遍歷串列
* 看第一個串列的首元素
* 它在其他串列中也是首元素
* 或者它在其他串列不存在
* 滿足以上條件,則移除該首元素,合并到mro中
* 不滿足,則拋出例外
C3演算法分析F類的mro
mro(F) -> [F] + merge(mro(E), mro(A), [E, A, O])
-> [F] + merge([E, A, O], [A, O], [E, A, O])
-> [F, E] + merge([A, O], [A, O], [A, O])
-> [F, E, A] + merge([O], [O], [O])
-> [F, E, A, O]
merge操作成功,mro決議正確,最終mro為[F, E, A, O]
C3演算法分析G類的mro
mro(G) -> [G] + merge(mro(A), mro(E), [A, E, O])
-> [G] + merge([A, O], [E, A, O], [A, E, O])
-> raise TypeError:
第一個串列的首元素為A,在第二個串列中存在但不是首元素,不滿足merge的條件,直接拋出例外,
結論:
- 應該盡量避免多繼承
- 多繼承會對程式的心智負擔造成非常大的壓力
Mixin類
參考
- 廖雪峰-多重繼承與MixIn
- 知乎-Mixin是什么概念?
- Python Cookbook-利用Mixins擴展類功能
在編程中,mixin是指為繼承自它的class提供額外的功能, 但它自身卻是不單獨使用的類.,在具有多繼承能力的編程語言中, mixin可以為類增加額外功能或方法,
因此,MixIn模式的目的就是給一個類增加多個功能,這樣,在設計類的時候,我們優先考慮通過多重繼承來組合多個MixIn的功能,而不是設計多層次的復雜的繼承關系,
在Python 3.5.2 源代碼 socketserver.py 中的639到643行可以看到以下四個類的定義
class ForkingUDPServer(ForkingMixIn, UDPServer): pass
class ForkingTCPServer(ForkingMixIn, TCPServer): pass
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
- BaseServer:server類的基類
- UDPServer:UDP server class,繼承自BaseServer
- TCPServer:TCP server class,繼承自BaseServer
- ForkingMixIn:Mix-in class to handle each request in a new process.
- ThreadingMixIn:Mix-in class to handle each request in a new thread.
Python自帶了TCPServer和UDPServer這兩類網路服務,而要同時服務多個用戶就必須使用多行程或多執行緒模型,這兩種模型由ForkingMixIn和ThreadingMixIn提供,通過組合,就可以得到以上四個類,
這幾個類之間的關系如下圖:

可以看到,從BaseServer開始逐層繼承的程序中,混入(MixIn)了ForkingMixIn類和ThreadingMixIn類,
這樣的多重繼承的技巧稱為MixIn,
如果不采用MixIn技術,而是采用層次復雜的單繼承實作,則類的數量會呈指數增長,
具體不采用MixIn技術設計的繼承層次關系參見:廖雪峰-多重繼承與MixIn中的Animal類的設計思路,
MixIn總結
MixIn其實也是一種組合的方式,通常來說,組合優于繼承
Mixin 類的限制
- Mixin類不應該有初始化方法
- Mixin類通常不能獨立作業
- Mixin類的祖先也應該是Mixin類
通常情況下,Mixin類總在繼承串列的第一位
記得幫我點贊哦!
精心整理了計算機各個方向的從入門、進階、實戰的視頻課程和電子書,按照目錄合理分類,總能找到你需要的學習資料,還在等什么?快去關注下載吧!!!

念念不忘,必有回響,小伙伴們幫我點個贊吧,非常感謝,
我是職場亮哥,YY高級軟體工程師、四年作業經驗,拒絕咸魚爭當龍頭的斜杠程式員,
聽我說,進步多,程式人生一把梭
如果有幸能幫到你,請幫我點個【贊】,給個關注,如果能順帶評論給個鼓勵,將不勝感激,
職場亮哥文章串列:更多文章

本人所有文章、回答都與著作權保護平臺有合作,著作權歸職場亮哥所有,未經授權,轉載必究!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/149049.html
標籤:其他
