2015-06-17 142 views
1

我有代碼,別人寫的是這樣的:動態創建的類的所有屬性@ attribute.setter方法(Python)的

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方法添加到類中?

+1

如果這是它存在的唯一位置,那麼從函數內部提取'another_name'的東西沒有任何理智的方法。 – BrenBarn

回答

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

看起來你正在修改'MyClass'。我希望有一個解決方案,以便我不必修改其他人的課程。如果我這樣做了,我可以在他/她的類裏面寫@ attr.setter方法。不錯的裝飾,但! –

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分配給的內容。我瞭解到,您可以使用特殊的getter,setterdeleter方法(請參閱here)創建一個新屬性,該屬性使用舊屬性實例化自己。所以,我可以覆蓋A的價值屬性,並通過這樣做二傳手吧:

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是基類。但是,如果它不工作(因爲在規定的返回值是討厭的東西),我在類跑了settattr(B, name, val) B.這通常發生變化的B創建的所有其他情況下(如在第2碼在這篇文章中),但我動態地使用type('B', (A,), {})創建B,並且只用它一次,所以改變類本身對其他任何東西都沒有影響。

有很多的黑魔法類的東西會在這裏我想,但它是在當天很酷,很多才多藝左右,我用它了。這些都不是可複製代碼,但如果你理解它,那麼你可以編寫你的修改。

我真的希望/希望有更好的辦法,但我不知道其中之一。也許從類創建的元類或描述符可以爲你做出一些不錯的魔法,但我還不太瞭解它們。

評論贊賞!