通过对象和子对象下降以在层次结构中设置任意项或属性

时间:2012-03-29 06:39:37

标签: python oop dictionary

如何构建一个可以在字典或对象中设置任意值的函数?或者换句话说,一个可以设置传递给它的任何值的函数。

例如,假设您有一个这样的字典:

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来构建适用于类的属性的东西。我可以构建一些适用于词典中的项目的东西。但我不知道你将如何为子词典或子对象做这件事。

答案不一定是这种形式。相反,我只想编写一个在对象层次结构中设置任意属性/项的函数。

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

您可以测试类型(isinstance(dict)或isinstance(object))来执行setattr()或update(),但大多数时候,如果需要它,则意味着您的设计存在缺陷。你最好为此目的做两个功能,或者找另一种方式。

HTH