1. 程式人生 > >筆記-python-元類metaclass

筆記-python-元類metaclass

elf vat bsp 實現 字典 tac self 步驟 附錄

筆記-python-元類metaclass

1. 元類

1.1. 類也是對象

class Person(object):

pass

上面的代碼會在內存中創建一個類,它也是對象,

  1. 可以將它賦值給一個變量;
  2. 可以拷貝它;
  3. 可以為它增加屬性;
  4. 可以將它作為函數參數進行傳遞;

1.2. 動態地創建類

類也是對象,可以在運行時動態的創建它們,可以在函數中創建類。

>>> def chosse_class(name):

if name == ‘fa‘:

class Fa(object):

pass

return Fa

else:

class Fb(object):

pass

return Fb

>>> myclass = chosse_class(‘ff‘)

>>> dir(myclass)

[‘__class__‘, ‘__delattr__‘, ‘__dict__‘, ‘__dir__‘, ‘__doc__‘, ‘__eq__‘, ‘__format__‘, ‘__ge__‘, ‘__getattribute__‘, ‘__gt__‘, ‘__hash__‘, ‘__init__‘, ‘__init_subclass__‘, ‘__le__‘, ‘__lt__‘, ‘__module__‘, ‘__ne__‘, ‘__new__‘, ‘__reduce__‘, ‘__reduce_ex__‘, ‘__repr__‘, ‘__setattr__‘, ‘__sizeof__‘, ‘__str__‘, ‘__subclasshook__‘, ‘__weakref__‘]

>>> myclass

<class ‘__main__.chosse_class.<locals>.Fb‘>

>>> myclass()

<__main__.chosse_class.<locals>.Fb object at 0x00000092615AFD30>

使用class可以創建類,是通過python內置功能函數完成的,能不能手動創建類呢?

1.3. type

type(1) #<class ‘int‘>

常用來查看類型,但type還可以動態的創建類。

類的三大關鍵組成部分:類名,類的基類(們),類的名稱空間;將這些參數傳遞給type就可以手動創建類了。

語法:

type(類名, 父類元組(繼承,可以為空),屬性字典)

下面兩部分代碼的功能是一樣的;

>>> class Pa(object):

pass

>>> Pa

<class ‘__main__.Pa‘>

>>> myclass = type(‘Pb‘, (), {})

>>> myclass

<class ‘__main__.Pb‘>

1.4. 元類

元類就是類的類,是道生一的道;

簡單來說,要創建實例需要先定義類;要定義類需要先創建類,創建類的根據是metaclass。

換句話說就是得先定義metaclass,再創建類,最後創建實例。

type是一個元類,type是python創建所有類的元類,類是type實例化的結果。

可以通過查看__class__屬性來檢查。

>>> age = 35

>>> age.__class__

<class ‘int‘>

>>> age.__class__.__class__

<class ‘type‘>

>>> foo.__class__

<class ‘function‘>

>>> foo.__class__.__class__

<class ‘type‘>

說明:

  1. __class__表示實例的類;
  2. __bases__表示父類;

總而言之,type是python的默認的元類,所有的類都是從它而來。

使用type創建類,實際上是:

Student = type()返回的一個類對象

student = Student()

2. 自定義元類

有時需要自定義元類。

定義語法:

class Foo(base1,base2,metaclass = mymeta):

也可以在類基列表中使用* args和** kwargs -style參數:

class Foo(base1, base2, metaclass=mymeta, private=True):

2.1. 元類原理及自定義元類實現

官方文檔PEP3115太坑了,下面的代碼很好的解釋了元類原理。

引用自https://www.cnblogs.com/ManyQian/p/8882639.html

2.1.1. 步驟一

# 控制類的創建

class Mymeta(type): # 繼承默認元類的一堆屬性

def __init__(self, class_name, class_bases, class_dic):

if ‘__doc__‘ not in class_dic or not class_dic.get(‘__doc__‘).strip():

raise TypeError(‘必須為類指定文檔註釋‘)

if not class_name.istitle():

raise TypeError(‘類名首字母必須大寫‘)

super(Mymeta, self).__init__(class_name, class_bases, class_dic)

class People(object, metaclass=Mymeta):

country = ‘China‘

def __init__(self, name, age):

self.name = name

self.age = age

def talk(self):

print(‘%s is talking‘ % self.name)

解釋:People類具備了Mymeta中的校驗功能。

2.1.2. 步驟二

# 控制類實例化的行為。

class People(object,metaclass=type):

def __init__(self,name,age):

self.name=name

self.age=age

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

print(self,args,kwargs)

obj=People(‘duoduo‘,18)

# 對象obj是可以調用的,具體參考__call__()

obj(1,2,3,a=1,b=2,c=3)

#輸出:<__main__.People object at 0x0000005BBD68E4E0> (1, 2, 3) {‘a‘: 1, ‘b‘: 2, ‘c‘: 3}

