用于Python中许多属性的装饰器

时间:2012-08-06 12:31:05

标签: python decorator

是否可以编写一次创建多个属性的装饰器?

喜欢而不是写作

class Test:
    @property
    def a(self):
        return self.ref.a
    @property
    def b(self):
        return self.ref.b

我想写

class Test:
    @properties("a", "b")
    def prop(self, name):
        return getattr(self.ref, name)

有可能吗?你推荐它吗?

4 个答案:

答案 0 :(得分:3)

回想一下装饰者

@decorator(dec_args)
def foo(args):
    pass

只是写作的语法糖

def foo(args):
    pass
foo = decorator(dec_args)(foo)

因此,方法装饰器不可能将多个方法(或属性等)添加到类中。

替代方法可能是注入属性的类装饰器:

def multi_property(prop, *names):
    def inner(cls):
        for name in names:
            setattr(cls, name, property(lambda self, name=name: prop(self, name)))
    return inner

@multi_property(prop, 'a', 'b')
class Test:
    pass

然而,通常更清楚的是让每个属性都出现在课堂上:

a = forward_property('ref', 'a')
b = forward_property('ref', 'b')

其中forward_property在适当时返回一个属性对象,实现描述符协议。这对于文档和其他静态分析工具以及(通常)读者来说更友好。

答案 1 :(得分:2)

为另一个对象编写代理的最简单方法是实现__getattr__()

class Proxy(object):
    def __init__(self, ref1, ref2):
        self._ref1 = ref1
        self._ref2 = ref2
    def __getatrr__(self, name):
        if name in ["a", "b", "c"]:
            return getattr(self._ref1, name)
        if name in ["d", "e", "f"]:
            return getattr(self._ref2, name)

请注意,__getattr__()仅针对当前实例中找不到的属性进行调用,因此您还可以向Proxy添加更多方法和属性。

答案 2 :(得分:2)

lagnuage中你可能真正意图的一个条款就是为一个类写__setattr__方法。

只要在实例上访问通常不存在的属性,就会对此方法进行处理:

>>> class Test(object):
...    a = 0
...    def __getattr__(self, attr):
...       return attr
... 
>>> t = Test()
>>> t.a
0
>>> t.b
'b'
>>> t.c
'c'

你所要求的也是可能的,bute需要一些黑客 - 尽管不是常识,但在野外生产中被广泛使用。 也就是说,对于Python中存在的属性,它是一个特殊类型对象的类属性 - 至少具有__get__方法的对象。 (要了解更多关于Python文档的“描述符协议”)。

现在,尝试一次创建多个属性,就像您通过示例粘贴的代码一样,将要求将属性名称注入到被调用函数的类名空间中。它_是可能的,甚至在Python中用于生产,甚至难以实现。但不是很漂亮。

因此,避免这种情况的一种可能方法是进行一个返回一系列“属性”对象的调用 - 它是干净的,可读的和可维护的:

class MultiProperty(object):
    def __init__(self, getter, setter, name):
        self.getter = getter
        self.setter = setter
        self.name = name
    def __get__(self, instance, owner):
        return self.getter(instance, self.name)
    def __set__(self, instance, value):
        return self.setter(instance, self.name, value)

def multi_property(mgetter, msetter, *args):
    props = []
    for name in args:
        props.append(MultiProperty(mgetter, msetter, name))
    return props


class Test(object):
    def multi_getter(self, attr_name):
        # isf desired, isnert some logic here
        return getattr(self, "_" + attr_name)

    def multi_setter(self, attr_name, value):
        # insert some logic here
        return setattr(self, "_" + attr_name, value)        
    a,b,c = multi_property(multi_getter, multi_setter, *"a b c".split())

答案 3 :(得分:0)

这在课堂上是不可能的。但是,您可以在以后修改该类。见这里:

def makeprop(meth, name):
    # make a property calling the given method.
    # It is not really a method, but it gets called with the "self" first...
    return property(lambda self: meth(self, name))

def propfor(cls, *names):
    def wrap(meth):
        for name in names:
            # Create a property for a given object, in this case self,
            prop = makeprop(meth, name)
            setattr(cls, name, prop)
        return meth # unchanged
    return wrap

class O(object):
    # just a dummy for your ref
    a = 9
    b = 12
    c = 199

class C(object):
    ref = O()

# Put the wanted properties into the class afterwards:
@propfor(C, "a", "b", "c")
def prop(self, name):
    return getattr(self.ref, name)

# alternative approach with a class decorator:
def propdeco(*names):
    meth = names[-1]
    names = names[:-1]
    def classdeco(cls):
        propfor(cls, *names)(meth) # not nice, but reuses code above
        return cls
    return classdeco

@propdeco("a", "b", "c", lambda self, name: getattr(self.ref, name))
class D(object):
    ref = O()

print C().a
print C().b
print C().c

print D().a
print D().b
print D().c

如果您更喜欢第二种方法,则应将propdeco写为

def propdeco(*names):
    meth = names[-1]
    names = names[:-1]
    def classdeco(cls):
        for name in names:
            # Create a property for a given object, in this case self,
            prop = makeprop(meth, name)
            setattr(cls, name, prop)
        return cls
    return classdeco