带参数的Python装饰器

时间:2016-05-10 16:42:33

标签: python python-3.x decorator

我的课程有很多非常相似的属性:

class myClass(object):

    def compute_foo(self):
        return 3

    def compute_bar(self):
        return 4

    @property
    def foo(self):
        try:
            return self._foo
        except AttributeError:
            self._foo = self.compute_foo()
            return self._foo

    @property
    def bar(self):
        try:
            return self._bar
        except AttributeError:
            self._bar = self.compute_bar()
            return self._bar
    ...   

以为我会写一个装饰器来做属性定义工作。

class myDecorator(property):
    def __init__(self, func, prop_name):
        self.func = func
        self.prop_name = prop_name
        self.internal_prop_name = '_' + prop_name

    def fget(self, obj):
        try:
            return obj.__getattribute__(self.internal_prop_name)
        except AttributeError:
            obj.__setattr__(self.internal_prop_name, self.func(obj))
            return obj.__getattribute__(self.internal_prop_name)

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.func is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)


class myClass(object):

    def compute_foo(self):
        return 3
    foo = myDecorator(compute_foo, 'foo')

    def compute_bar(self):
        return 4
    bar = myDecorator(compute_bar, 'bar')

这很有效,但是当我想使用@myDecorator('foo')语法时,它变得更复杂,无法确定__call__方法应返回什么以及如何将属性附加到其类。

目前我有:

class myDecorator(object):
    def __init__(self, prop_name):
        self.prop_name = prop_name
        self.internal_prop_name = '_' + prop_name

    def __call__(self, func):
        self.func = func
        return #???

    def fget(self, obj):
        try:
            return obj.__getattribute__(self.internal_prop_name)
        except AttributeError:
            obj.__setattr__(self.internal_prop_name, self.func(obj))
            return obj.__getattribute__(self.internal_prop_name)

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.func is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

class myClass(object):
    @myDecorator('foo')
    def compute_foo(self):
        return 3

c = myClass()
print(c.foo)

然后返回:AttributeError: 'myClass' object has no attribute 'foo'

3 个答案:

答案 0 :(得分:2)

你总是可以使用wraps技巧将参数传递给装饰器,如下所示:

from functools import wraps

class myDecorator(property):
    def __init__(self, prop_name):
        self.prop_name = prop_name

    def __call__(self, wrappedCall):
        @wraps(wrappedCall)
        def wrapCall(*args, **kwargs):
            klass = args[0]
            result = wrappedCall(*args, **kwargs)
            setattr(klass, self.prop_name, result)
        return wrapCall

class myClass(object):
    @myDecorator('foo')
    def compute_foo(self):
        return 3

c = myClass()
c.compute_foo()
print c.foo    

答案 1 :(得分:1)

如果您想使用@decorator语法,则无法将该属性重新映射到该类的其他名称。这意味着您的compute_x方法必须重命名为与属性相同。

编辑:可以重新映射名称,但您也需要使用类装饰器。

class MyProperty(property):
    def __init__(self, name, func):
        super(MyProperty, self).__init__(func)
        self.name = name
        self.internal_prop_name = '_' + name
        self.func = func

    def fget(self, obj):
        try:
            return obj.__getattribute__(self.internal_prop_name)
        except AttributeError:
            obj.__setattr__(self.internal_prop_name, self.func(obj))
            return obj.__getattribute__(self.internal_prop_name)

    def __get__(self, obj, objtype=None)
        if obj is None:
            return self
        if self.func is None:
            raise AttributeError('unreadable')
        return self.fget(obj)

def myproperty(*args)
    name = None
    def deco(func):
        return MyProperty(name, func)

    if len(args) == 1 and callable(args[0]):
        name = args[0].__name__
        return deco(args[0])
    else:
        name = args[0]
        return deco


class Test(object):

    @myproperty
    def foo(self):
        return 5

没有类装饰器,name参数唯一相关的时候就是你的内部变量名称与函数名称不同,所以你可以像

那样
@myproperty('foobar')
def foo(self):
    return 5

它会查找_foobar而不是_foo,但属性名称仍为foo

但是, 可以重新映射属性名称,但您也必须使用类装饰器。

def clsdeco(cls):
    for k, v in cls.__dict__.items():
        if isinstance(v, MyProperty) and v.name != k:
            delattr(cls, k)
            setattr(cls, v.name, v)
    return cls


@clsdeco
class Test(...)

    @myproperty('foo')
    def compute_foo(self):
        pass

这将遍历该类的所有属性并查找任何MyProperty个实例,并检查集合名称是否与映射名称相同,否则,它会将属性重新绑定到传递的名称进入myproperty装饰者。

答案 2 :(得分:0)

我最终得到了一个元类,使子类更容易。感谢Brendan Abel暗示这个方向。

import types

class PropertyFromCompute(property):

    def __init__(self, func):
        self.func = None
        self.func_name = func.__name__
        self.internal_prop_name = self.func_name.replace('compute', '')

    def fget(self, obj):
        try:
            return obj.__getattribute__(self.internal_prop_name)
        except AttributeError:
            obj.__setattr__(self.internal_prop_name, self.func())
            return obj.__getattribute__(self.internal_prop_name)

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.func is None:
            try:
                self.func =  obj.__getattribute__(self.func_name)
            except AttributeError:
                raise AttributeError("unreadable attribute")
        return self.fget(obj)

class WithPropertyfromCompute(type):

    def __new__(cls, clsname, bases, dct):
        add_prop = {}
        for name, obj in dct.items():
            if isinstance(obj, types.FunctionType) and name.startswith('compute_'):
                add_prop.update({name.replace('compute_',''): PropertyFromCompute(obj)})
        dct.update(add_prop)
        return super().__new__(cls, clsname, bases, dct)


class myClass(object, metaclass=WithPropertyfromCompute):

    def compute_foo(self):
        raise NotImplementedError('Do not instantiate the base class, ever !')

class myChildClass(myClass):

    def compute_foo(self):
        return 4

base = myClass()
try:
    print(base.foo)
except NotImplementedError as e:
    print(e)
print(myClass.foo)
child = myChildClass()
print(child.foo)