【问题标题】:Python decorator with arguments带参数的 Python 装饰器
【发布时间】:2016-05-10 16:42:32
【问题描述】:

我有一个类有很多非常相似的属性:

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'

【问题讨论】:

  • 我看的越多,我就越相信我想要达到的目标是疯狂的......myDecorator 可以知道myClass 是为它添加属性吗?
  • 直到__get__() 等被调用。
  • 谢谢。如果我理解正确,那么在这种情况下使用 @myDecorator('foo') 语法毫无意义。类属性foo应在类定义中定义,myDecorator应称为myCustomProperty
  • 您不必将装饰函数作为参数传递给装饰器。这可以通过使用包装器来解决。

标签: python python-3.x decorator


【解决方案1】:

您总是可以使用 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    

【讨论】:

  • 谢谢 我想到了这样的事情,但我不熟悉functools.wraps。我也会看看这个。
【解决方案2】:

如果您想使用@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 装饰师。

【讨论】:

  • 这是有道理的。非常感谢这个例子。我实际上将compute_foo 作为一个单独的函数,以便在子类化时能够覆盖它。所以我可能会坚持单独定义类属性。
  • 我喜欢类装饰器的想法。那很亮!我想我需要对 Test 的所有子类应用相同的装饰器才能使其工作?
  • 是的,它需要在所有子类上。如果您使用元类而不是类装饰器,则可以避免放入所有子类。
【解决方案3】:

我最终得到了一个元类,以使子类化更容易。感谢 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)

【讨论】:

    猜你喜欢
    • 2014-07-21
    • 2012-03-03
    • 2014-03-24
    • 2015-10-19
    • 1970-01-01
    • 1970-01-01
    • 2018-01-07
    • 2022-12-28
    相关资源
    最近更新 更多