該文章內容整理自《Python程式設計:從入門到實踐》、《流暢的Python》、以及網上各大部落格
Python是物件導向程式設計的語言,封裝、繼承、和多型是其三大特徵。對於封裝性,如將多種不同數據放到列表中就是一種數據層面的封裝;把常用的程式碼塊打包成一個函數也是一種語句層面的封裝。而類則是一種更好的封裝
類提供了一種組合數據和功能的方法。 建立一個新類意味着建立一個新的物件 型別,從而允許建立一個該型別的新範例 。和其他程式語言相比,Python 用非常少的新語法和語意將類加入到語言中。它是 C++ 和 Modula-3 中類機制 機製的結合。Python 的類提供了物件導向程式設計的所有標準特性:類繼承機制 機製允許多個基礎類別,派生類可以覆蓋它基礎類別的任何方法,一個方法可以呼叫基礎類別中相同名稱的的方法
一個類由三大組成部分,分別是
類的定義和範例化一般形式如下。其中__init__()爲類別建構函式。self爲類範例物件本身。另外,self只是約定俗成的名稱,並不是Python中的關鍵詞,因而可以將其宣告爲其他名稱。
class ClassName:
def __init__(self, a):
self.a = a
#def __init__(test, a):
# test.a = a
obj = ClassName()
類是獨立的名稱空間。在類定義中,根據變數定義位置及方式的不同,類中變數分爲以下 3 種類型:
class Test :
def __init__(self, a):
self.a = a
t = Test(1)
t.b = 0 # 動態新增屬性
class Test :
a = "A"
def info(self):
pass
在Python中,在類內定義的實體方法和類方法稱爲方法(method);而在類內定義的靜態方法和類外定義的函數稱爲函數(function)。即與類和範例有系結關係的function都屬於方法;與類和範例無系結關係的function都屬於函數
和類屬性的分類不同,類方法是通過函數修飾器進行分類的
class Test:
def func(self):
pass
t = Test()
t.func() # 通過範例化物件呼叫實體方法
Test.func(t) # 通過類名呼叫實體方法,此時需要手動給self傳入範例化物件
print(type(t.func)) # <class 'method'>
# 通過類名動態新增實體方法,影響類的全部範例
def info1(self):
pass
Test.info1 = info1
# 通過範例化物件名動態新增實體方法,隻影響範例化物件
def info2(): # 若這裏新增self參數,在呼叫時需要手動傳入參數
pass
t.info2 = info2
class Test:
@classmethod
def func(cls):
print("正在呼叫類方法", cls)
# 動態新增類方法
@classmethod
def info(cls):
pass
Test.info = info
class Test:
@staticmethod
def func(a, b):
print(a, b)
# 動態新增靜態方法
@staticmethod
def info():
pass
Test.info = info
Python不同於C++會在公有和私有成員之間進行很強的區分,實際上,Python中並不提供嚴格意義上的公有成員和私有成員。爲了實現類的封裝性,Python採用下劃線命名來進行區分
for _ in range(10): print("Hello")
car = ('red', 'auto', 12, 3812.4)
color, _, _, mileage = car
Python 提供了dir() 函數,當函數不帶參數時,返回當前範圍內的變數、方法和定義的型別列表;帶參數時,返回參數的屬性、方法列表。如果參數包含方法__dir__(),該方法將被呼叫。如果參數不包含__dir__(),該方法將最大限度地收集參數資訊。如
print(dir()) # 獲得當前模組的屬性列表
# ['__builtins__', '__doc__', ...]
print(dir([ ])) # 檢視列表的方法
# ['__add__', '__class__', ...]
class a(object):
pass
print(dir(a())) # 檢視一個最簡單的類的所有屬性和方法
# ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
常見雙下劃線屬性
常見雙下劃線方法
class NewTest():
def __new__(cls, *args, **kwargs):
print("__new__")
return object.__new__(cls)
def __init__(self, a, b):
print("__init__")
self.a = a
self.b = b
# 單例模式是__new__()的一個應用,即一個類始終是有一個範例
class Foo:
__instance = False
def __new__(cls, *args, **kwargs):
if cls.__instance: return cls.__instance
cls.__instance = object.__new__(cls)
return cls.__instance
class CallTest():
def __init__(self):
print("__init__")
def __call__(self):
print("__call__")
obj = CallTest() # 觸發建構函式
obj() # 觸發__call__
class itemTest:
def __getitem__(self, item):
print('getitem執行', self.__dict__[item])
def __setitem__(self, key, value):
print('setitem執行')
self.__dict__[key] = value
def __delitem__(self, key):
print('delitem執行')
self.__dict__.pop(key)
i = itemTest()
print(i["a"] + 1)
i["a"] = 1
del i["a"]
class AttrTest:
def __getattr__(self, item):
print("__getattr__", str(item))
# item爲該不存在屬性的名稱
return "Cannot find"
def __getattribute__(self, item):
print("__getattribute__", str(item))
# item爲該屬性的名稱
def __setattr__(self, key, value):
print("__setattr__", str(value))
# key爲變數,value爲設定值。但self.key=value,會造成死回圈
self.__dict__[key] = value
# 必須進行屬性字典的操作才能 纔能完成賦值
def __delattr__(self, item):
print("__delattr__", str(item))
# item爲該屬性的名稱
self.__dict__.pop(item)
# 必須進行屬性字典的操作才能 纔能完成刪除
obj = AttrTest()
print(obj.a) # 呼叫屬性不存在時觸發__getattr__,並且返回None
obj.a = 1 # 修改屬性時觸發__setattr__
del obj.a # 刪除屬性時觸發__delattr__
class TestEH:
def __init__(self, a, b):
self.a = a
self.b = b
def __hash__(self):
print("__hash__")
return hash(self.a + self.b)
def __eq__(self, other):
print("__eq__")
return self.a == other.a and self.b == other.b
a = TestEH(1, 2)
b = TestEH(3, 4)
print(a == b) # 進行等價判斷時會呼叫__eq__()方法
s = set()
s.add(a) # 需要進行獲得物件的雜湊值時會呼叫__hash__()方法
s.add(b)
class TestLen:
def __init__(self, a):
self.a = a
def __len__(self):
print("__len__")
return len(self.__dict__)
a = TestLen(1)
print(len(a)) # 呼叫__len__()方法
class Range:
def __init__(self, n, stop, step):
self.n = n
self.stop = stop
self.step = step
def __next__(self):
if self.n >= self.stop:
raise StopIteration #拋出停止迭代異常
x = self.n
self.n += self.step
return x
def __iter__(self):
return self
for i in Range(1, 7, 3):
print(i)
# 或生成斐波那契數列
class Fib:
def __init__(self):
self._a = 0
self._b = 1
def __iter__(self):
return self
def __next__(self):
self._a, self._b = self._b, self._a + self._b
return self._a
for i in Fib():
if i > 100:
break
print('%s ' % i, end='')
format_dic = {
'y-m-d':'{obj.year}-{obj.mon}-{obj.day}',
'd/m/y':'{obj.day}/{obj.mon}/{obj.year}'
}
class Date:
def __init__(self,year,mon,day):
self.year = year
self.mon = mon
self.day = day
def __format__(self, format_spec):
if not format_spec or format_spec not in format_dic:
format_spec = 'y-m-d'
fm = format_dic[format_spec]
return fm.format(obj = self)
d = Date(2000, 1, 1)
print(format(d, 'y-m-d'))
class TestStr:
def __init__(self, a):
self.a = a
def __str__(self):
return str(self.a)
def __int__(self):
return self.a
def __float__(self):
return self.a
a = TestEH(1)
print(str(a)) # 此時會呼叫__str__()方法
print(int(a)) # 此時會呼叫__int__()方法
print(float(a)) # 此時會呼叫__float__()方法
class Open:
def __init__(self, filepath, mode='r', encoding='utf-8'):
self.filepath = filepath
self.mode = mode
self.encoding = encoding
def __enter__(self):
# print('enter')
self.f = open(self.filepath, mode=self.mode, encoding=self.encoding)
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
# print('exit')
self.f.close()
return True
def __getattr__(self, item):
return getattr(self.f, item)
with Open('a.txt', 'w') as f:
print(f)
f.write('aaaaaa')
f.wasdf #拋出異常,交給__exit__處理
描述符是一個具有系結行爲的物件屬性,其屬性存取將由描述符協定中的方法覆蓋。這些方法爲 __get__()、__set__() 和 __delete__()。簡單來講,描述符是指用含有這些方法中的一些的一個類,當對這個類的範例化物件進行操作時,會自動呼叫相應的方法
當Python直譯器發現範例物件中有與描述符同名的屬性時,描述符優先,會覆蓋掉範例屬性。如果全定義了__get__() 方法,而沒有定義 __set__(), __delete__() 方法,則成爲數據描述符,若只定義了 __get__() 方法,則認爲是非數據描述符。數據描述符的優先順序要比非數據描述符高,且非數據描述符優先順序要比範例屬性,類屬性要低
實現方式
class Descriptor(object):
def __init__(self):
self._name = ''
def __get__(self, instance, owner):
print("__get__")
return self._name
def __set__(self, instance, value):
print("__set__")
self._name = value
def __delete__(self, instance):
print("__delete__")
del self._name
class Person(object):
def __init__(self):
self.name = Descriptor()
p = Person()
print(p.name)
# print(p.__dict__['name'].__get__(p, Person))
p.name = 'A'
# p.__dict__['name'].__set__(p, 'A')
del p.name
# p.__dict__['name'].__delete__(p)
class Person(object):
def __init__(self):
self._name = ''
def fget(self):
print("Getting: %s" % self._name)
return self._name
def fset(self, value):
print("Setting: %s" % value)
self._name = value.title()
def fdel(self):
print("Deleting: %s" % self._name)
del self._name
name = property(fget, fset, fdel, "I'm the property.")
p = Person()
print(p.name)
p.name = 'A'
del p.name
class Person(object):
def __init__(self, name):
self._name = name
@property
def name(self):
print("get_name")
return self._name
@name.setter # 若不設定setter,則該屬性爲只讀
def name(self, value):
print("set_name")
self._name = value
@name.deleter
def name(self):
print("del_name")
del self._name
p = Person("A")
print(p.name) #此時是用@property修飾的函數名來作爲屬性的名稱
p.name = "B"
del p.name
描述符的使用場景
繼承是一種建立新類的方式。其中,父類別又可稱爲基礎類別或超類,新建的類稱爲派生類或子類,而子類會繼承父類別的屬性和方法。注意,如果該類沒有顯式指定繼承自哪個類,則預設繼承 object 類(object 類是 Python 中所有類的父類別,即要麼是直接父類別,要麼是間接父類別)。其一般形式爲
class 類名(父類別1):
#類定義部分
和C++的多繼承機制 機製一樣,Python中新建的類可以繼承一個或多個父類別(但和單繼承相比,多繼承容易讓程式碼邏輯複雜、思路混亂,一直備受爭議,中小型專案中較少使用,後來的 Java、C#、PHP 等乾脆取消了多繼承)。使用多繼承經常需要面臨的問題是,多個父類別中包含同名的類方法。對於這種情況,Python 的處置措施是:根據子類繼承多個父類別時這些父類別的前後次序決定,即排在前面父類別中的類方法會覆蓋排在後面父類別中的同名類方法(注意多繼承是子類同時繼承多個父類別,而多重繼承則是繼承有多個層次)
class People:
def say(self):
print("People類")
class Animal:
def say(self):
print("Animal類")
class Person(People, Animal):
pass
p = Person()
p.say()
這種繼承多個父類別的設計模式使得子類得以「混入」多種額外的功能,因而這種設計模式稱爲混入類(MixIn)。混入類避免設計多層次的複雜的繼承關係,爲程式碼重用而生,使得程式碼結構簡單清晰
如有狗狗、 蝙蝠、 鸚鵡、 鴕鳥四個子類,這四個子類都繼承父類別動物類。現對這四種動物有兩種分類方法:以能不能飛分類,和以是否爲哺乳類動物分類。若沒有多繼承,則只能動物類下有哺乳類和非哺乳類兩個子類,哺乳類下有能飛和能跑兩個子類,非哺乳類下同樣有能飛和能跑兩個子類,然後最下面 下麪纔是四種動物的子類。若分類方式增多,則繼承的子類個數會以指數遞增。而當使用多繼承時,中間哺乳類子類、非哺乳類子類,能跑子類、以及能飛子類則爲混入類,最底層的動物子類能夠同是繼承混入多個混入類。如狗類同時有哺乳類混入類以及能跑混入類
在Python2中,預設都是經典類,只有顯式繼承了object纔是新式類,即:
在Python3中取消了經典類,預設都是新式類,並且不必顯式的繼承object,也就是說上面三種寫法都是新式類寫法。他們最明顯的區別在於繼承搜尋的順序發生了改變,即經典類多繼承搜尋順序爲深度優先,先深入繼承樹左側查詢,然後再返回,開始查詢右側;而新式類多繼承搜尋順序爲廣度優先,先在水平方向查詢,然後再向上查詢
對於定義的類,Python會建立一個方法解析順序MRO(Method Resolution Order)列表來記錄類繼承的順序。對於單繼承來說,MRO 一般比較簡單;而對於多繼承來說,MRO 就複雜很多。Python 至少有三種不同的 MRO,下面 下麪分別以菱形繼承(D的父類別爲B和C,B和C的父類別爲A)說明
1). 經典類(classic class)的深度優先遍歷
此時查詢順序爲 [D, B, A, C, A],有些父類別可能會查詢多次,因而會增大開銷
2). Python 2.2 的新式類(new-style class)預計算
爲解決經典類 MRO 所存在的問題,Python 2.2 針對新式類提出了一種新的 MRO 計算方式,在定義類時就計算出該類的 MRO 並將其作爲類的屬性。因此新式類可以直接通過__mro__屬性獲取類的 MRO。Python 2.2 的新式類 MRO 計算方式和經典類 MRO 的計算方式非常相似,它仍然採用從左至右的深度優先遍歷,但是如果遍歷中出現重複的類,只保留最後一個。因而此時查詢順序爲 [D, B, C, A, object](因爲爲新式類,當類A、B、C、D都不存在目標方法時會繼續往父類別object查詢)
3). Python 2.3 的新式類的C3 演算法
它也是 Python 3 唯一支援的方式。針對一些比較複雜的情況,預計算方法可能並不奏效。如下圖所示
此時對於 A 來說,其搜尋順序爲[A, X, Y, object];對於 B,其搜尋順序爲 [B, Y, X, object];對於 C,其搜尋順序爲[C, A, B, Y, X, object]。也就是說,A 和 C 中 X、Y 的搜尋順序是相反的,即當 A 被繼承時,它本身的行爲、發生了改變,這很容易導致不易察覺的錯誤。此外,即使把 C 搜尋順序中 X 和 Y 互換仍然不能解決問題,這時候它又會和 B 中的搜尋順序相矛盾。其原因在於,上述繼承關係違反了線性化的單調性原則,父類別的繼承順序不同。因而Python 2.3 提出了C3 演算法。此時再按上述定義類則會產生一個異常,禁止建立具有二義性的繼承關係
在C3演算法中,Python把類 C 的 MRO 記爲 L[C] = [C1, C2,…,CN]。其中 C1 稱爲 L[C] 的頭,其餘元素 [C2,…,CN] 稱爲尾。如果一個類 C 繼承自基礎類別 B1、B2、……、BN,那麼我們可以根據以下兩步計算出 L[C]:
(1) L\[object] = \[object]
(2) L[C(B1…BN)] = [C] + merge(L[B1]…L[BN], [B1]…[BN])
merge 的計算方式如下:
(1) 檢查第一個列表的頭元素(如 L[B1] 的頭),記作 H
(2) 若 H 未出現在其它列表的尾部,則將其輸出,並將其從所有列表中刪除,然後回到步驟(1);否則,取出下一個列表的頭部記作 H,繼續該步驟
(3) 重複上述步驟,直至列表爲空或者不能再找出可以輸出的元素。如果是前一種情況,則演算法結束;如果是後一種情況,說明無法構建繼承關係,Python 會拋出異常
該方法有點類似於圖的拓撲排序,但它同時還考慮了基礎類別的出現順序。此時,A 的MRO計算方法過程爲:
L[A] = [A] + merge(L[X], L[Y], [X], [Y])
= [A] + merge([X, object], [Y, object], [X], [Y])
= [A, X] + merge([object], [Y, object], [Y])
= [A, X, Y] + merge([object], [object])
= [A, X, Y, object]
注意第3步,merge([object], [Y, object], [Y]) 中首先輸出的是 Y 而不是 object。這是因爲 object 雖然是第一個列表的頭,但是它出現在了第二個列表的尾部。所以我們會跳過第一個列表,去檢查第二個列表的頭部,也就是 Y。Y 沒有出現在其它列表的尾部,所以將其輸出
super() 函數是用於呼叫已經在子類中被重寫的父類別方法。super 是用來解決多重繼承問題的,直接用類名呼叫父類別方法在使用單繼承的時候沒問題,但是如果使用多繼承,會涉及到查詢順序(MRO)、重複呼叫(鑽石繼承)等種種問題。super() 的一般形式爲 super(type[, object-or-type])。其中,type表示類,object-or-type表示 類,一般是 self。super(type, obj)返回 obj 的MRO中Type的下一個類的代理。Python3 和 Python2 中super()的區別爲,Python3 可以使用直接使用 super().xxx 代替 Python2 中的 super(type, obj).xxx
class A:
def x(self):
print('run A.x')
super().x()
print(self)
class B:
def x(self):
print('run B.x')
print(self)
class C(A,B):
def x(self):
print('run C.x')
super().x()
print(self)
C().x()
# 輸出
# run C.x
# run A.x
# run B.x
# <__main__.C object at 0x000002B5041BB710>
# <__main__.C object at 0x000002B5041BB710>
# <__main__.C object at 0x000002B5041BB710>
在呼叫了A中的x()方法之後,下一個呼叫的是B中的x()方法,在繼承結構中,類A和類B互爲兄弟關係,super()在A中呼叫的時候,最終卻呼叫其兄弟的同名方法。這是因爲在呼叫super(type, obj)時,type參數傳入的是當前的類,而obj參數則是預設傳入當前的範例物件,在super()的後續呼叫中,obj一直未變,而實際傳入的class是動態變化。不過,在首次呼叫時,MRO就已經被確定(注意整個過程只有一個範例),是obj所屬類(即C)的MRO,因此class參數的作用就是從已確定的MRO中找到位於其後緊鄰的類,作爲再次呼叫super()時查詢該方法的下一個類
因此,在子類建構函式中呼叫父類別建構函式的順序也是按照MRO的順序呼叫
通過在Python中使用super()函數,可以實現僅通過重寫無法實現的,多繼承協同工作的邏輯。如子類Final類的父類別爲Minix1、Minix2、Header類,Final類從Header類中繼承獲得屬性header列表,並且通過Minix1和Minix2類在列表header中追加屬性data1和data2
class Minix1:
def get_header(self):
print('run Minix1.get_header')
ctx = super().get_header()
ctx.append('data1')
return ctx
class Minix2:
def get_header(self):
print('run Minix2.get_header')
ctx = super().get_header()
ctx.insert(0, 'data2')
return ctx
class Header:
header = []
def get_header(self):
print('run Headers.get_header')
return self.header if self.header else []
class Final(Minix1, Minix2, Header):
def get_header(self):
return super().get_header()
Python有兩個可以判斷繼承關係的內建函數:
使用isinstance()檢查範例的型別:isinstance(obj, int),當且僅當obj.__class__是int或者派生於int的類時,返回True
使用issubclass()檢查類的繼承關係:issubclass(bool, int)返回True,因爲bool是int的子類。然而issubclass(float, int)返回False,因爲float不是int的子類
在Python2.2之後,內建型別都可以子類化。內建型別子類化是指自定義一個新類,使其繼承有類似行爲的內建類,通過重定義這個新類實現指定的功能。如果想實現與某個內建型別具有類似行爲的類時,最好的方法就是將這個內建型別子類化。當使用內建型別子類化時,它們作爲子類的速度更快,程式碼更整潔
class DopperDict(dict):
def __setitem__(self, key, value):
super(DopperDict,self).__setitem__(key, [value]*2)
d = DopperDict(one=1) # d = {'one': 1}
d['two'] = 2 # d = {'two': [2,2], 'one': 1}
d.update(three=3) # d = {'three': 3, 'two': [2,2], 'one':1}
這裏雖然重寫了__setitem__()方法,使用[]運算子會呼叫覆蓋的__setitem__()方法,按照預期工作,但繼承自dict的__init__()和update()方法忽略了被覆蓋的__setitem__()方法,one和three值並沒有重複。這就會導致初始化和賦值的時候得到的結果不一樣。因而自定義的內建型別子類應該繼承collections模組,如UserDict,UserList,UserString等。這些類做了特殊設計,因此易於拓展。但是仍不推薦重寫內建型別子類中的方法
import collections
class DopperDict(collections.UserDict):
def __setitem__(self, key, value):
super(DopperDict,self).__setitem__(key, [value]*2)
Python中定義抽象基礎類別必須繼承abc模組的ABC類,或者使用元類方式,設定類的元類爲abc.ABCMeta來實現。同時,使用裝飾器@abc.abstractmethod定義抽象方法,並且其子類必須定義抽象方法
from abc import ABC
class A(ABC):
#class A(metaclass=abc.ABCMeta):
# 抽象實體方法
@abstractmethod
def a(self): pass
# 抽象類方法
@classmethod
@abstractmethod
def b(cls): pass
# 抽象靜態方法
@staticmethod
@abstractmethod
def c(): pass
# 抽象屬性
@property
def x(self): pass
@x.setter
@abstractmethod
def x(self, val): pass
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def whoAmI(self):
return 'I am a Person, my name is %s' % self.name
class Student(Person):
def __init__(self, name, gender, score):
super(Student, self).__init__(name, gender)
self.score = score
def whoAmI(self):
return 'I am a Student, my name is %s' % self.name
class Teacher(Person):
def __init__(self, name, gender, course):
super(Teacher, self).__init__(name, gender)
self.course = course
def whoAmI(self):
return 'I am a Teacher, my name is %s' % self.name
def who_am_i(x):
print x.whoAmI()
p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')
who_am_i(p)
who_am_i(s)
who_am_i(t)
由於Python是動態語言,所以,傳遞給函數 who_am_i(x)的參數 x 不一定是 Person 或 Person 的子型別。任何數據型別的範例都可以,只要它有一個whoAmI()的方法即可。這是動態語言和靜態語言(例如Java)最大的差別之一。動態語言呼叫實體方法,不檢查型別,只要方法存在,參數正確,就可以呼叫。另外,注意在 Python 中不僅實體方法支援多型,類方法同樣支援多型
之前說過Python能夠動態新增(系結)屬性和方法,其實,Python也能夠動態建立類。有兩個方法可以在執行時動態建立類:
def choose_class(name):
if name == 'foo':
class Foo(object):
pass
return Foo # 返回的是類,不是類的範例
else:
class Bar(object):
pass
return Bar
MyClass = choose_class('foo')
class parent:
class child:
def __init__(self):
self.name = 'child'
child = parent.child() # 返回的是範例
def func(self):
pass
Test = type("Test",(object,),dict(func = func, name = "Test Class"))
t = Test()
在Python當中萬物皆物件,我們用class關鍵字定義的類本身也是一個物件,可以像使用其他物件一樣使用這個類物件。而負責產生該物件的類稱之爲元類(Meta Class),元類可以簡稱爲類的類。即MyClass = MetaClass()中元類範例化物件爲自定義類物件,而MyObject = MyClass()中自定義類的範例化物件爲MyObject。而type()函數就是Python的一個內建元類,在python當中任何class定義的類其實都是type類範例化的結果。只有繼承了type類才能 纔能稱之爲一個元類,否則就是一個普通的自定義類。在Python的世界中,object是父子關係的頂端,所有的數據型別的父類別都是它;type是型別範例關係的頂端,所有物件都是它的範例的。元類type爲小寫是爲了保持一致性,如int是用來建立整數物件的類,而str是用來建立字串物件的類。Python中所有物件都有__class__屬性,它表示物件所屬的類型別,而__class__屬性也有__class__,爲type,表示類型別的型別
class MyMetaClass(type):
def __init__(self, cls_name, cls_bases, cls_dict):
print("test")
def func(self): pass
Test1 = MyMetaClass("Test1",(object,), {'func': func, 'name': "Test Class"})
# 在Python2中需要指定__metaclass__屬性
class Test2(object):
__metaclass__ = MyMetaClass
name = "Test Class"
def func(self): pass
# 在Python3中則可以在一開始宣告元類
class Test3(object, metaclass=MyMetaClass):
name = "Test Class"
def func(self): pass
在定義類,即MyClass = MetaClass()時,先找類的元類,如果找不到則往上找父類別的元類,如果也找不到則找模組的元類,如果再找不到則用Python內建的type元類。在找到元類後,就開始建立類物件,此時,先通過元類的__new__()產生一個空物件,通過元類的__init__()初始化這個類物件,最後返回這個類物件。在得到類物件後,在類物件範例化,即MyObject = MyClass()時,MyClass()中的括號表示呼叫元類的__call__()方法(MyClass對於元類來說是範例,所以MyClass()是呼叫元類的__call__()方法,而MyObject()纔是呼叫MyClass的__call__()方法)。換句話說,重寫元類的__new__()和__init__()方法能修改類物件的建立過程,而重寫原來的__call__()方法能修改類物件範例化的過程。所以之前重寫自定義類的__new__()方法來實現單例模式,這裏可通過重寫元類的__call__()方法來實現
class MyMetaClass(type):
def __init__(self, *args, **kwargs):
self.__instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super().__call__(*args, **kwargs)
return self.__instance
class MyClass(object, metaclass=MyMetaClass):
pass
t1 = MyClass()
t2 = MyClass()
print(t1 is t2)
下面 下麪給出另一個利用元類的例子。要求保證使用者設計的類必須有文件註釋,不能爲空 ;同時要求在一個類內部定義的所有函數必須有文件註釋,不能爲空
class MyMetaClass1(type):
def __init__(self, cls_name, cls_bases, cls_dict):
if '__doc__' not in cls_dict or \
len(cls_dict['__doc__'].strip()) == 0:
raise TypeError('類必須有非空文件註釋')
for key, value in cls_dict.items():
if key.startswith('__'):
continue
if not callable(value):
continue
if not value.__doc__ or \
len(value.__doc__.strip()) == 0:
raise TypeError('函數必須有非空文件註釋')
super().__init__(cls_name, cls_bases, cls_dict)
# 也可以利用__new__()方法來實現
class MyMetaClass2(type):
def __new__(cls, cls_name, cls_bases, cls_dict):
if '__doc__' not in cls_dict or \
len(cls_dict['__doc__'].strip()) == 0:
raise TypeError('類必須有非空文件註釋')
for key, value in cls_dict.items():
if key.startswith('__'):
continue
if not callable(value):
continue
if not value.__doc__ or \
len(value.__doc__.strip()) == 0:
raise TypeError('函數必須有非空文件註釋')
return super().__new__(cls, cls_name, cls_bases, cls_dict)
Python 3中新增加了 Enum 列舉類,和普通類的用法不同,列舉類不能用來範例化物件
from enum import Enum
class Color(Enum):
red = 1 # 這些值不能在類外部修改
green = 2
blue = 3
# 存取
print(Color.red) # Color.red
print(Color['red']) # Color.red
print(Color(1)) # Color.red
print(Color.red.value) # 1
print(Color.red.name) # red
for color in Color:
print(color)
# Color.red Color.green Color.blue
for name, member in Color.__members__.items():
print(name,"-",member)
# red-Color.red green-Color.green blue-Color.blue
# 比較
print(Color.red == Color.green) # Flase
print(Color.red.name is Color.green.name) # Flase
Python允許值相同的情況,此時會將r當作red的別名,因此存取r成員最終輸出的是red。若想避免這種情況,需要用@unique 裝飾器,當列舉類中出現相同值的成員時會報 ValueError 錯誤
from enum import Enum
class Color(Enum):
red = 1
r = 1
@unique
class UniqueColor(Enum):
red = 1
r = 1
除了通過繼承 Enum 類的方法建立列舉類,還可以使用 Enum() 函數建立列舉類。此時成員的值從1開始順序賦值
from enum import Enum
Color = Enum("Color", ('red','green','blue'))
與C++不同,Python中過載運算子只需要重寫相應的函數就可以了
class OpTest:
def __init__(self, a):
self.a = a
def __lt__(self, record): #過載 < 號運算子
return self.a < record.a:
def __add__(self, record): #過載 + 號運算子
return OpTest(self.a + record.a)
def __str__(self):
return "a:" + str(self.a)
a = OpTest(1)
b = OpTest(2)
print(a < b)
print (a + b)
下面 下麪列出了 Python 中常用的可過載的運算子,以及各自的含義