2012-03-29 45 views
1

如何構建可以在字典或對象內設置任意值的函數?換句話說,一個可以設置任何值的函數。通過對象和子對象下降來設置層次結構中的任意項目或屬性

例如,假設你有一個這樣的詞典:

my_dict = { 
    'foo' : 'bar', 
    'subdict':{ 
    'sub1' : 1, 
    'sub2' : 2 
    } 
} 

我想創建一個setter功能,使用這樣的:

x = 'x' 
# I want to be able to set normal dictionary items 
setter(lambda val: val['foo'], my_dict, x) 

# And also set subdictionary items 
setter(lambda val: val['subdict']['sub1'], my_dict, x) 

# Setting items that don't exist would be great too 
setter(lambda val: val['new_item'], my_dict, x) 

# If my_dict was a class, I'd like to use the same function, like this 
setter(lambda val: val.myproperty, my_dict, x) 

# Would also be cool if it worked with lists 
my_list = [1, 2] 
setter(lambda val: val[1], my_list, x) 

我沒有看到一個明顯這樣做的方法。下面清楚地用簡單的方式不起作用:

def setter(get_attr_func, param, x): 
    # This doesn't work, but I'd like something like it 
    get_attr_func(param) = x 

我可以使用setattr來建立一些有關類屬性的作品。我可以構建其他可用於字典中的項目的其他內容。但是我不知道你會怎麼做它的子詞典或子對象。

答案不一定要用這種確切形式。相反,我只想編寫一個函數,在對象層次結構中設置任意的屬性/項目。

回答

1

我決定這是一個有趣的問題。首先,當您嘗試從中獲取項目或屬性時,此類將收集項目或屬性名稱。然後它可以使用這些收集的操作來獲取或設置對象層次結構上的對應值。它也有一些便利的方法來製作set_to_x函數,就像你的例子中的函數一樣。

類:

class LocationProxy(object): 
    def __init__(self, ops = None): 
     self.ops = ops or [] 
    def __getitem__(self, item): 
     self.ops.append((True, item)) 
     return self 
    def __getattr__(self, attr): 
     self.ops.append((False, attr)) 
     return self 
    @staticmethod 
    def iterativeget(obj, ops): 
     for isitem, key in ops: 
      obj = obj[key] if isitem else getattr(obj, key) 
     return obj 
    def get(self, obj): 
     return self.iterativeget(obj, self.ops) 
    def set(self, obj, value): 
     isitem, key = self.ops[-1] 
     obj = self.iterativeget(obj, self.ops[:-1]) 
     if isitem: 
      obj[key] = value 
     else: 
      setattr(obj, key, value) 
    def set_from_object(self, obj): 
     return lambda value: self.set(obj, value) 
    def set_to_value(self, value): 
     return lambda obj: self.set(obj, value) 
    @staticmethod 
    def object_value_setter(obj, value): 
     return lambda location: location.set(obj, value) 
    @staticmethod 
    def object_setter(obj): 
     return lambda location, value: location.set(obj, value) 
    @staticmethod 
    def value_setter(value): 
     return lambda location, obj: location.set(obj, value) 

讓我們準備一些數據,並提出一些setter函數:

# since you can't set attributes on a normal dict, use a subclass 
class MyDict(dict): pass 

my_dict = MyDict({ 
    'foo' : 'bar', 
    'subdict':{ 
    'sub1' : 1, 
    'sub2' : 2 
    } 
}) 

my_list = [1, 2] 

x = 'x' 

# we're going to set multiple things in my_dict to x, let's not repeat ourselves 
set_my_dict_to_x = LocationProxy.object_value_setter(my_dict, x) 

# we'll use set_to_x as you used it later on my_list 
set_to_x = LocationProxy.value_setter(x) 

現在讓我們來測試他們:

# you can assign a name to a setter to use multiple times 
foosetter = LocationProxy()['foo'] 

# set normal dictionary items 
set_my_dict_to_x(foosetter) 

# And also set subdictionary items 
set_my_dict_to_x(LocationProxy()['subdict']['sub1']) 

# Set items that don't exist 
set_my_dict_to_x(LocationProxy()['new_item']) 

print 'my_dict', my_dict 

# my_dict is a class, use the same function 
set_my_dict_to_x(LocationProxy().myproperty) 

print 'myproperty', my_dict.myproperty 

# it works with lists 
set_to_x(LocationProxy()[1], my_list) 

print 'my_list', my_list 
+0

不錯的嘗試。除了更改調用約定的子字典之外,它對所有值都很有用。也許這是不可能的。像C中的指針會幫助你。 – speedplane 2012-03-29 14:06:07

+0

@speedplane我用一個和你的問題中的例子完全一樣的工具取代了我的答案。它完全是類型不可知的 - 它可以與任何可以獲取的對象或設置屬性或項目。 – agf 2012-03-31 12:28:04

+0

這令人印象深刻。我發現你可以將任意一組操作串在一起,所以更有趣的東西可以工作,例如:'set_my_dict_to_x(LocationProxy()['item'] [0] .prop ['item2'] [3])''。很酷! – speedplane 2012-04-01 22:02:13

0

您可以測試類型(isinstance(dict)或isinstance(object))以執行setattr()或update(),但大多數情況下,如果您需要它,則意味着您的設計存在缺陷。你應該爲此做好兩個功能,或者找到另一種方式。

HTH

+0

我並不需要它,它只會讓代碼更漂亮。 – speedplane 2012-03-29 13:54:28