python元编程详解
什么是元編程
軟件開發(fā)中很重要的一條原則就是“不要重復自己的工作(Don’t repeat youself)”,也就是說當我們需要復制粘貼代碼時候,通常都需要尋找一個更加優(yōu)雅的解決方案,在python中,這類問題常常會歸類為“元編程”
元編程目的
是創(chuàng)建函數(shù)和類,并用他們操作代碼(例如修改,生成,或者包裝自己已有的代碼)。盡可能的使代碼優(yōu)雅簡潔。具體而言,通過編程的方法,在更高的抽象層次上對一種層次的抽象的特性進行修改
元編程應用
給函數(shù)添加一個包裝(裝飾器)
注意:對wraps裝飾器的使用進行補充說明,在類裝飾器中使用閉包會導致生成的對象不再是被裝飾的類的實例,而是在裝飾器函數(shù)創(chuàng)建的子類的實例,這會影響__name__和__doc__等屬性,在上篇我們使用@wraps裝飾器對函數(shù)裝飾器進行操作讓問題得到解決,但在類裝飾器中這一方法無效。
元類
在理解元類之前,您需要掌握Python中的類。Python對于從Smalltalk語言借用的類是非常奇怪的。在大多數(shù)語言中,類只是描述如何生成對象的代碼片段。在Python中也是如此:
>>> class ObjectCreator(object): ... pass ...>>> my_object = ObjectCreator() >>> print(my_object) <__main__.ObjectCreator object at 0x8974f2c>一旦使用關鍵字class,Python就會執(zhí)行它并創(chuàng)建一個OBJECT。指示
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學習交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' >>> class ObjectCreator(object): ... pass ...在內(nèi)存中創(chuàng)建一個名為“ObjectCreator”的對象。這個對象(類)本身能夠創(chuàng)建對象(實例),這就是為什么它是一個類。但是,它仍然是一個對象,因此:
- 您可以將其分配給變量
- 你可以復制它
- 你可以添加屬性
- 您可以將其作為函數(shù)參數(shù)傳遞
例如:
>>> 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>動態(tài)創(chuàng)建類
由于類是對象,因此您可以像任何對象一樣動態(tài)創(chuàng)建它們。首先,您可以使用class以下命令在函數(shù)中創(chuàng)建類:
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學習交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' >>> 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>但它還不是我們想要的,創(chuàng)建類應該有更優(yōu)的方法,由于類是對象,因此它們必須由某些東西生成。
使用class關鍵字時,Python會自動創(chuàng)建此對象。但與Python中的大多數(shù)內(nèi)容一樣,它為您提供了手動執(zhí)行此操作的方法。我們可用通過type函數(shù)查看對象的類型:
>>> print(type(1)) <type 'int'> >>> print(type("1")) <type 'str'> >>> print(type(ObjectCreator)) <type 'type'> >>> print(type(ObjectCreator())) <class '__main__.ObjectCreator'>type除了可以查看數(shù)據(jù)類型外,還有一個特殊的能力,它也可以動態(tài)創(chuàng)建類。type可以將類的描述作為參數(shù),并返回一個類。查看type內(nèi)部原理:
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可以通過以下方式手動創(chuàng)建:
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學習交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' >>> 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當然,你可以繼承它,所以:
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學習交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' >>> class FooChild(Foo): ... pass會解釋成:
>>> FooChild = type('FooChild', (Foo,), {}) >>> print(FooChild) <class '__main__.FooChild'> >>> print(FooChild.bar) # bar is inherited from Foo True最后,如果想要為我們創(chuàng)建的類添加方法,只需使用正確的簽名定義函數(shù)并將其指定為屬性即可。
會解釋成:
>>> 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在動態(tài)創(chuàng)建類之后,您可以添加更多方法,就像向正常創(chuàng)建的類對象添加方法一樣。
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學習交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' >>> def echo_bar_more(self): ... print('yet another method') ... >>> FooChild.echo_bar_more = echo_bar_more >>> hasattr(FooChild, 'echo_bar_more') True在Python中,類是對象,您可以動態(tài)地動態(tài)創(chuàng)建類。這是Python在您使用關鍵字時所執(zhí)行的操作class,它通過使用元類來實現(xiàn)。
什么是元類(終于講到重點了)
元類是創(chuàng)建類的“類”。我們可以定義類來創(chuàng)建實例,python一切皆對象,類也不列外,它是通過元類來創(chuàng)建。類是創(chuàng)建實例的藍圖,元類是創(chuàng)建類的藍圖。可以很容易地看出,Python類中也需要是第一類對象才能啟用此行為。
例如:
MyClass = MetaClass() my_object = MyClass()通過type來創(chuàng)建:
MyClass = type('MyClass', (), {})這是因為該函數(shù)type實際上是一個元類。type是Python用于在幕后創(chuàng)建所有類的元類。
為什么是小寫type而不是大學Type?
type與str創(chuàng)建字符串對象int的類創(chuàng)建整數(shù)對象的類類似,它也只是創(chuàng)建類對象的類。我們通過檢查__class__屬性來查看。
一切,一切,一切重要的事情說三遍,都是Python中的一個對象。這包括整數(shù),字符串,函數(shù)和類。所有這些都是對象。所有這些都是從一個類創(chuàng)建的:
>>> 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__?
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學習交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' >>> age.__class__.__class__ <type 'type'> >>> name.__class__.__class__ <type 'type'> >>> foo.__class__.__class__ <type 'type'> >>> b.__class__.__class__ <type 'type'>因此,元類只是創(chuàng)建類對象的東西。我們也稱它為類工廠
type 是Python使用的內(nèi)置元類,我們也可以創(chuàng)建自己的元類。
__mataClass__屬性
在Python 2中,我們在編寫類時添加屬性:
class Foo(object):__metaclass__ = something...[...]如果引用__mataClass__屬性,python將使用元類類創(chuàng)建Foo,但是這樣class Foo(object),類對象Foo并不是在內(nèi)存中創(chuàng)建的
Python將會在父類中查找__metaclass__,如果沒有,就繼續(xù)向父類的父類查找,如果還是沒有,就在模塊中找,還是沒有的話就用缺省的MetaClass即type創(chuàng)建類。
當你這樣做時:
class Foo(Bar):passPython會執(zhí)行以下操作:
如果有__metaclass__屬性,將會在內(nèi)存中創(chuàng)建一個類對象,名稱Foo使用是__metaclass__。如果Python找不到__metaclass__,它將__metaclass__在MODULE級別查找,并嘗試執(zhí)行相同的操作(但僅適用于不繼承任何內(nèi)容的類,基本上是舊式類)。
如果還是找不到__metaclass__,它將使用Bar’s(第一個父級)自己的元類(可能是默認的type)來創(chuàng)建類對象。
Python中的元類3
在Python 3中更改了設置元類的語法:
class Foo(object, metaclass=something):...即__metaclass__不再使用該屬性,而是支持基類列表中的關鍵字參數(shù)。但是并不會影響元類的功能。
python3中我們可以將屬性作為關鍵字參數(shù)傳遞給元類,如下所示:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):...自定義元類
一個類沒有聲明自己的元類,默認他的元類就是type,除了使用元類type,用戶也可以通過繼承type來自定義元類
自定義元類的主要目的是:
- 攔截類的創(chuàng)建
- 讀取類的信息,可以做修改
- 返回新的類
通過傳入不同的字符串動態(tài)的創(chuàng)建不同的類
def create_class(name):if name == 'user':class User:def __str__(self):return "user"return Userelif name == "company":class Company:def __str__(self):return "company"return Companyif __name__ == '__main__':Myclass = create_class("user")my_obj = Myclass()print(my_obj) #userprint(type(my_obj)) #<class '__main__.create_class.<locals>.User'>用type創(chuàng)建
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學習交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' # 一個簡單type創(chuàng)建類的例子 #type(object_or_name, bases, dict) #type里面有三個參數(shù),第一個類名,第二個基類名,第三個是屬性 User = type("User",(),{"name":"derek"})my_obj = User() print(my_obj.name) #derek#帶方法的創(chuàng)建<br><br>def say(self): #必須加selfreturn "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創(chuàng)建的類繼承一個基類
def say(self): #必須加selfreturn "i am derek"class BaseClass:def answer(self):return "i am baseclass"#type里面有三個參數(shù),第一個類名,第二個基類名,第三個是屬性 User = type("User",(BaseClass,),{"name":"derek","say":say})if __name__ == '__main__':my_obj = User()print(my_obj.name) #d erekprint(my_obj.say()) # i am derekprint(my_obj.answer()) # i am baseclass但是在實際編碼中,我們一般不直接用type去創(chuàng)建類,而是用元類的寫法,自定義一個元類metaclass去創(chuàng)建
''' 遇到問題沒人解答?小編創(chuàng)建了一個Python學習交流QQ群:579817333 尋找有志同道合的小伙伴,互幫互助,群里還有不錯的視頻學習教程和PDF電子書! ''' # 把User類創(chuàng)建的過程委托給元類去做,這樣代碼的分離性比較好class MetaClass(type):def __new__(cls, *args, **kwargs):return super().__new__(cls,*args, **kwargs)class User(metaclass=MetaClass):def __init__(self,name):self.name = namedef __str__(self):return "test"if __name__ == '__main__':#python中類的實例化過程,會首先尋找metaclass,通過metaclass去創(chuàng)建User類my_obj = User(name="derek")print(my_obj) #test還有一個典型的自定義元類例子就是Django ORM。
元類的主要用例是創(chuàng)建API。
它允許您定義如下內(nèi)容:
class Person(models.Model):name = models.CharField(max_length=30)age = models.IntegerField()但是如果你這樣做:
guy = Person(name='bob', age='35') print(guy.age)它不會返回一個IntegerField對象。它將返回一個int,甚至可以直接從數(shù)據(jù)庫中獲取它。
因為models.Model定義__metaclass__它會使用一些魔法將Person您剛剛使用簡單語句定義的內(nèi)容轉(zhuǎn)換為數(shù)據(jù)庫字段的復雜sql。
Django通過公開一個簡單的API并使用元類,從這個API中重新創(chuàng)建代碼來完成幕后的實際工作,從而使復雜的外觀變得簡單。
總結(jié)
以上是生活随笔為你收集整理的python元编程详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python中的__str__ __na
- 下一篇: Python-类型注解(3.5引入)