Python: Easy Way To Replace Attribute On Nested Namedtuple?
Solution 1:
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()
.
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.
(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.)
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
)
Solution 2:
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.
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). 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).
Solution 3:
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.
Post a Comment for "Python: Easy Way To Replace Attribute On Nested Namedtuple?"