#總結:如果說類People是元類type的實例,那麽在元類type內肯定也有一個__call__,會在調用People(‘duoduo‘,18)時觸發執行,然後返回一個初始化好了的對象obj

2.1.3. 步驟三

# 自定義元類,控制類的調用(即實例化)的過程

class Mymeta(type): #繼承默認元類的一堆屬性

def __init__(self,class_name,class_bases,class_dic):

if not class_name.istitle():

raise TypeError(‘類名首字母必須大寫‘)

super(Mymeta,self).__init__(class_name,class_bases,class_dic)

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

#self=People

print(self,args,kwargs) #<class ‘__main__.People‘> (‘egon‘, 18) {}

#1、實例化People,產生空對象obj

obj=object.__new__(self)

#2、調用People下的函數__init__,初始化obj

self.__init__(obj,*args,**kwargs)

#3、返回初始化好了的obj

return obj

class People(object,metaclass=Mymeta):

country=‘China‘

def __init__(self,name,age):

self.name=name

self.age=age

def talk(self):

print(‘%s is talking‘ %self.name)

obj=People(‘duoduo‘,18)

print(obj.__dict__) #{‘name‘: ‘duoduo‘, ‘age‘: 18}

理解:

從上面的代碼我們可以看出,對象的產生其實就是,調用了類,進而觸發了元類的__call__ 方法,

但是調用類產生的是對象,說明元類的__call__ 方法是用來產生對象的。

說明元類的__call__ 方法就做了下面的幾件事

1、創造了一個空對象

2、調用了類的__init__ 方法

3、將參入傳入__init__方法中

2.1.4. 步驟四

class Mymeta(type):

def __init__(self,class_name,class_bases,class_dic):

if not class_name.istitle():

raise TypeError(‘類名首字母必須大寫‘)

super(Mymeta,self).__init__(class_name,class_bases,class_dic)

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

#self=People

print(self,args,kwargs) #<class ‘__main__.People‘> (‘egon‘, 18) {}

#1、調用self,即People下的函數__new__,在該函數內完成:1、產生空對象obj 2、初始化 3、返回obj

obj=self.__new__(self,*args,**kwargs)

#2、一定記得返回obj,因為實例化People(...)取得就是__call__的返回值

return obj

class People(object,metaclass=Mymeta):

country=‘China‘

def __init__(self,name,age):

self.name=name

self.age=age

def talk(self):

print(‘%s is talking‘ %self.name)

def __new__(cls, *args, **kwargs):

obj=object.__new__(cls)

cls.__init__(obj,*args,**kwargs)

return obj

obj=People(‘duoduo‘,18)

print(obj.__dict__) #{‘name‘: ‘duoduo‘, ‘age‘: 18}

2.1.5. 步驟五:案例,元類實現單例模式

#步驟五:基於元類實現單例模式

# singleton metaclass

class Singleton(type):

print(‘www‘)

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

if not hasattr(cls, ‘_instance‘):

print(cls)

cls._instance = super(Singleton, cls).__call__(*args, **kwargs)

return cls._instance

class MyClass(metaclass=Singleton):

a = 4

a1 = MyClass()

a2 = MyClass()

print(a1 == a2) #True

附錄

3. type和object,class的關系

上面的內容中沒有提到type和object的關系,這中間也有點繞。

先說結果,在Python的世界中,object是父子關系的頂端,所有的數據類型的父類都是它;type是類型實例關系的頂端,所有對象都是它的實例。

object是type實例化的結果,總之,一個類/函數調用後返回了一個類,這個類/函數就是元類了。如果你願意,原理上是可以返回一個和元類相同的類的,type就是這樣幹的。

type(type) # <class ‘type‘>

更詳細的信息如下:

print(type.__class__,type.__bases__)

print(object.__class__, object.__bases__)

輸出:

<class ‘type‘> (<class ‘object‘>,)

<class ‘type‘> ()

其它內置繼承於object:

>>> list.__class__

<class ‘type‘>

>>> list.__bases__

(<class ‘object‘>,)

>>> int.__class__

<class ‘type‘>

>>> int.__bases__

(<class ‘object‘>,)

比較奇特的是None,None是Python的特殊類型,NoneType對象,它只有一個值None. 但NoneType是未定義的,也沒有父類。

它不支持任何運算也沒有任何內建方法。

None和任何其他的數據類型比較永遠返回False。

None有自己的數據類型NoneType。

你可以將None復制給任何變量,但是你不能創建其他NoneType對象。

>>>type(None)

<class ‘NoneType‘>

>>> None.__bases__

Traceback (most recent call last):

File "<pyshell#53>", line 1, in <module>

None.__bases__

AttributeError: ‘NoneType‘ object has no attribute ‘__bases__‘

>>> dir(NoneType)

Traceback (most recent call last):

File "<pyshell#51>", line 1, in <module>

dir(NoneType)

NameError: name ‘NoneType‘ is not defined

3.1. type,object關系圖

筆記-python-元類metaclass