骏马金龙 (新博客:www.junmajinlong.com)

网名骏马金龙,钟情于IT世界里的各种原理和实现机制,强迫症重症患者。爱研究、爱翻译、爱分享。特借此一亩三分田记录自己成长点滴!!!

python装饰器2:类装饰器

本文是装饰器相关内容的第二篇,关于类装饰器。

"类装饰器"有两种解读方式:用来装饰类的装饰器;类作为装饰器装饰其它东西。你如何认为取决于你,两种说法都有出现在其它的文章中。我的文章中是将"类装饰器"解读为第一种方式,即装饰类的东西。而“类作为装饰器装饰其它东西”,我都会为其标注"类作为装饰器"或"作为装饰器的类"以避免歧义。

类装饰器的形式

函数装饰器是装饰函数(方法)的,类装饰器是装饰类的,它们的表现形式是一样的。

@decorator
class cls:
    ...

c = cls()

等价于:

class cls:
    ...

cls = decorator(cls)

c = cls()

它的效果是创建实例对象的时候,会触发装饰器中的代码逻辑。

再细细一想,发现decorator(cls)要返回的是一个类,所以decorator中的结构大概是这样的:

def decorator(cls):
    class wrapper:
        ...
    return wrapper

这样就会让被包装的类cls实际变成wrapper类,并且以后调用cls构造对象的时候,实际上是调用wrapper类来构造对象。换句话说,wrapper已经拦截了对所有的cls操作。

但并非一定如此,比如直接返回原始的cls:

def decorator(cls):
    ...do something about cls...
    return cls

这种方式比较简单,本文主要对前一种方式进行详细解释。

由于返回的是class wrapper,那么它装饰类的时候,假设所装饰的类有构造方法__init__,构造方法中有属性,这个类中还有方法。如下:

@decorator
class cls():    # 等价于cls = decorator(cls)
    def __init__(self, x, y):
        self.attrx = x
        self.attry = y
    def method(self):
        return self.x, self.y

那么在包装器wrapper中,需要能够构造出这个对象,并且能够取得被包装类的对象属性、类属性。如下:

def decorator(cls):
    class wrapper():
        def __init__(self, *args, **kwargs):
            self.wrapped = cls(*args, **kwargs)

        def __getattr__(self, name):
            return getattr(self.wrapped, name)
    return wrapper

因为操作cls类的时候,实际上是在操作wrapper类。所以构造cls对象的时候:

c = cls(3, 4)

实际上是在调用wrapper(3, 4)来构造对象,所以会执行wrapper里的__init__。但类装饰器最终的目标是为了扩展类cls,所以在wrapper里必须得构造出cls的对象。上面采取的方式是通过cls()来构造cls对象,并放在wrapper对象的一个属性wrapped中。

因为cls已经被金蚕脱壳成了wrapper,所以要获取到cls的属性必须在wrapper中重写属性获取的方式。

下面是一个示例:

def decorator(cls):
    class wrapper():
        def __init__(self, *args, **kwargs):
            self.wrapped = cls(*args, **kwargs)

        def __getattr__(self, name):
            return getattr(self.wrapped, name)
    return wrapper

@decorator
class cls():
    def __init__(self, x, y):
        self.attrx = x
        self.attry = y
    def method(self):
        return self.attrx, self.attry

c = cls(3, 4)
print(c.attrx)
print(c.attry)
print(c.method())

输出结果:

3
4
(3, 4)
posted @ 2019-01-01 16:48  骏马金龙  阅读(7485)  评论(3编辑  收藏  举报