什么是元編程
軟件開發中很重要的一條原則就是“不要重復自己的工作(Don’t repeat youself)”,也就是說當我們需要復制粘貼代碼時候,通常都需要尋找一個更加優雅的解決方案,在python中,這類問題常常會歸類為“元編程”
元編程目的
是創建函數和類,並用他們操作代碼(例如修改,生成,或者包裝自己已有的代碼)。盡可能的使代碼優雅簡潔。具體而言,通過編程的方法,在更高的抽象層次上對一種層次的抽象的特性進行修改
元編程應用
給函數添加一個包裝(裝飾器)
關於裝飾器的參考鏈接:http://www.cnblogs.com/xiaobingqianrui/p/8435074.html
注意:對wraps裝飾器的使用進行補充說明,在類裝飾器中使用閉包會導致生成的對象不再是被裝飾的類的實例,而是在裝飾器函數創建的子類的實例,這會影響__name__和__doc__等屬性,在上篇我們使用@wraps裝飾器對函數裝飾器進行操作讓問題得到解決,但在類裝飾器中這一方法無效。
元類
在理解元類之前,您需要掌握Python中的類。Python對於從Smalltalk語言借用的類是非常奇怪的。在大多數語言中,類只是描述如何生成對象的代碼片段。在Python中也是如此:
>>> class ObjectCreator(object): ... pass ... >>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>
一旦使用關鍵字class,Python就會執行它並創建一個OBJECT。指示
>>> class ObjectCreator(object): ... pass ...
在內存中創建一個名為“ObjectCreator”的對象。這個對象(類)本身能夠創建對象(實例),這就是為什么它是一個類。但是,它仍然是一個對象,因此:
-
- 您可以將其分配給變量
- 你可以復制它
- 你可以添加屬性
- 您可以將其作為函數參數傳遞
例如:
>>> print(ObjectCreator) # you can print a class because it's an object <class '__main__.ObjectCreator'> >>> def echo(o): ... print(o) ... >>> echo(ObjectCreator) # you can pass a class as a parameter <class '__main__.ObjectCreator'> >>> print(hasattr(ObjectCreator, 'new_attribute')) False >>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class >>> print(hasattr(ObjectCreator, 'new_attribute')) True >>> print(ObjectCreator.new_attribute) foo >>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable >>> print(ObjectCreatorMirror.new_attribute) foo >>> print(ObjectCreatorMirror()) <__main__.ObjectCreator object at 0x8997b4c>
動態創建類
由於類是對象,因此您可以像任何對象一樣動態創建它們。首先,您可以使用class以下命令在函數中創建類:
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>
但它還不是我們想要的,創建類應該有更優的方法,由於類是對象,因此它們必須由某些東西生成。
使用class關鍵字時,Python會自動創建此對象。但與Python中的大多數內容一樣,它為您提供了手動執行此操作的方法。我們可用通過type函數查看對象的類型:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
type除了可以查看數據類型外,還有一個特殊的能力,它也可以動態創建類。type可以將類的描述作為參數,並返回一個類。查看type內部原理:
type(name of the class,
tuple of the parent class (for inheritance, can be empty),
dictionary containing attributes names and values)
例如:
>>> class MyShinyClass(object): ... pass
可以通過以下方式手動創建:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>
type接受字典來定義類的屬性。所以:
>>> class Foo(object): ... bar = True
可以翻譯成:
>>> Foo = type('Foo', (), {'bar':True})
並用作普通類:
>>> print(Foo) <class '__main__.Foo'> >>> print(Foo.bar) True >>> f = Foo() >>> print(f) <__main__.Foo object at 0x8a9b84c> >>> print(f.bar) True
當然,你可以繼承它,所以:
>>> class FooChild(Foo): ... pass
會解釋成:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True
最后,如果想要為我們創建的類添加方法,只需使用正確的簽名定義函數並將其指定為屬性即可。
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True
在動態創建類之后,您可以添加更多方法,就像向正常創建的類對象添加方法一樣。
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True
在Python中,類是對象,您可以動態地動態創建類。這是Python在您使用關鍵字時所執行的操作class,它通過使用元類來實現。
什么是元類(終於講到重點了)
元類是創建類的“類”。我們可以定義類來創建實例,python一切皆對象,類也不列外,它是通過元類來創建。類是創建實例的藍圖,元類是創建類的藍圖。可以很容易地看出,Python類中也需要是第一類對象才能啟用此行為。
例如:
MyClass = MetaClass() my_object = MyClass()
通過type來創建:
MyClass = type('MyClass', (), {})
這是因為該函數type實際上是一個元類。type是Python用於在幕后創建所有類的元類。
為什么是小寫type而不是大學Type?
type與str創建字符串對象int的類創建整數對象的類類似,它也只是創建類對象的類。我們通過檢查__class__屬性來查看。
一切,一切,一切重要的事情說三遍,都是Python中的一個對象。這包括整數,字符串,函數和類。所有這些都是對象。所有這些都是從一個類創建的:
>>> age = 35 >>> age.__class__ <type 'int'> >>> name = 'bob' >>> name.__class__ <type 'str'> >>> def foo(): pass >>> foo.__class__ <type 'function'> >>> class Bar(object): pass >>> b = Bar() >>> b.__class__ <class '__main__.Bar'>
那么__class__的__class__?
>>> age.__class__.__class__ <type 'type'> >>> name.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'>
因此,元類只是創建類對象的東西。我們也稱它為類工廠
type 是Python使用的內置元類,我們也可以創建自己的元類。
__mataClass__屬性
在Python 2中,我們在編寫類時添加屬性:
class Foo(object):
__metaclass__ = something...
[...]
如果引用__mataClass__屬性,python將使用元類類創建Foo,但是這樣class Foo(object),類對象Foo並不是在內存中創建的
Python將會在父類中查找__metaclass__,如果沒有,就繼續向父類的父類查找,如果還是沒有,就在模塊中找,還是沒有的話就用缺省的MetaClass即type創建類。
當你這樣做時:
class Foo(Bar):
pass
Python會執行以下操作:
如果有__metaclass__屬性,將會在內存中創建一個類對象,名稱Foo使用是__metaclass__。如果Python找不到__metaclass__,它將__metaclass__在MODULE級別查找,並嘗試執行相同的操作(但僅適用於不繼承任何內容的類,基本上是舊式類)。
如果還是找不到__metaclass__,它將使用Bar's(第一個父級)自己的元類(可能是默認的type)來創建類對象。

