什么是元編程
軟件開發中很重要的一條原則就是“不要重復自己的工作(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