我正在使用嵌套的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
遍布各处。
有没有更简单的方法来解决这个问题?
答案 0 :(得分:1)
抱歉,没有什么好方法可以做你想做的事 - 你的解决方案几乎是最好的解决方案。
它确实糟透了,不要搞错,但据我所知,在即将发布的Python版本中没有改进计划。
老实说,如果你想要使用纯度和函数式编程结构,你应该看另一种语言(Clojure和Haskell是最好的候选者)。 Python并不能很好地强制实现不可变性和纯FP,核心开发人员根本不关心FP(至少就Python而言)。
答案 1 :(得分:1)
我已经创建了一个能够更清晰地处理这个问题的函数。它还兼作namedtuple._replace()
的通用替代品。
Gist here,代码如下。
child
参数是一个字符串,有点kludgy,但我无法想到解决这个问题,并且由于namedtuples已将其属性定义为字符串,因此它不是一种超级基础方法反正。
(至于这种困境是否只存在,因为Python对于不可变数据是不好的(因为Python没有针对函数式编程进行优化),请注意this StackOverflow answer表示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
)
答案 2 :(得分:0)
元组是不可变的,因此您无法替换它们上的属性,也无法替换嵌套的元组。它们适用于创建不希望对其属性进行更改的对象。
>>> 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
要更改它的任何部分,您必须重建一个完整的新对象。