元類 class底層分析 物件生成控制

2022-03-11 09:43:18 字數 4113 閱讀 8371

太難了太難了,頂不住頂不住

前方核能,這可能是我比較裝逼的一講,全部退後,我要開裝了。

首先我們要把什麼是元類搞清楚。

我們知道的,在python中,萬物皆物件,其實在學到元類之前,我真的無法體會,我只知道這麼乙個概念,知道遇見了元類,我才體會到這個概念的意思。

我們有類,然後通過例項化,來產生這個類的物件,是吧,那有沒有想過,類是怎麼來的,對沒錯,是class出來的,那有沒有想過它裡面發生了什麼操作?

其實,類他也是物件,他是元類的物件,又元類例項化出來的物件就是類!現在不要再把對物件的概念侷限在只有通過類例項化出來的東西了,萬物皆物件,是不是可以感受到那麼一點點?

那麼元類的概念也清楚了,元類就是例項化類物件的類。那麼元類是什麼樣子的呢?

print(type(dict))

print(type(list))

print(type(str))

print(type(object))

print(type(type))

我們可以看到這些類的型別全都是 type型別的,就連 type 本身,都是 type 類。

那麼基本可以確定了, type ,就是元類。並且所有的類都是由它例項化出來的,然後被例項化出來的類才能例項化出乙個個物件。

那麼既然元類也是類,第乙個元類怎麼來的?先有雞還是先有雞蛋?第乙個元類是用c語言寫的,相信小傻子們都明白了凡是遇到解釋不清楚的東西,你都可以說是c寫的。。。。。

這個就不要去想了,他已經是最底層的東西了。

我們知道 class +類名,就會把類給構造出來了,通過上一節我們也知道了,其實並沒有我們想的那麼簡單,實際上是元類例項化產生了類這個物件。

實際上,它是經歷了這麼乙個步驟。

person=type('person',(object,),dict)
type的返回值是乙個類,person來接收了這個返回值,於是就有了乙個叫做『person』的類,賦值給了person。你就可以用這個person來例項化各種物件了。

首先我們要講一下這個各個引數的作用,第乙個引數,很明顯就是要建立的這個類的名字,第二個就是這個類要繼承的父類,一定要寫成元組或者列表的形式,因為你不一定只繼承乙個類,第三個才是關鍵,是乙個字典,那麼這個字典裡面寫的是什麼呢?你回想一下你之前建立乙個類的時候,是不是要寫很多屬性,很多方法在裡面,這時候這些屬性和方法,全都被擠到這個dict裡面去了

l={}

exec('''

school='oldboy'

def __init__(self,name):

self.name=name

def score(self):

print('分數是100')

''',{},l)

exec方法,一共三個引數,第乙個是字串,第二個是全域性變數存放的字典,第三個是區域性變數的存放字典,我們要拿的是類裡面的東西,所以我們需要的是第三個引數,然後全放進了 l 裡面,這個exec的作用,他只是以字串的形式把**塊放在了第乙個引數的位置,然後執行這些**,就會生成各種命名空間,有乙個命名空間棧和命名空間堆,棧放變數名和方法名,堆放變數值和方法,列印出來為了方便**,會直接以變數名:變數值的形式,但是方法不是,方法沒辦法列印出來,只能是方法名:方法位址,然後把這些命名空間的對映關係存在第三個引數 l 裡面,可以說 l 裡面存的就是對映關係。那麼我們來列印一下這個 l。

果然是以乙個存放對映的字典,然後我們把這個字典扔進type的第三個引數裡

person=type('person',(object,),l)
就變成了這樣了,那麼到這裡,類就生成好了,我們就可以用這個person類來例項化物件了。

肯定有人會有這麼乙個問題,為什麼type方三個引數會生成類,放乙個引數卻是返回型別。

看,他的原始碼就是這樣的。我們用type這個元類例項化乙個類的時候,就會呼叫type的init方法,裡面具體是這樣的。

