Python:替换嵌套namedtuple上的属性的简单方法?

Python:替换嵌套namedtuple上的属性的简单方法?,python,namedtuple,Python,Namedtuple,我正在用嵌套的namedtuples创建一个数据结构(练习我不变的函数编程技能),但我正在努力找到一种简单的方法来替换嵌套namedtuples中的值 假设我有这样一个数据结构: from collections import namedtuple Root = namedtuple("Root", "inventory history") Inventory = namedtuple("Inventory", "item1 item2") Item = namedtuple("Item",

我正在用嵌套的namedtuples创建一个数据结构(练习我不变的函数编程技能),但我正在努力找到一种简单的方法来替换嵌套namedtuples中的值

假设我有这样一个数据结构:

from collections import namedtuple

Root = namedtuple("Root", "inventory history")
Inventory = namedtuple("Inventory", "item1 item2")
Item = namedtuple("Item", "name num")
Event = namedtuple("Event", "action item num")
r = Root(
    inventory=Inventory(
        item1=Item(name="item1", num=1),
        item2=Item(name="item2", num=2)
    ),
    history=(
        Event(action="buy", item="item1", num=1),
        Event(action="buy", item="item2", num=2)
    )
)

# Updating nested namedtuples is very clunky
num_bought = 4
r_prime = r._replace(
    history = r.history + (Event(action="buy", item="item2", num=num_bought),),
    inventory = r.inventory._replace(
        item2 = r.inventory.item2._replace(
            num = r.inventory.item2.num + num_bought
        )
    )
)

# Contrast with the ease of using a version of this based on mutable classes:
r.history += Event(action="buy", item="item2", num=num_bought),
r.inventory.item2.num += num_bought
如您所见,更改库存中某个项目的值是一件非常痛苦的事情,这要感谢a)被迫单独更新该值嵌套的所有层,b)无法访问诸如
+=
之类的操作符

由于调用
getattr
到处都是,如果我正在更新的库存中的物品是动态的,这会变得更加糟糕


有没有更简单的方法来处理这个问题?

元组是不可变的,因此不能替换元组上的属性,也不能替换嵌套的属性。它们非常适合创建您不希望更改其属性的对象

>>> import collections
>>> MyTuple = collections.namedtuple('MyTuple', 'foo bar baz')
>>> t = MyTuple(MyTuple('foo', 'bar', 'baz'), 'bar', 'baz')
>>> t
MyTuple(foo=MyTuple(foo='foo', bar='bar', baz='baz'), bar='bar', baz='baz')
>>> isinstance(t, tuple)
True
如果您试图更改属性:

>>> t.baz = 'foo'

Traceback (most recent call last):
  File "<pyshell#68>", line 1, in <module>
    t.baz = 'foo'
AttributeError: can't set attribute
t.baz='foo' 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 t、 baz=‘foo’ AttributeError:无法设置属性
要更改它的任何部分,您必须重新构建一个完整的新对象。

对不起,没有好的方法可以实现您想要的功能—您的解决方案几乎是最好的

这确实很糟糕,毫无疑问,但据我所知,在即将发布的Python版本中并没有计划对其进行任何改进


老实说,如果你想玩弄纯粹性和函数式编程结构,你应该看看另一种语言(Clojure和Haskell是最好的选择)。Python不太适合强制不变性和纯FP,核心开发人员根本不关心FP(至少就Python而言)。

我创建了一个函数,可以更清晰地处理这个问题。它还兼作
namedtuple.\u replace()
的通用替代品

,代码转载如下

child
参数是一个字符串,这有点麻烦,但我想不出解决方法,因为namedtuples已经将其属性定义为字符串,所以无论如何,这不是一种超基本的方法

(至于这一困境是否只是因为Python不适合使用不可变数据(因为Python没有针对函数式编程进行优化),请注意,这表明Haskell遇到了一个非常类似的问题,Haskell库提出的解决方案与我的Python解决方案在精神上类似。)

我会等待一点,以标记这是答案,给互联网一个机会,提供更优雅的东西

def attr_update(obj, child=None, _call=True, **kwargs):
    '''Updates attributes on nested namedtuples.
    Accepts a namedtuple object, a string denoting the nested namedtuple to update,
    and keyword parameters for the new values to assign to its attributes.

    You may set _call=False if you wish to assign a callable to a target attribute.

    Example: to replace obj.x.y.z, do attr_update(obj, "x.y", z=new_value).
    Example: attr_update(obj, "x.y.z", prop1=lambda prop1: prop1*2, prop2='new prop2')
    Example: attr_update(obj, "x.y", lambda z: z._replace(prop1=prop1*2, prop2='new prop2'))
    Example: attr_update(obj, alpha=lambda alpha: alpha*2, beta='new beta')
    '''
    def call_val(old, new):
        if _call and callable(new):
            new_value = new(old)
        else:
            new_value = new
        return new_value

    def replace_(to_replace, parts):
        parent = reduce(getattr, parts, obj)
        new_values = {k: call_val(getattr(parent, k), v) for k,v in to_replace.iteritems()}
        new_parent = parent._replace(**new_values)
        if len(parts) == 0:
            return new_parent
        else:
            return {parts[-1]: new_parent}

    if child in (None, ""):
        parts = tuple()
    else:
        parts = child.split(".")
    return reduce(
        replace_,
        (parts[:i] for i in xrange(len(parts), -1, -1)),
        kwargs
    )

为什么不使用字典或类?
nametuple
s不是适合您的数据容器。@jonrsharpe我正在尝试在这个项目上练习我的函数式编程技能,而不可变数据结构是函数式编程的核心组件。除此之外,如果可能的话,我希望受益于这样一个事实:不可变的数据结构非常清楚值更新的来源,有助于减少错误计数。是的,我理解元组是不可变的,更新它们的值实际上意味着创建一个新的元组。我的问题是,这样做的语法必须像我提供的那样难看吗?你必须构造一个完整的新对象,因此你必须遍历旧对象的属性,识别你要更改的属性,然后重新创建整个对象。这将是丑陋的。