筆記-python-元類metaclass
筆記-python-元類metaclass
1. 元類
1.1. 類也是對象
class Person(object):
pass
上面的代碼會在內存中創建一個類,它也是對象,
- 可以將它賦值給一個變量;
- 可以拷貝它;
- 可以為它增加屬性;
- 可以將它作為函數參數進行傳遞;
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‘>
說明:
- __class__表示實例的類;
- __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