我們知道的,元類就是type,已經寫好了的,我們動不了,那我們怎麼通過元類來控制類的產生呢,我們可以自己寫,對自己寫,然後把別的類的預設的元類改成自己寫的元類就好了。

自定義元類:來控制類的產生:可以控制類名,可以控制類的繼承父類,控制類的命名空間

自定義元類必須繼承type,寫乙個類繼承type ,這種類都叫元類

class mymeta(type):

# def __init__(self,*args,**kwargs):

def __init__(self,name,bases,dic):

# self 就是person類

# print(name)

# print(bases)

# print(dic)

這樣我們就寫好了乙個自定義的元類了。

來做個小練習

練習一:加限制 控制類名必須以sb開頭

class mymeta(type):

# def __init__(self,*args,**kwargs):

def __init__(self,name,bases,dic):

if not name.startswith('sb'):

raise exception('類名沒有以sb開頭')

比如我們在例項化乙個類

class person(metaclass=mymeta):

name='nick'

我們在類的括號裡面加了乙個metaclass=mymeta,這就是指定了他的元類是我們自定義的那個類,而不是預設的type。這時候我們把name(也就是自身的類名),bases(也就是自身的父類,這裡沒有),和dic(自身的**塊)傳進了自定義元類的init方法裡,下面的操作你們就懂了。

練習二:類必須加注釋

class mymeta(type):

# def __init__(self,*args,**kwargs):

def __init__(self,name,bases,dic):

if not self.__dict__['__doc__']:

raise exception('類裡沒有注釋')

這個就不多解釋了,一樣的道理

這個和上面的有什麼區別呢?

區別就是這個才是細節

控制類的呼叫過程,實際上在控制:物件的產生

怎麼實現?用__call__來實現

比如說我們必須要新生成的類是以 qssb開頭的,就可以在call裡面寫判斷邏輯。

class mymeta(type):

def __call__(self, *args, **kwargs):

obj=object.__new__(self)

obj.__init__(*args, **kwargs)

# print(obj.__dict__)

if not obj.__class__.__name__.startswith('qssb'):

raise exception('類名沒有以qssb開頭')

return obj

class person(object,metaclass=mymeta):

school='oldboy'

def __init__(self,name):

self.name=name

def score(self):

print('分數是100')

p=person('nick')

print(p.name)

我不想解釋這個**了,我累了。

因為你的例項化出的p,會呼叫person(),所以就會執行他的元類的__call__方法,在這裡面有呼叫了自身的init,懂了吧。首先我們看一下這個new,在他的括號裡面寫乙個類的名稱,就能夠例項化出乙個空的物件。然後就你懂得了

類的屬性查詢順序:先從類本身中找--->mro繼承關係去父類中找---->去自己定義的元類中找--->type中--->報錯

物件的屬性查詢順序:先從物件自身找--->類中找--->mro繼承關係去父類中找--->報錯

結束結束,腰痛

class底層原理分析

class 類名 會把類構造出來 實際上是 元類例項化產生類 這個物件 類例項化產生物件,一定是 類名 person 類是由type例項化產生,傳一堆引數 type 呼叫類的 init 方法 type type object or name,bases,dict object or name 類的名...

類 Class 物件 定義 方法

類 class 用來描述具體相同的屬性和方法的物件的集合。定義了該集合中每個物件所共有的屬性和方法。物件是類的示例。類變數 類變數在整個例項化的物件中是公用的。類變數定義在類中且在函式體之外。類變數通常不作為例項變數使用。方法重寫 如果從父類繼承的方法不能滿足子類的需求,可以對其 進行改寫,這個過程...

Python 物件底層實現分析

pyobject物件是一切python物件共有的部分,包含以下內容 typedef struct object pyobject pyobject是所有物件共有的頭部,所以可以通過pyobject 來引用任意乙個物件,類似於c 的繼承。python中除了有物件可分為兩種 定長物件 int,float...