为类中的所有属性动态创建@ attribute.setter方法(Python)

时间:2015-06-17 01:22:01

标签: python properties attributes setter

我有其他人写的代码:

class MyClass(object):
    def __init__(self, data):
        self.data = data

    @property
    def attribute1(self):
        return self.data.another_name1

    @property
    def attribute2(self):
        return self.data.another_name2

我想在运行时自动创建相应的属性设置器,因此我不必修改其他人的代码。属性设置器应如下所示:

    @attribute1.setter
    def attribue1(self, val):
        self.data.another_name1= val

    @attribute2.setter
    def attribue2(self, val):
        self.data.another_name2= val

如何将这些setter方法动态添加到类中?

2 个答案:

答案 0 :(得分:4)

您可以像这样编写自定义描述符:

from operator import attrgetter


class CustomProperty(object):
    def __init__(self, attr):
        self.attr = attr

    def __get__(self, ins, type):
        print 'inside __get__'
        if ins is None:
            return self
        else:
            return attrgetter(self.attr)(ins)

    def __set__(self, ins, value):
        print 'inside __set__'
        head, tail = self.attr.rsplit('.', 1)
        obj = attrgetter(head)(ins)
        setattr(obj, tail, value)


class MyClass(object):
    def __init__(self, data):
        self.data = data

    attribute1 = CustomProperty('data.another_name1')
    attribute2 = CustomProperty('data.another_name2')

<强>演示:

>>> class Foo():
...         pass
...
>>> bar = MyClass(Foo())
>>>
>>> bar.attribute1 = 10
inside __set__
>>> bar.attribute2 = 20
inside __set__
>>> bar.attribute1
inside __get__
10
>>> bar.attribute2
inside __get__
20
>>> bar.data.another_name1
10
>>> bar.data.another_name2
20

答案 1 :(得分:2)

这是问题的作者。我发现了一个非常简单的解决方案,但我不知道另一种方法。 (我顺便使用python 3.4。)

我将从遇到的问题开始。

首先,我想要完全覆盖财产,如下所示:

鉴于此课程

class A(object):
    def __init__(self):
        self._value = 42
    @property
    def value(self):
        return self._value

你可以通过做这样的事情来完全写出财产:

a = A()
A.value = 31 # This just redirects A.value from the @property to the int 31
a.value # Returns 31

问题是这是在类级而不是在实例级别完成的,所以如果我创建一个新的A实例,那么会发生这种情况:

a2 = A()
a.value # Returns 31, because the class itself was modified in the previous code block.

我希望返回a2._value,因为a2A()的全新实例,因此不应受我对a所做的影响。

对此的解决方案是使用新属性覆盖A.value,而不是我想要将实例_value分配给的任何内容。我了解到您可以创建一个新属性,使用特殊的gettersetterdeleter方法从旧属性中实例化自己(请参阅here)。所以我可以覆盖A的value属性并通过这样做为它设置一个setter:

def make_setter(name):
    def value_setter(self, val):
        setattr(self, name, val)
    return value_setter
my_setter = make_setter('_value')
A.value = A.value.setter(my_setter) # This takes the property defined in the above class and overwrites the setter with my_setter
setattr(A, 'value', getattr(A, 'value').setter(my_setter)) # This does the same thing as the line above I think so you only need one of them

只要原始类在原始类的属性定义中具有非常简单的东西(在这种情况下它只是return self._value),这一切都很好。然而,一旦你变得更加复杂,像我一样return self.data._value之类的东西,事情变得讨厌 - 就像@BrenBarn在我对我的帖子的评论中说的那样。我使用inspect.getsourcelines(A.value.fget)函数来获取包含返回值的源代码行并对其进行解析。如果我没有解析字符串,我提出了一个异常。结果看起来像这样:

def make_setter(name, attrname=None):
    def setter(self, val):
        try:
            split_name = name.split('.')
            child_attr = getattr(self, split_name[0])
            for i in range(len(split_name)-2):
                child_attr = getattr(child_attr, split_name[i+1])
            setattr(child_attr, split_name[-1], val)
        except:
            raise Exception("Failed to set property attribute {0}".format(name))

它似乎有效,但可能有错误。

现在问题是,如果事情失败该怎么办?这取决于你和这个问题的偏离轨道。就个人而言,我做了一些讨厌的事情,涉及创建一个继承自A的新类(让我们称之为B类)。然后,如果setter适用于A,它将适用于B的实例,因为A是基类。但是,如果它不起作用(因为A中定义的返回值是令人讨厌的),我在 B上运行settattr(B, name, val)。这通常会更改所有其他实例从B创建(就像在这篇文章中的第二个代码块中)但是我使用type('B', (A,), {})动态创建B并且只使用它一次,因此更改类本身不会影响其他任何内容。

我认为这里有很多黑魔法类型的东西,但是它很酷且在当天使用它非常通用我一直在使用它。这些都不是可复制的代码,但如果您理解它,那么您可以编写修改。

我真的希望/希望有更好的方法,但我不知道。也许从类创建的元类或描述符可以为你做一些不错的魔术,但我还不太了解它们还不确定。

评论赞赏!