面向?qū)ο?/h3>
創(chuàng)建類
通過class
關鍵字定義刀崖,類名最好以大寫字母開頭仅财,舉例:
class Test:
pass
實例化
直接調(diào)用類即可實例化,舉例:
class Test: pass
t = Test()
定義屬性/方法
舉例:
class Test:
# 定義屬性a
a = 1
# 定義構(gòu)造方法
def __init__(self, b):
# 定義對象屬性b
self.b = b
# 定義對象方法m(后面會介紹幾種方法的定義)
def m(self, v):
print(self.a, self.b, v)
調(diào)用對象屬性和方法
通過.
來調(diào)用属韧,舉例:
class Test:
# 定義屬性a
a = 1
# 定義構(gòu)造方法
def __init__(self, b):
# 定義對象屬性b
self.b = b
# 定義方法m
def m(self, v):
print(self.a, self.b, v)
t = Test(2)
t.m(3)
# 1 2 3
類屬性/對象屬性
在定義類時定義的屬性被稱為類屬性安拟,在實例化的對象里定義的屬性為對象屬性,簡單來說對象在調(diào)用屬性時宵喂,會優(yōu)先調(diào)用對象屬性糠赦,如果該屬性不存在,才會去類中查找該屬性锅棕,并調(diào)用(實際上調(diào)用屬性順序遠不止這么簡單拙泽,后面會介紹),舉例:
class A:
# 定義類屬性a
a = 0
a = A()
print(a.a) # 0裸燎,對象屬性a不存在顾瞻,讀取的是類屬性a
a.a = 10 # 設置對象屬性a
print(a.a) # 10,讀取的是對象屬性
print(A.a) # 0德绿,類屬性沒有被修改
A.a = 100 # 修改類屬性
print(A.a) # 100
print(a.a) # 10荷荤,讀取的是對象屬性
del a.a # 刪除對象屬性
print(a.a) # 100,對象屬性被刪除移稳,讀取的是類屬性
注:
上面說的是簡單的情況蕴纳,實際的調(diào)用順序后面會介紹,并且之后會有相關的魔法方法个粱,如果自定義了相關魔法方法古毛,情況和默認的又不一樣了
類繼承
在類名的括號里加上父類名,且python支持多繼承關系几蜻,舉例:
class A: pass
class B: pass
# 繼承自B類
class C(B): pass
# 繼承自C和A類
class D(C, A): pass
多繼承的調(diào)用關系參照后面介紹的mro
順序喇潘,一般來說就是在非菱形繼承(多個父類繼承自另一個相同的父類)的情況下基于深度優(yōu)先体斩,在菱形繼承的情況下基于廣度優(yōu)先
所有類的基類
所有類都繼承于object
類(除了object
類,因此颖低,object
類是唯一父類為空的類)絮吵,可以通過__bases__
屬性查看一個類的所有基類(直接父類),舉例:
class A: pass
print(A.__bases__)
print(object.__bases__)
# (<class 'object'>,)
# ()
重寫
子類中可以通過重寫定義同名方法來重寫父類方法忱屑,如果希望調(diào)用父類方法蹬敲,可以通過super()
獲取父類(單繼承的情況下super()
指向父類,多繼承就未必莺戒,后面會介紹)伴嗡,然后調(diào)用,舉例:
class A:
def test(self):
print(1)
class B(A):
# 重寫父類A的test方法
def test(self):
# 調(diào)用父類A的test方法
super().test()
print(2)
b = B()
b.test()
# 1
# 2
私有屬性
默認定義的屬性和方法都是公有的从铲,如果希望改成私有的瘪校,可以就在前面加上雙下劃線__
,舉例:
class A:
__a = 1
a = A()
print(A.__a)
print(a.__a)
# 上面兩個print都會報錯:不存在__a屬性
但實際上python中沒有真正將屬性變?yōu)樗接械姆椒危厦娴膶嵸|(zhì)并不是將屬性變?yōu)樗接汹逖铮菍傩灾孛桑?code>_類名__屬性,舉例:
class A:
__a = 1
a = A()
print(A._A__a)
print(a._A__a)
# 1
# 1
可以看出該方式只是將對外暴露的屬性名給重命名了伸辟,通過新的命名方式麻惶,還是可以在外部調(diào)用該屬性。因此在python里并沒有真正的私有屬性信夫,不過該方式能夠很好的解決繼承類的屬性名沖突問題窃蹋,例如:
class A:
a = 1
class B(A):
a = 2
a = B()
print(a.a) # 2
此時在B的實例化對象里就無法獲取A中的a屬性,但是如果使用私有屬性静稻,因為屬性名會被修改成以類名開頭的格式警没,因此不會導致屬性名沖突,舉例:
class A:
__a = 1
class B(A):
__a = 2
a = B()
print(a._A__a, a._B__a) # 1 2
實例化相關方法
__new__
在對象生成之前執(zhí)行姊扔,接收當前類作為參數(shù)惠奸,可以自定義類的生成過程梅誓,舉例:
class A:
def __new__(cls):
print("創(chuàng)建對象")
obj = super().__new__(cls)
obj.a = 1
return obj
a = A()
print(a.a)
# 創(chuàng)建對象
# 1
可以看出在創(chuàng)建對象前執(zhí)行恰梢,并且可以進行一些類相關的操作,如示例中給對象添加了屬性值梗掰。由于__new__
返回處理后的類嵌言,然后根據(jù)返回的類生成對象,所以假如__new__
里不返回實例化的對象及穗,那么后面的方法如__init__
也就不會執(zhí)行了
__init__
在對象生成之后執(zhí)行摧茴,接收當前對象作為參數(shù),可以進行一些初始化操作埂陆,完善對象苛白,舉例:
class A:
def __init__(self, a):
print("初始化對象")
self.a = a
a = A(1)
print(a.a)
# 初始化對象
# 1
__init__
返回必須返回None
對象娃豹,否則會拋出TypeError
異常
python中的對象特征
- 身份:內(nèi)存地址,可以通過id函數(shù)查看购裙,代表其唯一的身份
- 類型
- 值
例如a=1
懂版,說明變量a
指向了一個地址,這個地址的數(shù)據(jù)是int
類型躏率,值為1
方法
python中支持定義對象方法躯畴、類方法、靜態(tài)方法薇芝,以及借助其他模塊定義抽象方法
對象方法
對象方法會將對象本身傳入第一個參數(shù)蓬抄,因此方法的第一個參數(shù)需要接收一個對象,舉例:
class A:
# 定義對象方法夯到,self代表對象本身
def o_m(self, x):
print(self, x)
a = A()
# 調(diào)用對象方法嚷缭,會自動將a對象傳入第一個參數(shù),等價于:A.o_m(a, 1)
a.o_m(1)
# <__main__.A object at 0x000001BCD5A396D8> 1
靜態(tài)方法
通過類調(diào)用耍贾,靜態(tài)方法不會主動接收類和對象峭状,可以理解成類中定義的函數(shù),通過@staticmethod
語法糖裝飾的方法即為靜態(tài)方法逼争,舉例:
class A:
# 定義靜態(tài)方法优床,staticmethod不加也能以靜態(tài)方式調(diào)用
@staticmethod
def s_m(x):
print(x)
# 調(diào)用靜態(tài)方法
A.s_m(1)
A().s_m(1)
# 1
# 1
注:
對象方法中能夠使用到各種實例屬性和類屬性,但前提是必須要先實例化才能調(diào)用誓焦,而有些方法希望不用實例化也可以調(diào)用胆敞,此時就可以使用靜態(tài)方法。靜態(tài)方法可以理解為在某個類命名空間下定義的函數(shù)
注2:
如果靜態(tài)方法不加上staticmethod
裝飾器杂伟,那么該方法是靜態(tài)方法還是對象方法將由調(diào)用方式來決定移层,舉例:
class A:
def t_m(x):
print(x)
A.t_m(111)
# 通過類調(diào)用則表現(xiàn)為靜態(tài)方法
A().t_m()
# 通過對象調(diào)用則表現(xiàn)為對象方法
# 111
# <__main__.A object at 0x0000024EEC3DA6A0>
所以如果以類方式去調(diào)用靜態(tài)方法,則無需加staticmethod
裝飾器赫粥。但如果加上staticmethod
裝飾器观话,那么無論是類調(diào)用還是對象調(diào)用,都表現(xiàn)為靜態(tài)方法越平,舉例:
class A:
@staticmethod
def t_m(x):
print(x)
A.t_m(111)
# 通過類調(diào)用表現(xiàn)為靜態(tài)方法
A().t_m(111)
# 通過對象調(diào)用也表現(xiàn)為靜態(tài)方法
# 111
# 111
類方法
類方法會將類本身傳入第一個參數(shù)频蛔,通過@classmethod
語法糖裝飾的方法即為類方法,舉例:
class A:
# 定義類方法秦叛,第一個參數(shù)為類本身
@classmethod
def c_m(cls, x):
print(cls, x)
# 調(diào)用類方法晦溪,等價于:A.c_m(A, 1)
A.c_m(1)
# <class '__main__.A'> 1
注:
在靜態(tài)方法或者對象方法當中假如要使用到類屬性或者類方法,那么就只能通過類.xxx
來進行調(diào)用挣跋,而這樣會帶來一個問題——當類名修改了以后三圆,靜態(tài)方法里也得跟著修改,而類方法就能解決這個問題
抽象方法
抽象方法只負責定義方法,而不負責實現(xiàn)舟肉,繼承的父類中如果含有抽象方法修噪,則子類必須實現(xiàn)該方法,常見的定義抽象方法有兩種方式:抽象方法中拋異常路媚;借助元類定義
抽象方法中拋異常
可以在定義的抽象方法當中拋出未實現(xiàn)的異常割按,若子類沒有實現(xiàn)該方法,那么在調(diào)用該方法時就會報錯磷籍,舉例:
class A():
# 定義抽象方法适荣,未實現(xiàn)則無法調(diào)用
def a_m(self):
raise NotImplementedError("未實現(xiàn)的抽象方法")
class B(A):
def a_m(self):
print(111)
class C(A): pass
B().a_m()
C().a_m()
# 111
# NotImplementedError: 未實現(xiàn)的抽象方法
可以看出C類沒有實現(xiàn)該方法,于是調(diào)用時報錯院领。但該方式實現(xiàn)的抽象方法必須在調(diào)用時才會報錯弛矛,如果希望實例化時就報錯,那么可以通過第二種方式實現(xiàn)
借助元類定義
通過abc
模塊下的abstractmethod
裝飾實現(xiàn)比然,并且定義抽象方法的類需要設置元類為ABCMeta
(元類后面會介紹)丈氓,舉例:
from abc import ABCMeta, abstractmethod
# 定義元類
class A(metaclass=ABCMeta):
# 定義抽象方法
@abstractmethod
def a_m(self):
pass
class B(A):
def a_m(self):
print(111)
class C(A): pass
B().a_m()
C()
# 111
# TypeError: Can't instantiate abstract class C with abstract methods a_m
可以看出B類中實現(xiàn)了對應的抽象方法,所以可以正常使用强法,而C類中沒有實現(xiàn)万俗,所以在實例化時就會報錯
屬性描述符
@property
可以設置對象只讀,以及設置修改對象時必須通過對應的setter
方法修改饮怯,舉例:
class Person:
def __init__(self):
self._name = "default"
self._age = 18
@property
def name(self):
return self._name
@name.setter
# name屬性的setter方法
def name(self, new_name):
print("修改name對象")
self._name = new_name
@property
# age沒有setter方法闰歪,為只讀屬性
def age(self):
return self._age
p = Person()
print(p.name, p.age)
p.name = "aaa"
print(p.name, p.age)
# p.age = 20
# age只讀,這句會報錯
# 結(jié)果:
# default 18
# 修改name對象
# aaa 18
參考:https://www.liaoxuefeng.com/wiki/1016959663602400/1017502538658208
魔法方法
可以理解為類的生命周期方法(或者說類在各種行為中的表現(xiàn)方式)蓖墅,在實例化對象中執(zhí)行特定行為時將觸發(fā)库倘,是python中的類能夠功能如此強大的重要特性,魔法方法都以雙下劃線__
開頭和結(jié)尾论矾,舉例:
class A:
# 定義對象的構(gòu)造方法
def __init__(self, *args):
print("初始化對象")
self._data = args
# 定義獲取對象長度的觸發(fā)方法
def __len__(self):
return len(self._data)
# 定義索引對象內(nèi)容時的觸發(fā)方法
def __getitem__(self, index):
return self._data[index]
# 定義對象被調(diào)用的觸發(fā)方法
def __call__(self):
print("對象被調(diào)用")
a = A(1,2,3)
print(len(a))
print(a[1])
a()
# 初始化對象
# 3
# 2
# 對象被調(diào)用
魔法方法詳細參考我的另一篇:Python 魔法方法總結(jié)
類相關內(nèi)置函數(shù)
type(obj)
獲取當前對象的所屬類教翩,舉例:
class A: pass
a = A()
print(type(a))
# <class '__main__.A'>
type
函數(shù)還可以用于創(chuàng)建類(實際上類默認就是通過type
來創(chuàng)建的),此時需要傳入三個參數(shù)贪壳,分別是:類名饱亿、基類和屬性,舉例:
def test(self): pass
A = type("A", (), {"x": 1, "y": test})
print(A)
# <class '__main__.A'>
注:
type
創(chuàng)建類是元類編程的基礎闰靴,后面會介紹
issubclass(clsA ,clsB)
判斷A類是否為B類的子類彪笼,結(jié)果返回一個布爾對象,并且第二個參數(shù)可以傳入一個元組传黄,只要第一個參數(shù)類是元組中其中一個類的子類就返回True
杰扫,舉例:
print(issubclass(dict, list))
print(issubclass(dict, (object, list)))
# False
# True
isinstance(obj , cls)
判斷一個對象是否為某個類的實例队寇,使用方法和issubclass
類似膘掰,舉例:
print(isinstance({}, list))
print(isinstance({}, (dict, list)))
# False
# True
注:
type
/isinstance
區(qū)別:type
是判斷是否為同一個類(不包括父類),而isinstance
則包括父類的判斷,舉例:
>>> class A: pass
>>> class B(A): pass
>>> isinstance(B(), B)
True
>>> isinstance(B(), A)
True
# 包括父類也能判斷
>>> type(B()) is B
True
>>> type(B()) is A
False
# 不判斷父類
hasattr(obj ,attr)
反射相關识埋,判斷對象里面是否有指定的屬性或方法凡伊,舉例:
class A:
a = 1
def b(self):
pass
print(hasattr(A(), "a"))
print(hasattr(A(), "b"))
print(hasattr(A(), "c"))
# True
# True
# False
getattr(obj ,attr [,default])
反射相關,獲取一個對象中指定屬性的值窒舟,其中可以對不存在的屬性設置默認值系忙,否則會拋出AttributeError
異常,舉例:
class A:
a = 1
def b(self):
pass
print(getattr(A(), "a"))
print(getattr(A(), "b"))
print(getattr(A(), "c", "default"))
print(getattr(A(), "c"))
# 1
# <bound method A.b of <__main__.A object at 0x00000274558496D8>>
# default
# AttributeError: 'A' object has no attribute 'c'
setattr(obj ,attr ,value)
反射相關惠豺,給對象的指定屬性賦值银还,如果屬性不存在則會創(chuàng)建該屬性,舉例:
class A:
a = 1
a = A()
setattr(a, "a", 2)
print(a.a)
setattr(a, "b", 1)
print(a.b)
# 2
# 1
delattr(obj ,attr)
反射相關洁墙,刪除對象中指定屬性蛹疯,舉例:
class A:
a = 1
def __init__(self):
self.b = 2
a = A()
print(hasattr(a, "a"))
# a是類屬性,要從類中刪除
delattr(A, "a")
print(hasattr(a, "a"))
print(hasattr(a, "b"))
# b是對象屬性热监,要從對象中刪除
delattr(a, "b")
print(hasattr(a, "b"))
property(get ,set ,del)
描述符相關捺弦,能夠定義一個屬性的get
/set
/del
操作,舉例:
class A:
def setv(self, v):
print("設置v的值")
self._v = v
def getv(self):
print("獲取v的值")
return self._v
def delv(self):
print("刪除v")
del self._v
v = property(getv, setv, delv)
a = A()
a.v = 100
print(a.v)
del a.v
# 設置v的值
# 獲取v的值
# 100
# 刪除v
super(cls, obj)
獲取指定類的父類(一般情況下孝扛,后面會說明)列吼,并將當前對象轉(zhuǎn)成指定類的對象,舉例:
class A:
def __init__(self):
self.x = 1
def test(self, y):
print("A", self.x, y)
class B(A):
def __init__(self):
self.x = 2
def test(self, y):
super(B, self).test(y)
# 獲取B的父類A苦始,并將當前對象轉(zhuǎn)成A類寞钥,從而調(diào)用A類的test方法
b = B()
b.test(2)
# A 2 2
可以看出通過super
函數(shù),對象b轉(zhuǎn)成A類的對象陌选,但屬性x
并沒有因為轉(zhuǎn)成A類而改變凑耻。
super
函數(shù)并非類中才能使用热鞍,舉例:
class A:
def __init__(self):
self.x = 1
def test(self, y):
print("A", self.x, y)
class B(A):
def __init__(self):
self.x = 2
super(B, B()).test(100)
# A 2 100
注:
在單繼承的情況下super
的確可以簡單地理解為尋找父類剂邮,但實際上其是尋找mro
(方法解析順序官帘,后面會介紹)列表中的指定類的下一個索引類惠险,詳細參考:
http://www.reibang.com/p/de7d38c84443
類對象內(nèi)置屬性
__class__
創(chuàng)建當前對象的類闸氮,舉例:
class A: pass
a = A()
print(a.__class__, A.__class__)
# <class '__main__.A'> <class 'type'>
可以看到創(chuàng)建類的類默認是type
唤蔗,這個涉及到元類編程格嗅,后面會介紹
__name__
當前類的名稱焕檬,舉例:
class A: pass
a = A()
print(A.__name__)
# A
__dict__
python自省機制實現(xiàn)的核心(自省機制:通過一定的機制查詢到對象內(nèi)部的結(jié)構(gòu))宴霸,一個類(類在python中本身也是對象)/對象所有的屬性是通過__dict__
屬性(字典對象)進行管理的囱晴,舉例:
class A:
a = 1
class B(A):
def __init__(self):
self.b = 2
b = B()
print(b.__dict__) # 類實例對象屬性
# {'b': 2}
print(B.__dict__) # 類對象屬性
# {'__module__': '__main__', ...}
print(A.__dict__)
# {'__module__': '__main__', 'a': 1, ...}
可以看出首先b中有實例屬性b,B中有類屬性一大堆瓢谢,而屬性a則在A類里畸写,當b獲取屬性a時,先尋找自身的__dict__
當中是否存在屬性a氓扛,不存在則去找B里面的枯芬,然后再去找A里面的论笔。
并且我們也可以通過__dict__
來對屬性進行增刪改之類的操作,舉例:
class A:
a = 1
class B(A):
def __init__(self):
self.b = 2
b = B()
b.__dict__["c"] = 3
print(b.c)
# 3
print(b.__dict__)
# {'b': 2, 'c': 3}
注:
可以通過dir
函數(shù)能得到對象能夠獲取到的所有屬性(__dict__
只能獲取到當前對象的千所,而通過__mro__
查找到的屬性無法展示狂魔,dir
則可以),舉例:
class A:
a = 1
class B(A):
def __init__(self):
self.b = 2
b = B()
print(b.__dict__)
# {'b': 2}
print(dir(b))
# ['__class__', '__delattr__', '__dict__', ..., 'a', 'b']
python自省機制參考:https://kb.cnblogs.com/page/87128/
__slots__
限制實例化的對象中允許有哪些屬性淫痰,在python中是默認允許對象動態(tài)添加屬性和方法的最楷,比如下面的代碼:
class A:
def __init__(self):
self.b = 1
A.c = 2
# 類A添加屬性c
a = A()
a.d = 3
# 對象a添加屬性d
print(a.b, a.c, a.d)
可以看到定義好的類A后來有給添加的屬性c
和d
,但這樣有可能會給代碼帶來一些風險待错,所以有時候我們要限制能夠添加的屬性籽孙,此時可以通過__slots__
屬性來設置,舉例:
class A:
__slots__ = ['b', 'c']
# 限制實例化的對象只能有b和c屬性
def __init__(self):
self.b = 1
a = A()
a.c = 3
# 對象添加屬性c
# a.d = 3
# 對象只能有b和c屬性火俄,因此無法添加蚯撩,這句會報錯
A.d = 2
# 類A添加屬性d,這句是可以的
print(a.b, a.c, a.d)
__bases__
能夠查看當前類的所有直接父類烛占,舉例:
>>> class A: pass
>>> A.__bases__
(<class 'object'>,)
# 默認是object(python3下不指定基類胎挎,默認繼承object)
>>> class B(A): pass
>>> B.__bases__
(<class '__main__.A'>,)
>>> class C(B): pass
>>> C.__bases__
(<class '__main__.B'>,)
__bases__
只列出直接父類,如果希望查看所有的父類(包括間接的)忆家,可以通過__mro__
屬性查看
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.__bases__)
print(D.__mro__)
# (<class '__main__.B'>, <class '__main__.C'>)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
還有__base__
屬性可以查看其super
指向的類犹菇,舉例:
class A:pass
class B(A):pass
class C(A):pass
class D(B, C):pass
print(D.__bases__)
print(D.__base__)
# (<class '__main__.B'>, <class '__main__.C'>)
# <class '__main__.B'>
__subclasses__
查看該類的所有子類,舉例:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(A.__subclasses__())
# [<class '__main__.B'>, <class '__main__.C'>]
可以發(fā)現(xiàn)該方法只能查看到該類的直接子類芽卿,如果想要查看所有子類揭芍,可以封裝如下:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E(D): pass
def get_all_subclass(c):
queue = [c]
res = []
while queue:
c = queue.pop(0)
sub_cs = c.__subclasses__()
for sub_c in sub_cs:
if not sub_c in res:
queue.append(sub_c)
res.append(sub_c)
return res
print(get_all_subclass(A))
# [<class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.E'>]
注:
通過dir(類)
會發(fā)現(xiàn)__subclasses__
、__base__
等屬性并不在里面卸例,那為何類能使用這些屬性呢称杨?因為所有的類最終都是基于type
類(或者說對象)來創(chuàng)建的,我們可以查看type
屬性如下:
print(dir(type))
# ['__abstractmethods__', '__base__', '__bases__', ..., '__subclasscheck__', '__subclasses__', ..., 'mro']
可以發(fā)現(xiàn)這些屬性都是存在于type
類中的筷转,而所有的類可以認為是type
類創(chuàng)建的對象姑原,因此對象里自然能夠使用這些方法
__module__
查看類所在模塊,舉例:
class A: pass
print(A.__module__, type(A.__module__))
# __main__ <class 'str'>
但可以看出該方式獲取的是字符串的模塊名呜舒,如果希望獲取模塊對象本身锭汛,可以通過__import__
函數(shù)動態(tài)導入模塊,或者從sys.modules
(存放已載入模塊的字典對象)中獲取已載入的模塊袭蝗,舉例:
import sys
class A: pass
# 通過動態(tài)導入獲取模塊
print(__import__(A.__module__))
# 從已載入模塊的字典中找尋模塊
print(sys.modules.get(A.__module__))
# <module '__main__' from 'xxx:\\test.py'>
# <module '__main__' from 'xxx:\\test.py'>
__annotations__
函數(shù)/類標注唤殴,指定期望的參數(shù)類型以及返回值類型,舉例:
class A:
aaa: int = 1
def test(aaa: str, bbb:int = 1) -> list:
pass
print(test.__annotations__)
print(A.__annotations__)
# {'aaa': <class 'str'>, 'bbb': <class 'int'>, 'return': <class 'list'>}
# {'a': <class 'int'>, 'return': <class 'str'>}
相關概念
鴨子類型
當一只鳥走起來像鴨子到腥、游泳起來像鴨子朵逝、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子
什么意思呢乡范?例如在Python里配名,我可以實現(xiàn)多個類啤咽,只要他們都實現(xiàn)了相同的方法,那么不論換哪個段誊,都能正常使用闰蚕,舉例:
class Bird:
def walk(self):
print("Bird walk...")
class Duck:
def walk(self):
print("Duck walk...")
duck = None
for animal in [Bird, Duck]:
duck = animal()
duck.walk()
這里duck可以變成鳥栈拖,也能變成鴨子连舍,并且使用對應的方法,因為沒有像靜態(tài)語言中的類型檢查涩哟,他不論變成哪個類的實例都沒問題索赏,只要實現(xiàn)了需要的方法,就能夠被正常調(diào)用
而在靜態(tài)語言里(如Java)贴彼,鳥就是鳥潜腻,鴨子就是鴨子,他們兩就算是功能一模一樣器仗,也無法進行轉(zhuǎn)換融涣,更無法調(diào)用,舉例:
public class Test {
public static void main(String[] args) {
Duck duck = new Duck();
duck.walk();
// duck = new Bird();
// 這句會報錯精钮,因為他們兩個類不同威鹿,無法直接轉(zhuǎn)換
}
}
class Bird {
public void walk() {
System.out.println("Bird walk...");
}
}
class Duck {
public void walk() {
System.out.println("Duck walk...");
}
}
所以此時必須要他們繼承自同一個類/或者接口,然后使用父類/接口實例化轨香,從而才能轉(zhuǎn)換忽你,舉例:
public class Test {
public static void main(String[] args) {
Animal duck = new Duck();
// 使用同一個接口實例化,才能成功轉(zhuǎn)換
duck.walk();
duck = new Bird();
duck.walk();
}
}
interface Animal {
public void walk();
}
class Bird implements Animal{
public void walk() {
System.out.println("Bird walk...");
}
}
class Duck implements Animal {
public void walk() {
System.out.println("Duck walk...");
}
}
因此python的多態(tài)可以很容易的實現(xiàn)(動態(tài)類型的優(yōu)勢)臂容,假如我們有多個類科雳,他們實現(xiàn)了相同的方法,那么我們可以很容易的隨意修改這個對象指向這幾個類脓杉,從而調(diào)用方法
再舉個例子糟秘,例如list的extend方法需要傳入的是一個可迭代的對象(會隱式調(diào)用該對象的迭代器),因此該方法的傳入對象不一定非得傳入一個list或者什么指定類球散,而是只要實現(xiàn)了可迭代方法的類都可以蚌堵,舉例:
li = []
s = {1,2,3}
li.extend(s)
# s雖然是集合,但作為可迭代對象沛婴,因此可以傳入
說白了吼畏,python中的一個類,他可以有多重身份嘁灯,例如實現(xiàn)了迭代器相關的魔法方法泻蚊,他就可以是一個迭代器,實現(xiàn)了enter和exit丑婿,他就可以是一個上下文管理器性雄,而且這些身份可以兼有
而在靜態(tài)語言當中没卸,基本上就是指定了傳入的對象類型
面向?qū)ο筮M階部分
類的執(zhí)行機制
和函數(shù)的區(qū)別
類和函數(shù)在定義上有一點很重要的不同就是:函數(shù)只有在調(diào)用了以后才會開始執(zhí)行函數(shù)里面的語句,而類在調(diào)用前就已經(jīng)執(zhí)行了初始化定義語句分配好了內(nèi)存(實際上是編譯時會創(chuàng)建類對象秒旋,并對類對象進行一些初始化操作)约计,比如下面的定義了一個函數(shù)和類:
def a():
print(1)
class A():
print(2)
def __init__(self):
print(3)
if __name__ == '__main__':
pass
# 2
可以看出類還未實例化就已經(jīng)執(zhí)行了創(chuàng)建類對象的相關語句,即:類已經(jīng)完成了初始化定義和內(nèi)存分配
類的執(zhí)行順序
不管有沒調(diào)用類迁筛,只要定義了以后煤蚌,就會默認執(zhí)行一些初始化語句,所以類在程序中的執(zhí)行順序為:類里的最外層語句(初始化定義語句)>__new__
>__init__
细卧,比如下面的類:
print(0)
class A():
print(1)
def __init__(self):
print(4)
print(2)
if __name__ == '__main__':
print(3)
a = A()
結(jié)果為:
0
1
2
3
4
python解釋器是逐行執(zhí)行尉桩,所以類外部的執(zhí)行順序和類無關,只看執(zhí)行的語句在哪一行
通過類/對象調(diào)用方法的區(qū)別
通過類調(diào)用方法和對象調(diào)用方法的主要區(qū)別就是:通過類調(diào)用時贪庙,相當于直接調(diào)用一個函數(shù)蜘犁,而通過對象調(diào)用時,會默認第一個參數(shù)傳入對象本身(類方法則傳入類本身)止邮,因此obj.method(...)
等價于:cls.method(obj, ...)
这橙,舉例:
class A:
def test(self, x):
print(self, x)
a = A()
a.test(1)
A.test(a, 1)
# <__main__.A object at 0x00000229993F96D8> 1
# <__main__.A object at 0x00000229993F96D8> 1
類繼承時的方法解析順序(MRO)
新式類和經(jīng)典類
如果一個類或者他的父類繼承了object
類則是新式類,否則就是經(jīng)典類导披,在python3之后都是新式類(新式類如果不設置繼承父類屈扎,默認繼承object
類),而對于類繼承時的方法解析順序盛卡,在經(jīng)典類中采用深度優(yōu)先(因為經(jīng)典類沒有要求必須繼承object
類助隧,所以可以采用深度優(yōu)先,像新式類因為必須繼承object
類滑沧,則不適合采用深度優(yōu)先并村,否則對于object
擁有的方法,多繼承的情況下就不太可能輪到別的類上進行查詢)滓技,而python3中采用C3算法遍歷繼承的父類關系
C3算法
簡單來說:對于有相同父類的繼承關系哩牍,采用廣度優(yōu)先;無相同父類的繼承關系令漂,采用深度優(yōu)先膝昆,例如一個繼承關系如下的類:
object
|
/ \
A E
/ \ |
B C |
\ / |
D F
\ /
X
可以看出B
和C
有相同父類,而D
和F
無相同父類叠必,因此B
和C
之間采用廣度優(yōu)先荚孵,B
和C
之間采用深度優(yōu)先,打印其mro
關系纬朝,如下:
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
class E: pass
class F(E): pass
class X(D, F): pass
for cls in X.__mro__:
print(cls)
# <class '__main__.X'>
# <class '__main__.D'>
# <class '__main__.B'>
# <class '__main__.C'>
# <class '__main__.A'>
# <class '__main__.F'>
# <class '__main__.E'>
# <class 'object'>
遍歷過程
按照深度優(yōu)先遍歷收叶,如果有相同的父類,則需先遍歷完子類再遍歷父類共苛,如果遇到重復的父類判没,則只保留最后一個蜓萄,因此上面的示例中,計算過程如下:
- 首先澄峰,對于
D
和F
類進行深度優(yōu)先嫉沽,因此先遍歷D
類:X->D
- 由于D類的父類當前也是多重繼承,并且
B
和C
類擁有共同父類A
俏竞,因此也是先遍歷B
類:B
- 然后在遍歷
C
類绸硕,遍歷完成即可遍歷父類A
:C->A
- 最后遍歷
F
類:F->E->object
- 將之前遍歷的結(jié)果合起來就是:
X->D->B->C->A->F->E->object
參考:
https://www.cnblogs.com/bashaowei/p/8508276.html
https://blog.csdn.net/u011467553/article/details/81437780
https://hanjianwei.com/2013/07/25/python-mro/
http://www.chinaoc.com.cn/p/1196683.html
super調(diào)用順序
前面介紹的super
函數(shù)實際上就是基于mro
順序,獲取下一個類的內(nèi)容胞此,而并非單純的找自身父類臣咖,舉例:
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
print("B")
super().__init__()
class C(A):
def __init__(self):
print("C")
super().__init__()
class D(B, C):
def __init__(self):
print("D")
super().__init__()
d = D()
print(D.__mro__)
# D
# B
# C
# A
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
可以看出當D的super返回的是B跃捣,而B的父類是A漱牵,但其super卻是C,也就是mro的下一個疚漆,最后才是A
None對象
在解釋器啟動時酣胀,會通過None類型生成一個None對象,全局只有一個娶聘,舉例:
>>> type(None)
<class 'NoneType'>
# 可以看出是NoneType類生成的對象
>>> None is None
True
# 可以看出None對象是唯一的
上面的例子為什么能說明None唯一(很簡單闻镶,因為對象的id是唯一的,不過如果覺得前面講的有點繞丸升,可以繼續(xù)看下面解釋):
首先實例化的對象铆农,肯定是互相不同的(因為新分配了空間,id肯定唯一)狡耻,舉例:
>>> class A: pass
>>> id(A())
2396138626464
>>> id(A())
2396138625624
# 可以看出兩次實例化的地址不同
而None
作為NoneType
類的對象墩剖,必然每次實例化的id也不相同,但這里卻相同夷狰,所以也就說明了其唯一
這里可以再說一下類岭皂,類本身也是對象,但類的id會發(fā)現(xiàn)也唯一沼头,舉例:
>>> class A: pass
>>> A is A
True
這也可以看出解釋器在最開始就會通過type
實例化生成各種類對象爷绘,并且這些類對象都是唯一的
注:
由于python內(nèi)部的優(yōu)化機制,一些簡單的整數(shù)进倍、字符串等都會先預分配id土至,所以對于一些簡單的數(shù)據(jù),可能實例化多個的id是一樣的猾昆,舉例:
>>> id(int(1))
1660742096
>>> id(int(1))
1660742096
而None
其實也屬于被優(yōu)化的一部分
抽象方法實現(xiàn)原理
這里如果分析源碼陶因,可以發(fā)現(xiàn)該方式就是首先給綁定了abc.abstractmethod
裝飾器的方法添加一個__isabstractmethod__
屬性,并且值為True
毡庆,源碼如下:
def abstractmethod(funcobj):
funcobj.__isabstractmethod__ = True
return funcobj
然后當實例化時坑赡,通過元類獲取當前類以及所有父類的抽象方法名(即獲取所有存在__isabstractmethod__
屬性且值為True
的方法烙如,因為通過反射獲取方法時,假如子類實現(xiàn)了該方法毅否,那么該方法默認是不存在__isabstractmethod__
屬性的亚铁,假如子類沒有實現(xiàn)該方法,那么就會去父類中找該方法螟加,而父類中該方法存在__isabstractmethod__
屬性徘溢,并且值為True
),并保存到類的__abstractmethods__
屬性當中捆探,而解釋器則會讀取這個屬性然爆,并報出對應錯誤信息,下面是加注釋的源碼:
def __new__(mcls, name, bases, namespace):
# 先生成對應的類對象
cls = super().__new__(mcls, name, bases, namespace)
# 獲取類對象的抽象方法(存在屬性__isabstractmethod__且值為True的方法)
abstracts = {name
for name, value in namespace.items()
if getattr(value, "__isabstractmethod__", False)}
for base in bases:
# 獲取所有父類中的抽象方法名
for name in getattr(base, "__abstractmethods__", set()):
# 獲取當前類對象的該方法
value = getattr(cls, name, None)
# 如果當前類的該方法的__isabstractmethod__屬性值為True黍图,則說明是從父類中繼承來的抽象方法曾雕,那么加入到當前類的抽象方法里
# 如果不為True,說明當前類或者父類里重寫了對應的方法助被,因為方法被重寫剖张,因此__isabstractmethod__屬性也就不存在了
if getattr(value, "__isabstractmethod__", False):
abstracts.add(name)
# 將當前類的所有抽象方法加入到__abstractmethods__屬性當中
cls.__abstractmethods__ = frozenset(abstracts)
# 弱引用相關操作...
cls._abc_registry = WeakSet()
cls._abc_cache = WeakSet()
cls._abc_negative_cache = WeakSet()
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
return cls
通過上面的分析,我們可以發(fā)現(xiàn)python主要根據(jù)屬性__abstractmethods__
來判斷當前類的抽象方法揩环,所以根據(jù)抽象方法的實現(xiàn)原理搔弄,我們也可以這樣實現(xiàn):
class AnimalBase:
# 往類屬性__abstractmethods__當中指定當前類的抽象方法
__abstractmethods__ = frozenset({"walk", "eat"})
def __new__(cls):
# 定義一個集合存放所有當前類的抽象方法
abstract = set()
for base in cls.__mro__:
# 遍歷當前類及父類的所有抽象方法
for abcmethod in getattr(base, "__abstractmethods__", set()):
# 如果父類的抽象方法在當前類中沒有,則添加到當前類的抽象方法當中
if not abcmethod in cls.__dict__:
abstract.add(abcmethod)
# 設置當前類的__abstractmethods__屬性丰滑,注意是當前類顾犹,而不是當前類實例化的對象,因為該屬性是綁定在類上的
cls.__abstractmethods__ = frozenset(abstract)
return super().__new__(cls)
class Duck(AnimalBase):
pass
duck = Duck()
注:
__abstractmethods__
是一個類屬性(不是類實例化后對象的屬性)褒墨,用于聲明當前類的所有抽象方法
抽象基類
存在抽象方法的類即為抽象基類炫刷,抽象基類不能直接實例化,主要用于制定一些規(guī)范貌亭。
內(nèi)置抽象基類
假如我們希望自定義一個集合類型的類柬唯,例如類似tuple
的類,那么我們就需要實現(xiàn)集合相關的魔法方法圃庭,如:__len__
/__iter__
/__contains__
/锄奢,此時如果我們要判斷這個類符不符合序列類的要求,可能就會進行這樣的判斷:
class A: pass
def is_sequence(cls):
return hasattr(cls, "__len__") and hasattr(cls, "__iter__") and hasattr(cls, "__contains__")
# 判斷是否實現(xiàn)了序列類的所有魔法方法
print(is_sequence(A))
這樣需要我們進行一系列的判斷剧腻,編寫判斷條件也十分的麻煩拘央。而在collections.abc
模塊下提供了很多內(nèi)置數(shù)據(jù)類型結(jié)構(gòu)的抽象基類,能夠更加簡單地幫我們判斷是否為某一種數(shù)據(jù)類型书在,例如前面判斷集合類的代碼就能夠改成:
from collections.abc import Collection
class A: pass
print(issubclass(A, Collection))
而實際上這些基類也是通過重寫了issubclass
行為的魔法方法灰伟,這里就拿集合的源碼來分析:
class Collection(Sized, Iterable, Container):
__slots__ = ()
@classmethod
def __subclasshook__(cls, C):
if cls is Collection:
return _check_methods(C, "__len__", "__iter__", "__contains__")
# _check_methods檢查指定類的父類當中是否存在這些方法,源碼放在下面
return NotImplemented
def _check_methods(C, *methods):
# 基于mro順序遍歷父類方法,查看指定的方法是否實現(xiàn)
mro = C.__mro__
for method in methods:
for B in mro:
if method in B.__dict__:
if B.__dict__[method] is None:
return NotImplemented
break
else:
return NotImplemented
return True
可以看出其內(nèi)部實現(xiàn)了__subclasshook__
魔法方法來進行子類判斷栏账,所以只要實現(xiàn)了指定的魔法方法帖族,就是當前基類的子類
collections.abc
模塊下提供的這些抽象基類除了前面的用處以外,也讓我們能夠更好地參考其提供的所有內(nèi)置基本數(shù)據(jù)類型結(jié)構(gòu)挡爵,下面是加注釋的源碼一覽:
__all__ = [
"Awaitable",
# await語法后面必須接的awaitable對象基類
"Coroutine",
# 協(xié)程基類
"AsyncIterable",
# 異步可迭代對象基類
"AsyncIterator",
# 異步迭代器基類竖般,如使用async for語法必須實現(xiàn)該基類的方法
"AsyncGenerator",
# 異步生成器基類
"Hashable",
# 可哈希的對象基類,只有可哈希的對象才能存入像集合茶鹃、字典的key當中
"Iterable",
# 可迭代的對象基類
"Iterator",
# 迭代器基類
"Generator",
# 生成器基類
"Reversible",
# 可逆序的基類(可以使用reversed方法)
"Sized",
# 存在size長度的基類(可以使用len方法)
"Container",
# 可判斷是否存在某元素的基類(可以使用in語法)
"Callable",
# 可以當函數(shù)調(diào)用的基類
"Collection",
# 集合基類(這個集合代表可以存放多個元素涣雕,并且能迭代、獲取尺寸闭翩、判斷是否存在)
"Set",
# 不可變集合的基類挣郭,如frozenset
"MutableSet",
# 可變集合的基類,如set
"Mapping",
# 不可變映射的基類疗韵,如MappingProxyType
"MutableMapping",
# 可變映射的基類兑障,如dict
"MappingView",
# 可獲取映射類長度的基類,使映射類可以使用(len)方法
"KeysView",
# 可以迭代key的基類伶棒,例如使映射類可以使用keys()方法
"ItemsView",
# 可以迭代key, value的基類旺垒,例如使映射類可以使用items()方法
"ValuesView",
# 可以迭代value的基類彩库,例如使映射類可以使用values()方法
"Sequence",
# 不可變序列的基類肤无,如tuple
"MutableSequence",
# 可變序列的基類,如list
"ByteString",
# 字節(jié)數(shù)據(jù)相關的基類(實際上就是繼承了不可變序列類)骇钦,如bytes
]
property動態(tài)屬性
通過property
裝飾器宛渐,我們可以設置動態(tài)屬性,使得一個屬性可以動態(tài)計算:
class A:
def __init__(self, birth, year):
self._birth = birth
self._year = year
@property
def age(self):
return self._year - self._birth
a = A(2000, 2020)
print(a.age)
# 20
可以看到我們訪問age
屬性眯搭,是通過指定age
方法獲得的窥翩,因此使用property
使我們能夠?qū)⒁粋€屬性通過方法來計算獲取
描述符
屬性描述符
通過描述符相關的魔法方法(__set__
/__get__
/__delete__
),可以定義對一個屬性讀/寫/刪操作的行為鳞仙,例如通過屬性描述符實現(xiàn)類型校驗:
class IntField:
def __get__(self, instance, owner):
return self.val
def __set__(self, instance, val):
# 對age字段賦值時進行校驗
if not type(val) is int:
raise TypeError
self.val = val
def __delete__(self, instance):
self.val = None
class People:
age = IntField()
p = People()
p.age = 100
p.age = "dasdas"
# TypeError
數(shù)據(jù)描述符/非數(shù)據(jù)描述符
- 數(shù)據(jù)描述符:實現(xiàn)了
__get__
和__set__
方法 - 非數(shù)據(jù)描述符:只實現(xiàn)了
__get__
方法
舉例:
class DataField:
# 數(shù)據(jù)描述符
def __get__(self, instance, owner):
return self.val
def __set__(self, instance, val):
self.val = val
class NonDataField:
# 非數(shù)據(jù)描述符
def __get__(self, instance, owner):
return "aaa"
兩者除了一個可以進行操作寇蚊,一個不行以外,在屬性的查找順序上也存在區(qū)別
調(diào)用屬性執(zhí)行順序
調(diào)用屬性時棍好,會先調(diào)用__getattribute__
方法(之后可能會調(diào)用的__get__
/__getattr__
等方法都是在該方法的處理邏輯當中調(diào)用的)
例如調(diào)用的屬性不存在仗岸,此時如果定義了__getattr__
方法,就會調(diào)用該方法借笙,否則拋出AttributeError
錯誤
完整邏輯如下:
- 如果屬性是在其類對象或者基類的
__dict__
屬性當中扒怖,并且屬性是數(shù)據(jù)描述符,那么將調(diào)用__get__
方法业稼,否則進入2 - 如果屬性是在當前實例對象的
__dict__
中盗痒,則直接返回__dict__[attr]
,否則進入3 - 如果屬性是在其類對象或者基類的
__dict__
屬性當中低散,并且屬性是非數(shù)據(jù)描述符俯邓,則調(diào)用其__get__
方法骡楼,否則進入4 - 如果屬性是在其類對象或者基類的
__dict__
屬性當中,并且屬性不是描述符(沒有實現(xiàn)__get__
方法)稽鞭,則返回__dict__[attr]
君编,否則進入5 - 進入5則說明該屬性不存在,此時如果當前類實現(xiàn)了
__getattr__
方法川慌,則調(diào)用該方法吃嘿,否則進入6 - 拋出
AttributeError
錯誤
舉例:
class DataField:
# 數(shù)據(jù)描述符
def __get__(self, instance, owner):
print("data...")
return 100
def __set__(self, instance, val):
self.val = val
class NonDataField:
# 非數(shù)據(jù)描述符
def __get__(self, instance, owner):
print("nondata...")
return "aaa"
class People:
name = NonDataField()
age = DataField()
def __init__(self):
self.name = "bbb"
self.age = 0
p = People()
print(p.name, p.age)
# data...
# bbb 100
可以看到這里定義了一個People
類,里面的name
和age
屬性分別是非數(shù)據(jù)描述符和數(shù)據(jù)描述符梦重,然后實例化的時候又定義了name
和age
屬性兑燥。
在調(diào)用這兩個屬性的時候,因為People
類的__dict__
當中存在數(shù)據(jù)描述符age
琴拧,因此根據(jù)上面查找的第一步降瞳,調(diào)用了__get__
方法,返回了屬性值100
蚓胸;而name
作為非數(shù)據(jù)描述符挣饥,且在當前的實例對象當中也有name
屬性,因此根據(jù)上面查找的第二步沛膳,返回了實例對象的name
屬性值bbb
元類編程
元類
元類就是能夠創(chuàng)建類的類扔枫,而type
函數(shù)能夠創(chuàng)建類(需要傳遞三個參數(shù):類名/基類/屬性值):
>>> Test = type('Test', (object,), {'a':1})
# 創(chuàng)建名為Test,繼承于object類锹安,存在屬性a的值為1的類
>>> test = Test()
>>> test.a
1
上面的創(chuàng)建方式等價于:
class Test(object):
a = 1
因此type
本身就是一個元類短荐,而我們編寫的類,只要其繼承type
叹哭,就是一個元類忍宋,通過元類,我們能夠控制類實例化的過程(當一個類沒有設置元類時风罩,會默認使用type
來創(chuàng)建該類)
實際上class
語法本質(zhì)上也就是相當于調(diào)用了type
方法進行類的創(chuàng)建糠排。而元類則可以理解成基于type
方式創(chuàng)建對象的類,當在類當中指定元類以后超升,將會調(diào)用元類的方法入宦,并在元類創(chuàng)建類時通過修改傳入的類名、繼承類廓俭、屬性等方式來達到動態(tài)修改類的目的云石,舉例:
class AddAttrMetaClass(type):
def __new__(meta_class, class_name, class_parents, class_attrs):
class_attrs['add_attr'] = '在元類里添加的屬性'
print("創(chuàng)建新類,類名:{}, 繼承父類:{}, 存在屬性:{}".format(class_name, class_parents, class_attrs))
return type.__new__(meta_class, class_name, class_parents, class_attrs)
class Test(metaclass = AddAttrMetaClass):
pass
test = Test()
# 結(jié)果:
# 創(chuàng)建新類研乒,類名:Test, 繼承父類:(), 存在屬性:{'__module__': '__main__', '__qualname__': 'Test', 'add_attr': '在元類里添加的屬性'}
可以看出上面代碼中Test
類指定了AddAttrMetaClass
為其元類汹忠,因此在創(chuàng)建類時會先執(zhí)行AddAttrMetaClass
里的__new__
方法,該方法通過修改class_attrs
屬性,從而達到修改創(chuàng)建的Test
類中含有的屬性宽菜。
動態(tài)創(chuàng)建類的方式
- 在函數(shù)內(nèi)部定義類(python解釋器在初始化時只是聲明函數(shù)谣膳,而函數(shù)內(nèi)部的類則是在函數(shù)執(zhí)行時的對應棧幀內(nèi)創(chuàng)建,因此可以看做是一種動態(tài)地創(chuàng)建類)铅乡,通過函數(shù)創(chuàng)建:
def create_class(cls):
class A: pass
class B: pass
di = {
"A": A,
"B": B
}
if cls in di:
return di[cls]
return None
a = create_class("A")()
print(a)
# <__main__.create_class.<locals>.A object at 0x000001D83A2294A8>
- 通過
type
創(chuàng)建继谚,舉例:
def y(self):
print(self.x)
A = type("A", (), {"x": 1, "y": y})
# 創(chuàng)建A類
a = A()
# 實例化A類
a.y()
# 調(diào)用a對象的y方法
print(A)
# 1
# <class '__main__.A'>
類的實例化過程
python解釋器在啟動時,會先創(chuàng)建所有唯一的類對象阵幸,而這些類對象就是通過元類來進行創(chuàng)建花履,例如下面代碼:
class M(type):
def __new__(cls, name, bases, attrs):
print(1)
return super().__new__(cls, name, bases, attrs)
class A(metaclass=M):
def __new__(cls):
print(3)
return super().__new__(cls)
print(2)
a = A()
a = A()
# 1
# 2
# 3
# 3
可以看出因為A
指定了元類M
,因此在解釋器啟動時挚赊,通過元類M
創(chuàng)建了類對象A诡壁,因此輸出1,然后才開始執(zhí)行我們的主流程語句荠割,輸出2妹卿,之后再生成A的實例化對象時,只是對A
的類進行實例化蔑鹦,沒有創(chuàng)建類對象的操作夺克,因此不再輸出1,而是執(zhí)行A
的實例化方法嚎朽,輸出3
元類中__call__
的作用
類都是通過的__call__
創(chuàng)建的铺纽,因此顯然__call__
的本質(zhì)是創(chuàng)建類,也就是調(diào)用類的__new__
和__init__
方法火鼻,舉例:
class A(type):
def __init__(self, object_or_name, bases, dict):
super().__init__(object_or_name, bases, dict)
print(1)
def __new__(cls, name, base, attr):
print(0)
return super().__new__(cls, name, base, attr)
def __call__(self, *args, **kwargs):
print(2)
return super().__call__(*args, **kwargs)
class B(metaclass=A):
def __new__(cls):
print(3)
return super().__new__(cls)
def __init__(self):
print(4)
b = B()
print(b)
# 0
# 1
# 2
# 3
# 4
# <__main__.B object at 0x00000246D5B2F668>
可以發(fā)現(xiàn)如果把元類的__call__
方法最后一行注釋室囊,則不會輸出3
和4
,即不會創(chuàng)建B
類了
type
type本身也是一個類魁索,但他同時也是一個對象(自身的實例),因此可以看到type的基類是object盼铁,但是object又是由type生成粗蔚,而type函數(shù)則是查看創(chuàng)建該類的類,舉例:
>>> type(object)
<class 'type'>
# 創(chuàng)建object的類是type
>>> type(type)
<class 'type'>
# 創(chuàng)建type的類也是type
>>> type.__bases__
(<class 'object'>,)
# type也是繼承于object
>>> class A(type): pass
>>> class B(metaclass=A): pass
>>> type(B)
<class '__main__.A'>
# B的元類是A饶火,所以創(chuàng)建B的類是A
因為一切都是對象鹏控,而對象很容易修改,所以python中的類即使定義好了也可以動態(tài)修改(猴子補丁之類的)肤寝,因為可以理解為修改的是type生成的對象
type/object/類之間關系
在Python中当辐,一切皆對象,而type
能夠生成類(因此默認情況下類是type
的對象)鲤看,對象本身也是類的對象缘揪,而type
則是自身的對象,舉例:
>>> class A:pass
>>> a = A()
>>> type(a)
<class '__main__.A'>
# 生成a的類是A
>>> type(A)
<class 'type'>
# 可以看出生成A的類是type
>>> type(A) is type
True
>>> type(object)
<class 'type'>
# 生成object的類是type
>>> type(type)
<class 'type'>
# 生成type的類也是type
>>> b = 1
>>> type(b)
<class 'int'>
>>> type(int)
<class 'type'>
# 可以看出一般情況下,類都是type的對象
但實際上找筝,類都是其元類的對象蹈垢,舉例:
>>> class B():pass
>>> class C(B):pass
>>> type(C)
<class 'type'>
# 可以看出C是type的對象
>>> class B(type):pass
# 元類需要繼承自type類
>>> class C(metaclass=B):pass
>>> type(C)
<class '__main__.B'>
# 可以看出這次C是元類B的對象
因此一個類如果不指定元類,默認元類就是type
元類實現(xiàn)orm
很多ORM框架都是基于元類來實現(xiàn)的袖裕,這里我們簡單模擬一下Django中的ORM操作:
class BaseField:
"""字段基類曹抬,實現(xiàn)一些公共方法"""
def __get__(self, instance, owner):
return self._val
def __set__(self, instance, val):
raise NotImplementedError
def _check_number(self, val):
"""檢查是否為數(shù)字"""
if not val is None:
if not issubclass(type(val), (int, float)):
raise TypeError
return True
else:
return False
def _check_str(self, val):
"""檢查是否為字符串"""
if not val is None:
if not issubclass(type(val), str):
raise TypeError
return True
else:
return False
class NumberField(BaseField):
"""數(shù)字校驗字段類"""
def __init__(self, col_name, min_val=None, max_val=None):
self._val = None
self.col_name = col_name
self.min_val = min_val
self.max_val = max_val
self._check_number(min_val)
self._check_number(max_val)
def __set__(self, instance, val):
if not self._check_number(val):
raise ValueError("need number")
if (not self.min_val is None) and val < self.min_val:
raise ValueError("below the min_val")
if (not self.max_val is None) and val > self.max_val:
raise ValueError("over the max_val")
self._val = val
class CharField(BaseField):
"""字符串校驗字段類"""
def __init__(self, col_name, max_len=None):
self._val = None
self.col_name = col_name
self.max_len = max_len
self._check_number(max_len)
def __set__(self, instance, val):
if not self._check_str(val):
raise ValueError("need str")
if (not self.max_len is None) and len(val) > self.max_len:
raise ValueError("over the max_len")
self._val = val
class ModelMeta(type):
"""model生成元類"""
def __new__(cls, name, bases, attrs):
if name == "BaseModel":
return super().__new__(cls, name, bases, attrs)
fields = {}
# 存儲所有字段屬性
for k, v in attrs.items():
# 非Meta和以下劃線開頭的屬性都添加進字段
if not k.startswith("_") and k != "Meta":
fields[k] = v
_meta = attrs.get("Meta")
table_name = name.lower()
# meta類處理
if not _meta is None:
meta_table_name = getattr(_meta, "table_name", None)
if not meta_table_name is None:
table_name = meta_table_name
del attrs["Meta"]
# 將字段相關屬性設置到類當中
attrs["table_name"] = table_name
attrs["_meta"] = _meta
attrs["fields"] = fields
return super().__new__(cls, name, bases, attrs)
class BaseModel(metaclass=ModelMeta):
def __init__(self, *args, **kwargs):
# 初始化將傳入的配置注入到屬性當中
for k, v in kwargs.items():
setattr(self, k, v)
super().__init__()
def save(self):
"""生成插入語句邏輯"""
fields = []
values = []
for k, v in self.fields.items():
col = v.col_name
val = getattr(self, k, "")
fields.append(col)
values.append(val)
if len(fields) < 1:
raise AttributeError
sql = "insert {}({}) value({});".format(self.table_name, str(fields)[1:-1].replace("'", ""), str(values)[1:-1])
print(sql)
class People(BaseModel):
age = NumberField(col_name="age", min_val=1, max_val=100)
name = CharField(col_name="name", max_len=10)
class Meta:
table_name = "tb_people"
people = People(name="aaa", age=30)
people.save()
# insert tb_people(age, name) value(30, 'aaa');
元類實現(xiàn)單例模式
懶漢式
懶漢式只需要在實例化該類時再創(chuàng)建對應的實例,因此直接在__new__
方法里控制單例的創(chuàng)建即可急鳄,舉例:
import threading
class A:
ins = None
lock = threading.Lock()
def __new__(cls):
# 如果已經(jīng)創(chuàng)建就直接返回ins
if cls.ins is None:
with cls.lock:
# 假如多個線程同時創(chuàng)建對象谤民,那么可能在同一時間,對應的ins都是none疾宏,
# 因此都能進入這里施绎,所以這里需要再進行一次判斷是否ins已經(jīng)創(chuàng)建
if cls.ins is None:
cls.ins = super().__new__(cls)
return cls.ins
a = A()
b = A()
print(a is b)
# True
餓漢式
餓漢式需要在一開始就創(chuàng)建好對應的對象,因此我們可以在元類當中控制——當創(chuàng)建類時就實例化當前類的對象厨姚,舉例:
class Meta(type):
def __new__(cls, object_or_name, bases, dict):
cls = super().__new__(cls, object_or_name, bases, dict)
# 在創(chuàng)建類時創(chuàng)建實例
cls.ins = cls()
return cls
class A(metaclass=Meta):
def __new__(cls):
if not getattr(cls, "ins", None):
cls.ins = super().__new__(cls)
return cls.ins
print(A.ins)
print(A() is A())