python元編程詳解


什么是元編程

軟件開發中很重要的一條原則就是“不要重復自己的工作(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”的對象。這個對象(類)本身能夠創建對象(實例),這就是為什么它是一個類。但是,它仍然是一個對象,因此:

    1. 您可以將其分配給變量
    2. 你可以復制它
    3. 你可以添加屬性
    4. 您可以將其作為函數參數傳遞

例如:

>>> 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來自定義元類

自定義元類的主要目的是:

    1. 攔截類的創建
    2. 讀取類的信息,可以做修改
    3. 返回新的類

通過傳入不同的字符串動態的創建不同的類

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM