日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

進階 | Python 面向切面編程與元類

 達坂城大豆 2017-12-21

作者:再見紫羅蘭

來源:http://www.cnblogs.com/linxiyue/p/8030604.html

在 Python中,實例對象是由類生成的,而類本身也是可以被傳遞和自省的對象。那么類對象是用什么創(chuàng)建和生成的呢?答案是元類,元類就是一種知道如何創(chuàng)建和管理類的對象。

讓我們回顧一個內(nèi)置函數(shù)type(),type不僅可以返回對象的類型,而且可以使用類名稱、基類元組、類主體定義的字典作為參數(shù)來創(chuàng)建一個新類對象:

  1. >>> Foo = type('Foo',(object,),{'foo':lambda self:'foo'})

  2. >>> Foo

  3. >>> type(Foo)

實際上,新型類的默認元類就是type,類可以用metaclass類變量顯示的指定元類,上述代碼功能與下述相同:

  1. class Foo():

  2.    __metaclass__ = type

  3.    def foo(self):

  4.        return 'foo'

如果沒有顯式的指定元類,class語句會檢查基類元組中的第一個基類的元類,比如新型類都是繼承object類的,所以新型類與object類的元類相同,為type,繼承object而不顯式的指定元類:

  1. class Foo(object):

  2.    def foo(self):

  3.        return 'foo'

如果沒有指定基類,class語句會檢查全局變量metaclass,如果沒有找到metaclass值,Python會使用默認的元類。

在python 2中,默認的元類是types.ClassType,就是所謂的舊樣式類。python2.2以后已不提倡使用,比如不指定元類并且不繼承object基類:

  1. class Foo():

  2.    def foo(self):

  3.        return 'foo'

  4. >>> import types

  5. >>> isinstance(Foo, types.ClassType)

  6. True

python 3以后,默認的元類皆為type了,顯式定義元類的時候需要在基類元組中提供metaclass關(guān)鍵字,class Foo(metaclass=type)如此定義。

使用元類的時候,一般會自定義一個繼承自type的子類,并重新實現(xiàn)init()與new()方法:

  1. class ExampleType(type):

  2.    def __new__(cls, name, bases, dct):

  3.        print 'create class %s'%name

  4.        return type.__new__(cls, name, bases, dct)

  5.    def __init__(cls, name, bases, dct):

  6.        print 'Init class %s'%name

  7.        type.__init__(cls, name, bases, dct)

  8. class Foo(object):

  9.    __metaclass__ = ExampleType

  10. >>>

  11. create class Foo

  12. Init class Foo

  13. >>> Foo

可見,使用class語句定義類后,元類就使用傳遞給元類的類名稱、基類元組和類方法字典創(chuàng)建類。

因為元類創(chuàng)建的實例是類對象,所以init方法的第一個參數(shù)按慣例寫為cls,其實與self功能相同。

面向切面編程

在運行時,動態(tài)地將代碼切入到類的指定方法、指定位置上的編程稱為面向切面的編程(AOP)。

簡單地說,如果不同的類要實現(xiàn)相同的功能,可以將其中相同的代碼提取到一個切片中,等到需要時再切入到對象中去。這些相同的代碼片段稱為切面,而切入到哪些類、哪些方法則叫切入點。

比如,要為每個類方法記錄日志,在python中一個可行的方法是使用裝飾器:

  1. def trace(func):

  2.    def callfunc(self, *args, **kwargs):

  3.        debug_log = open('debug_log.txt', 'a')

  4.        debug_log.write('Calling %s: %s ,%sn'%(func.__name__, args, kwargs))

  5.        result = func(self, *args, **kwargs)

  6.        debug_log.write('%s returned %sn'%(func.__name__, result))

  7.        debug_log.close()

  8.        return result

  9.    return callfunc

  10. def logcls(cls):

  11.    for k, v in cls.__dict__.items():

  12.        if k.startswith('__'):

  13.            continue

  14.        if not callable(v):

  15.            continue

  16.        setattr(cls, k, trace(v))

  17.    return cls

  18. @logcls

  19. class Foo(object):

  20.    num = 0

  21.    def spam(self):

  22.        Foo.num += 1

  23.        return Foo.num

另外一個可行的方法就是使用元類了:

  1. def trace(func):

  2.    def callfunc(self, *args, **kwargs):

  3.        debug_log = open('debug_log.txt', 'a')

  4.        debug_log.write('Calling %s: %s ,%sn'%(func.__name__, args, kwargs))

  5.        result = func(self, *args, **kwargs)

  6.        debug_log.write('%s returned %sn'%(func.__name__, result))

  7.        debug_log.close()

  8.        return result

  9.    return callfunc

  10. class LogMeta(type):

  11.    def __new__(cls, name, bases, dct):

  12.        for k, v in dct.items():

  13.            if k.startswith('__'):

  14.                continue

  15.            if not callable(v):

  16.                continue

  17.            dct[k] = trace(v)

  18.        return type.__new__(cls, name, bases, dct)

  19. class Foo(object):

  20.    __metaclass__ = LogMeta

  21.    num = 0

  22.    def spam(self):

  23.        Foo.num += 1

  24.        return Foo.num

元類的一個主要用途就是檢查收集或者更改類定義的內(nèi)容,包括類屬性、類方法、描述符等等。

元類與基類

元類中除了可以定義initnew方法外,還可以定義其它的屬性和方法:

  1. class ExaMeta(type):

  2.    name = 'ExaMeta'

  3.    def get_cls_name(cls):

  4.        print cls.__name__

  5. class Foo(object):

  6.    __metaclass__ = ExaMeta

那么,類可不可以訪問元類定義的方法和屬性呢?

  1. >>> Foo.get_cls_name()

  2. Foo

  3. >>> Foo.name

  4. 'ExaMeta'

這很好理解,類Foo是元類的一個實例,在實例的dict中查找不到要查詢的屬性時,就會到實例所屬的類字典中去查找,而元類正是定義類Foo的類。

可以再嘗試下使用類Foo的實例去訪問元類的屬性或者方法:

  1. >>> Foo().get_cls_name()

  2. AttributeError: 'Foo' object has no attribute 'get_cls_name'

  3. >>> Foo().name

  4. AttributeError: 'Foo' object has no attribute 'name'

顯然不能訪問。

查找一個不與實例關(guān)聯(lián)的屬性時,即先在實例的類中查找,然后再在從所有的基類中查找,查找的順序可以用mro屬性查看:

  1. >>> Foo.__mro__

  2. (, )

元類并不在其中,畢竟,類與元類不是繼承關(guān)系,而是實例與類的創(chuàng)造關(guān)系。

元類屬性的可用性是不會傳遞的,也就是說,元類的屬性是對它的類實例是可用的,但是對它的類實例的實例是不可用的,這正是元類與基類的主要不同。

有時候,一個類會同時有元類和基類:

  1. class M(type):

  2.    name = 'M'

  3. class B(object):

  4.    name = 'B'

  5. class A(B):

  6.    __metaclass__ = M

屬性訪問是這樣的:

  1. >>> A.name

  2. 'B'

  3. >>> A().name

  4. 'B'

可見類會先到繼承的基類中去查找屬性。

元類沖突

假如有兩個不同元類的類,要生成一個繼承這兩個類的子類,會產(chǎn)生什么情況呢?

  1. class MA(type):

  2.    pass

  3. class A(object):

  4.    __metaclass__ = MA

  5. class MB(type):

  6.    pass

  7. class B(object):

  8.    __metaclass__ = MB

  9. class C(A, B):

  10.    pass

結(jié)果會報錯,提示元類沖突:

  1. TypeError: Error when calling the metaclass bases

  2.    metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

我們需要手動構(gòu)造新子類的元類,讓新子類的元類繼承自A和B的元類:

  1. class MC(MA, MB):

  2.    pass

  3. class C(A, B):

  4.    __metaclass__ = MC


題圖:pexels,CC0 授權(quán)。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多