1

ネストされた名前付きタプルを使用してデータ構造を作成しています (不変の関数型プログラミングのスキルを練習しています) が、ネストされた名前付きタプルの値を簡単に置き換える方法を見つけるのに苦労しています。

次のようなデータ構造があるとします。

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

これを処理する簡単な方法はありますか?

4

3 に答える 3

2

この問題をもう少しきれいに処理する関数を作成しました。の汎用代替品としても機能しnamedtuple._replace()ます。

Gist here、コードを以下に再現。

パラメータはchild文字列であり、これはちょっと厄介ですが、それを回避する方法は考えられませんでした.namedtuplesはすでに文字列として定義された属性を持っているため、とにかく超オフベースのアプローチではありません.

(このジレンマが存在するのは、Python が不変データに対応していないため (Python は関数型プログラミングに最適化されていないため) だけであるかどうかについては、この StackOverflow の回答は、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
    )
于 2014-02-22T05:40:10.817 に答える
1

申し訳ありませんが、あなたが望むことを行うための良い方法はありません.あなたの解決策は、周りで最も素晴らしいものです.

間違いではありませんが、私が知る限り、Python の今後のリリースで計画されている改善はありません

正直なところ、純粋性と関数型プログラミングの構成要素を試してみたい場合は、別の言語を検討する必要があります (Clojure と Haskell が最適な候補です)。Python は、強制された不変性と純粋な FP にはあまり向いていません。コア開発者は FP をまったく気にしません (少なくとも Python に関する限り)。

于 2014-02-19T22:29:47.113 に答える
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

その一部を変更するには、新しいオブジェクト全体を再構築する必要があります。

于 2014-02-19T22:25:15.663 に答える