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

为类中的所有属性动态创建@attribute.setter方法(Python),python,properties,attributes,setter,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

我有其他人这样写的代码:

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
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')
a = A()
A.value = 31 # This just redirects A.value from the @property to the int 31
a.value # Returns 31
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))
我想在运行时自动创建相应的属性设置器,这样就不必修改其他人的代码。属性设置器应如下所示:

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
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')
a = A()
A.value = 31 # This just redirects A.value from the @property to the int 31
a.value # Returns 31
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))

如何将这些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
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')
a = A()
A.value = 31 # This just redirects A.value from the @property to the int 31
a.value # Returns 31
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))
演示:

>>> 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

这是问题的作者。我发现了一个非常偷工减料的解决方案,但我不知道另一种方法。(顺便说一下,我正在使用python 3.4。)

我将从我遇到的问题开始

首先,我考虑完全覆盖该属性,如下所示:

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
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')
a = A()
A.value = 31 # This just redirects A.value from the @property to the int 31
a.value # Returns 31
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))
鉴于这一类

class A(object):
    def __init__(self):
        self._value = 42
    @property
    def value(self):
        return self._value
您可以通过这样做完全重写属性:

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
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')
a = A()
A.value = 31 # This just redirects A.value from the @property to the int 31
a.value # Returns 31
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))
问题是,这是在类级别而不是在实例级别完成的,因此,如果我创建了的新实例,则会发生以下情况:

a2 = A()
a.value # Returns 31, because the class itself was modified in the previous code block.
我希望它返回
a2.\u value
,因为
a2
a()
的一个全新实例,因此不应该受到我对
a
所做操作的影响

解决方法是用一个新属性覆盖
A.value
,而不是我想将实例
\u value
分配给的任何属性。我了解到,您可以使用特殊的
getter
setter
deleter
方法从旧属性创建一个新属性来实例化它自己(请参阅)。因此,我可以覆盖
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
只要原始类在原始类的属性定义中有一些非常简单的东西(在本例中,它只是
返回self.\u value
),这一切都很好。然而,一旦你变得更加复杂,像我一样返回self.data.\u value,事情就会变得糟糕——就像@BrenBarn在我的帖子中评论的那样。我使用
inspect.getsourcelines(A.value.fget)
函数获取包含返回值的源代码行,并对其进行解析。如果解析字符串失败,则引发异常。结果如下所示:

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
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')
a = A()
A.value = 31 # This just redirects A.value from the @property to the int 31
a.value # Returns 31
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))
它似乎可以工作,但可能有bug

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

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

我真的希望有更好的办法,但我不知道有没有。也许从类中创建的元类或描述符可以为您带来一些美妙的魔力,但我对它们的了解还不够确定


欢迎评论

如果函数中只存在
另一个\u名称
,那么就没有合理的方法从函数中提取它。看起来您正在修改
MyClass
。我希望找到一个解决方案,这样我就不必修改其他人的类。如果我这样做了,我可以在他/她的类中编写@attr.setter方法。不过装饰得不错!