Python中的元類3
在Python 3中更改了設置元類的語法:
class Foo(object, metaclass=something):
...
即__metaclass__不再使用該屬性,而是支持基類列表中的關鍵字參數。但是並不會影響元類的功能。
python3中我們可以將屬性作為關鍵字參數傳遞給元類,如下所示:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
...
自定義元類
一個類沒有聲明自己的元類,默認他的元類就是type,除了使用元類type,用戶也可以通過繼承type來自定義元類
自定義元類的主要目的是:
-
- 攔截類的創建
- 讀取類的信息,可以做修改
- 返回新的類
通過傳入不同的字符串動態的創建不同的類
def create_class(name):
if name == 'user':
class User:
def __str__(self):
return "user"
return User
elif name == "company":
class Company:
def __str__(self):
return "company"
return Company
if __name__ == '__main__':
Myclass = create_class("user")
my_obj = Myclass()
print(my_obj) #user
print(type(my_obj)) #<class '__main__.create_class.<locals>.User'>
用type創建
# 一個簡單type創建類的例子
#type(object_or_name, bases, dict)
#type里面有三個參數,第一個類名,第二個基類名,第三個是屬性
User = type("User",(),{"name":"derek"})
my_obj = User()
print(my_obj.name) #derek
#帶方法的創建
def say(self): #必須加self return "i am derek" User = type("User",(),{"name":"derek","say":say}) my_obj = User() print(my_obj.name) #derek print(my_obj.say()) #i am derek
讓type創建的類繼承一個基類
def say(self): #必須加self
return "i am derek"
class BaseClass:
def answer(self):
return "i am baseclass"
#type里面有三個參數,第一個類名,第二個基類名,第三個是屬性
User = type("User",(BaseClass,),{"name":"derek","say":say})
if __name__ == '__main__':
my_obj = User()
print(my_obj.name) #d erek
print(my_obj.say()) # i am derek
print(my_obj.answer()) # i am baseclass
但是在實際編碼中,我們一般不直接用type去創建類,而是用元類的寫法,自定義一個元類metaclass去創建
# 把User類創建的過程委托給元類去做,這樣代碼的分離性比較好
class MetaClass(type):
def __new__(cls, *args, **kwargs):
return super().__new__(cls,*args, **kwargs)
class User(metaclass=MetaClass):
def __init__(self,name):
self.name = name
def __str__(self):
return "test"
if __name__ == '__main__':
#python中類的實例化過程,會首先尋找metaclass,通過metaclass去創建User類
my_obj = User(name="derek")
print(my_obj) #test
還有一個典型的自定義元類例子就是Django ORM。
元類的主要用例是創建API。
它允許您定義如下內容:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
但是如果你這樣做:
guy = Person(name='bob', age='35')
print(guy.age)
它不會返回一個IntegerField對象。它將返回一個int,甚至可以直接從數據庫中獲取它。
因為models.Model定義__metaclass__它會使用一些魔法將Person您剛剛使用簡單語句定義的內容轉換為數據庫字段的復雜sql。
Django通過公開一個簡單的API並使用元類,從這個API中重新創建代碼來完成幕后的實際工作,從而使復雜的外觀變得簡單。
參考:https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python
https://www.cnblogs.com/Gaoqiking/p/10744253.html
