Python 面向?qū)ο?/h1>

面向?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后來有給添加的屬性cd,但這樣有可能會給代碼帶來一些風險待错,所以有時候我們要限制能夠添加的屬性籽孙,此時可以通過__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

可以看出BC有相同父類,而DF無相同父類叠必,因此BC之間采用廣度優(yōu)先荚孵,BC之間采用深度優(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)先遍歷收叶,如果有相同的父類,則需先遍歷完子類再遍歷父類共苛,如果遇到重復的父類判没,則只保留最后一個蜓萄,因此上面的示例中,計算過程如下:

  1. 首先澄峰,對于DF類進行深度優(yōu)先嫉沽,因此先遍歷D類:X->D
  2. 由于D類的父類當前也是多重繼承,并且BC類擁有共同父類A俏竞,因此也是先遍歷B類:B
  3. 然后在遍歷C類绸硕,遍歷完成即可遍歷父類AC->A
  4. 最后遍歷F類:F->E->object
  5. 將之前遍歷的結(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錯誤

完整邏輯如下:

  1. 如果屬性是在其類對象或者基類的__dict__屬性當中扒怖,并且屬性是數(shù)據(jù)描述符,那么將調(diào)用__get__方法业稼,否則進入2
  2. 如果屬性是在當前實例對象的__dict__中盗痒,則直接返回__dict__[attr],否則進入3
  3. 如果屬性是在其類對象或者基類的__dict__屬性當中低散,并且屬性是非數(shù)據(jù)描述符俯邓,則調(diào)用其__get__方法骡楼,否則進入4
  4. 如果屬性是在其類對象或者基類的__dict__屬性當中,并且屬性不是描述符(沒有實現(xiàn)__get__方法)稽鞭,則返回__dict__[attr]君编,否則進入5
  5. 進入5則說明該屬性不存在,此時如果當前類實現(xiàn)了__getattr__方法川慌,則調(diào)用該方法吃嘿,否則進入6
  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類,里面的nameage屬性分別是非數(shù)據(jù)描述符和數(shù)據(jù)描述符梦重,然后實例化的時候又定義了nameage屬性兑燥。
在調(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__方法最后一行注釋室囊,則不會輸出34,即不會創(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())

參考:一文帶你完全理解Python中的metaclass

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者

  • 序言:七十年代末绰垂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子顺饮,更是在濱河造成了極大的恐慌吵聪,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兼雄,死亡現(xiàn)場離奇詭異吟逝,居然都是意外死亡,警方通過查閱死者的電腦和手機赦肋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門块攒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人佃乘,你說我怎么就攤上這事囱井。” “怎么了趣避?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵庞呕,是天一觀的道長。 經(jīng)常有香客問我程帕,道長住练,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任愁拭,我火速辦了婚禮讲逛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岭埠。我一直安慰自己盏混,他們只是感情好蔚鸥,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著括饶,像睡著了一般株茶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上图焰,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天启盛,我揣著相機與錄音,去河邊找鬼技羔。 笑死僵闯,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的藤滥。 我是一名探鬼主播鳖粟,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拙绊!你這毒婦竟也來了向图?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤标沪,失蹤者是張志新(化名)和其女友劉穎榄攀,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體金句,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡檩赢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了违寞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贞瞒。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖趁曼,靈堂內(nèi)的尸體忽然破棺而出军浆,到底是詐尸還是另有隱情,我是刑警寧澤彰阴,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布瘾敢,位于F島的核電站,受9級特大地震影響尿这,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庆杜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一射众、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晃财,春花似錦叨橱、人聲如沸典蜕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愉舔。三九已至,卻和暖如春伙菜,著一層夾襖步出監(jiān)牢的瞬間轩缤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工贩绕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留火的,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓淑倾,卻偏偏與公主長得像馏鹤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子娇哆,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學習記錄文檔湃累,今天18年5月份再次想寫文章,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 2,762評論 2 9
  • Objective-C語言是一門動態(tài)語言碍讨,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了運行時來處理治力。這種動態(tài)語言的...
    有一種再見叫青春閱讀 585評論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)垄开,斷路器琴许,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • 一、Python簡介和環(huán)境搭建以及pip的安裝 4課時實驗課主要內(nèi)容 【Python簡介】: Python 是一個...
    _小老虎_閱讀 5,746評論 0 10
  • 每—個百合也似的小花箭溉躲,卻密密團簇而成青蓮的樣子榜田,然而這是油菜花苞初妝成。 每一顆花苞都黃熟了锻梳,看誰開第一個箭券。 你...
    鈴蘭空地閱讀 238評論 0 0