2014-02-19 126 views
1

我创建了一个嵌套的namedtuples(练习我的不可变函数编程技巧)的数据结构,但我努力寻找一种简单的方法来替换嵌套namedtuples中的值。Python:简单的方法来替换嵌套的namedtuple属性?

比方说,我有一个数据结构是这样的:

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 

正如你所看到的,更改值在库存中的项目是一个相当痛苦,幸亏)被迫单独所有更新的值嵌套在下面,b)无法访问像+=这样的运算符。

如果我更新的库存中的物品是动态的,这要归功于getattr随处可见。

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

+0

为什么不使用字典或类? – jonrsharpe

+1

'nametuple's只是不适合你的正确数据容器。 – roippi

+0

@jonrsharpe我试图在这个项目上练习我的函数式编程技巧,而不可变的数据结构是函数式编程的核心组件。除此之外,如果可能的话,我希望从不变数据结构中清楚价值更新的来源,从而帮助减少错误计数。 – spiffytech

回答

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 

要改变它的任何部分,你必须重建一个完整的新对象。

+0

是的,我知道元组是不可变的,更新它们的值实际上构成了创建一个新的元组。我的问题是,这样做的语法是否必须像我提供的一样丑陋? – spiffytech

+0

您必须构建一个完整的新对象,因此您必须遍历旧对象的属性,并确定要更改的属性,然后重新创建整个对象。这将是丑陋的。 –

1

对不起,没有很好的方法去做你想做的事 - 你的解决方案几乎是最好的。

确实吮吸,没有任何错误,但据我所知,在即将发布的Python版本中没有改进计划。老实说,如果你想玩弄纯度和函数式编程结构,你应该看看另一种语言(Clojure和Haskell是最好的候选人)。 Python本身并不适用于强制执行不变性和纯FP,而核心开发人员根本不关心FP(至少就Python而言)。

1

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

Gist here,代码转载如下。

child参数是一个字符串,它是有点缺憾,但我想不出周围的一种方式,而且由于namedtuples已经有了自己的属性定义为字符串,它无论如何不是一个超级关闭基地的做法。

(至于这种困境是否只存在,因为Python是坏与不可变的数据(因为Python是不是函数式编程优化),请注意this StackOverflow answer表示哈斯克尔从一个非常类似的问题受到影响,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 
    ) 
相关问题