简体   繁体   English

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

[英]Python: Easy way to replace attribute on nested namedtuple?

I'm creating a data structure with nested namedtuples (practicing my immutable functional programming skills), but am struggling to find an easy way to replace values in nested namedtuples. 我正在创建带有嵌套namedtuple的数据结构(练习了我不变的函数编程技能),但是我正努力寻找一种简单的方法来替换嵌套namedtuple中的值。

Let's say I have a data structure like this: 假设我有一个像这样的数据结构:

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

As you can see, changing the value for an item in the inventory is quite a pain, thanks to a) being forced to individually update all of the layers the value is nested under, and b) not having access to operators like += . 如您所见,由于a)被迫单独更新值嵌套在其下的所有层,以及b)无法访问+=类的运算符,因此更改清单中某项的值是非常痛苦的。

This gets even uglier if the item in my inventory I'm updating is dynamic, thanks to calls to getattr being strewn everywhere. 如果我要更新的库存中的商品是动态的,则这变得更加丑陋,这要归功于到处散布着对getattr调用。

Is there an easier way to handle this? 有没有更简单的方法来解决这个问题?

Sorry, there's no nice way to do what you want -- your solution is pretty much the nicest one around. 抱歉,没有理想的方法来做您想做的事情-您的解决方案几乎是最好的一种。

It does suck, make no mistake, but as far as I can tell there are no improvements to that planned in upcoming releases of Python. 确实很烂,没有犯错,但是据我所知,在即将发布的Python版本中没有计划的改进。

Honestly, if you want to play around with purity and functional programming constructs, you should look at another language (Clojure and Haskell are the best candidates for that). 老实说,如果您想使用纯净的函数式编程结构,则应该考虑另一种语言(Clojure和Haskell是最适合的语言)。 Python doesn't lend itself too well to enforced immutability and pure FP, and the core developers don't care for FP at all (at least as far as Python is concerned). Python不太适合强制实施不变性和纯FP,并且核心开发人员根本不关心FP(至少就Python而言)。

I've created a function that will handle this problem a bit more cleanly. 我创建了一个函数,可以更干净地处理此问题。 It also doubles as a general-purpose replacement for namedtuple._replace() . 它还可以用作namedtuple._replace()的通用替代品。

Gist here , code reproduced below. 要点在这里 ,代码转载如下。

The child parameter is a string, which is kinda kludgy, but I couldn't think of a way around that, and since namedtuples already have their attributes defined as strings, it's not a super off-base approach anyway. child参数是一个字符串,有点儿笨拙,但是我想不出一种解决方法,而且由于namedtuples已经将其属性定义为字符串,因此无论如何这都不是一种超基础的方法。

(As for whether this dilemma only exists because Python is bad with immutable data (since Python isn't optimized for functional programming), please note that this StackOverflow answer indicates Haskell suffers from a very similar problem, and the Haskell library suggested arrived at solution that resembles my Python solution in spirit.) (关于这种困境是否仅由于Python不能处理不可变的数据而存在(因为Python并未针对函数式编程进行优化),请注意, 此StackOverflow答案表明Haskell遇到了非常类似的问题,建议使用Haskell库解决方案在本质上类似于我的Python解决方案。)

I'll wait bit to mark this as the answer, to give the Internet a chance to offer something more elegant. 我将稍等片刻,将其标记为答案,以使互联网有机会提供更精美的产品。

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
    )

Tuples are immutable, therefore you can't replace attributes on them, and you can't replace nested ones. 元组是不可变的,因此您不能替换它们的属性,也不能替换嵌套的属性。 They're good for creating objects that you don't want changes to be made to their attributes. 它们非常适合创建您不希望对其属性进行更改的对象。

>>> 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

And if you attempt to change an attribute: 如果您尝试更改属性:

>>> t.baz = 'foo'

Traceback (most recent call last):
  File "<pyshell#68>", line 1, in <module>
    t.baz = 'foo'
AttributeError: can't set attribute

To change any part of it, you'll have to reconstruct an entire new object. 要更改其中的任何部分,您必须重建一个新对象。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Python:除了用replace()以外,是否有一种简便的方法来替换字符串中的字符 - Python: Is there an easy way to replace a character in a string other than with replace() 使用Python> = 2.7将嵌套的namedtuple序列化为JSON - Serializing a nested namedtuple into JSON with Python >= 2.7 Python请求:替换http get request参数的简便方法? - python requests: easy way to replace http get request argument? 在Python中确定嵌套元组的嵌套级别的简便方法 - Easy way to determine a nesting level of nested tuples, in Python Python:循环并分配嵌套对象属性的简单方法? - Python: easy way to loop through and assign nested object attributes? 需要一种简单的方法来删除python中嵌套元组的重复项 - Need an easy way to remove duplicates of nested tuples in python 命名元组内命名元组的 Python 语法 - Python syntax for namedtuple inside a namedtuple 如何._replace() python3 namedtuple object 的多个元素的值? - How to ._replace() the value of multiple elements of a python3 namedtuple object? Python namedtuple:AttributeError:&#39;tuple&#39;对象没有属性&#39;end_pos&#39; - Python namedtuple: AttributeError: 'tuple' object has no attribute 'end_pos' #Python 为什么我不断收到此代码的命名元组属性错误? - #Python Why do I keep getting namedtuple attribute error for this code?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM