【问题标题】:Use a dict to access nested instances of classes in Python在 Python 中使用 dict 访问类的嵌套实例
【发布时间】:2012-03-01 20:55:35
【问题描述】:

我正在尝试在嵌套类中定义一个属性,然后稍后使用字符串或字符串列表访问它。这是我正在尝试做的代码

class MyNestedClass(object):
    def __init__(self):
        self.att1 = 5.

class MyClass(object):
    def __init__(self):
        self.my_nested_inst = MyNestedClass()

my_inst = MyClass()

我想更改my_inst.my_nested_inst.att1 的值,而我只有这样的列表:my_list = ['my_inst','my_nested_inst','att1']。

如果我使用这个:

vars(vars(vars()[my_list[0]])[my_list[1]])[my_list[2]]

这可行,但问题是我需要将它扩展到任意深度的嵌套实例。我想不出用 for 循环来完成这项工作的好方法。任何帮助是极大的赞赏。

另外,请注意,将字符串转换为全局命名空间中的变量名已得到很好的解决,但似乎没有一个答案适用于此。

EDIT1:我会尝试解释我为什么要这样做,如果我解释得不好,请告诉我。我正在使用 scipy.optimize.fmin,并且我只使用了 4 个参数进行优化。然而,我现在想扩展我的优化代码来处理任意数量的参数,其中一些是嵌套在类/实例层次结构中的几个层的属性。我希望能够在顶层创建一个列表或字典来告诉 fmin 如何解包参数数组以设置嵌套属性。

【问题讨论】:

  • 你应该告诉我们你真正想要做什么,因为除非你在做一些非常深奥的编程,否则“字符串输入”几乎总是错误的,而且可能有一个更简单的方法方法。
  • 参见 EDIT1。如果您能提出更好的方法来做到这一点,我将不胜感激。
  • 有人对 EDIT1 有任何想法吗?
  • 我写下了我的答案,因为我认为不可能做得更好。
  • 啊,我也不喜欢字符串输入,所以我希望有更好的东西,但这似乎效果很好。

标签: python namespaces nested-attributes


【解决方案1】:

您可以使用operator.attrgetter 通过指定包含点的属性名称来获取嵌套属性(需要 Python 2.6+):

在f = attrgetter('date.month')之后,调用f(b)返回b.date.month。

为方便起见,您可以创建一对辅助函数:

def get_nested_attr(vars_dict, attrs):
    inst = vars_dict[attrs[0]]
    return operator.attrgetter('.'.join(attrs[1:]))(inst)

def set_nested_attr(vars_dict, attrs, value):
    setattr(get_nested_attr(vars_dict, attrs[0:-1]), attrs[-1], value)

这是一个完整的示例(使用 Python 2.7.2 测试):

import operator

class MyNestedClass(object):
    def __init__(self):
        self.att1 = 5.

class MyClass(object):
    def __init__(self):
        self.my_nested_inst = MyNestedClass()

def get_nested_attr(vars_dict, attrs):
    inst = vars_dict[attrs[0]]
    return operator.attrgetter('.'.join(attrs[1:]))(inst)

def set_nested_attr(vars_dict, attrs, value):
    setattr(get_nested_attr(vars_dict, attrs[0:-1]), attrs[-1], value)


my_inst = MyClass()
my_list = ['my_inst','my_nested_inst','att1']

assert(my_inst.my_nested_inst.att1 == 5.)
set_nested_attr(vars(), my_list, 10.)
assert(my_inst.my_nested_inst.att1 == 10.)

【讨论】:

  • 可爱啊,这个答案基本上是说你想要的功能已经给你写好了;显然operator.attrgetter 将接受"date.month.etc" 形式的参数并返回obj.date.month.etc。我会在您的回答中引用文档的那一部分;在看到'.'.join(...) 之前,我几乎投了反对票。 =)
  • @ninjagecko:感谢您的反馈。我已更新我的回复以提高清晰度。
  • @ninjagecko,好电话。我不需要编写任何新函数,而且效果很好。我也只是使用一个字符串列表,不需要加入混乱。
  • 我遇到了一个有趣的问题。如果我做setattr(my_inst, 'my_nested_inst.att1', 44.),那么my_inst.__dict__['my_nested_inst.att1'] 是44。但my_inst.my_nested_inst.__dict__['att1'] 仍然是10。也许列表是解决这个问题的必要手段。
【解决方案2】:

假设您的vars... 中的最后一个) 是一个错字,那么您有多种选择来实现如下功能:

def namespace(names):
    """
        returns vars(vars(..vars(locals())[names[0]])..[names[-2]])[names[-1]]
    """
  • 使用reduce(有人在回答中写道,然后他删除了...)
  • 递归函数
  • 显式使用堆栈和 while 循环的函数

也许最容易理解的是递归实现:

def namespace(myList):
    if len(myList)==0:
        return locals()
    else:
        oneLevelUp = namespace(myList[:-1])
        return vars(oneLevelUp[myList[-1]])

reduce 实现同样优雅。以下是reduce 的工作原理:

>>> functools.reduce(lambda a,b:[a,b], range(4), 'x')
[[[['x', 0], 1], 2], 3]

实现:

def namespace(myList):
    return reduce(lambda ns,item:vars(ns[item]), myList, locals())

【讨论】:

    【解决方案3】:

    编辑: 正如 cmets 中所指出的,attrgetter 与我使用它的方式不同。你必须 使用 reduce 或循环。

    【讨论】:

    • 我认为你的语法可能不完全正确,但这是我想做的最优雅的方式,我会使用它!谢谢。另外,当我得到它的工作时,我会发布我最终得到的任何东西。
    • -1 的解释:这不起作用,因为 OP 有一个嵌套类链,其中必须引用 obj.obj2.obj3....attr。 attrgetter('a','b','c') 所做的只是生成元组 (x.a, x.b, x.c)。 setattr 将抛出一个元组正在传递给它的错误。如果你使用它,你需要将它与reduce 结合起来。
    猜你喜欢
    • 2020-10-26
    • 2012-03-08
    • 2016-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 2015-08-13
    相关资源
    最近更新 更多