Skip to content

Commit f10d050

Browse files
authored
Revert recursive evolve (#806)
* Revert "Recursively evolve nested attrs classes (#759)" * Add regression test * lol legacy python * Add newsfragment * Add a test to prevent inst -> dict replacement breaking
1 parent 24a2c1e commit f10d050

File tree

4 files changed

+34
-53
lines changed

4 files changed

+34
-53
lines changed

changelog.d/806.breaking.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
We had to revert the recursive feature for ``attr.evolve()`` because it broke some use-cases -- sorry!

docs/examples.rst

-21
Original file line numberDiff line numberDiff line change
@@ -618,27 +618,6 @@ In Clojure that function is called `assoc <https://clojuredocs.org/clojure.core/
618618
>>> i1 == i2
619619
False
620620

621-
This functions also works for nested ``attrs`` classes.
622-
Pass a (possibly nested) dictionary with changes for an attribute:
623-
624-
.. doctest::
625-
626-
>>> @attr.s(frozen=True)
627-
... class Child(object):
628-
... x = attr.ib()
629-
... y = attr.ib()
630-
>>> @attr.s(frozen=True)
631-
... class Parent(object):
632-
... child = attr.ib()
633-
>>> i1 = Parent(Child(1, 2))
634-
>>> i1
635-
Parent(child=Child(x=1, y=2))
636-
>>> i2 = attr.evolve(i1, child={"y": 3})
637-
>>> i2
638-
Parent(child=Child(x=1, y=3))
639-
>>> i1 == i2, i1.child == i2.child
640-
(False, False)
641-
642621

643622
Other Goodies
644623
-------------

src/attr/_funcs.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,7 @@ def evolve(inst, **changes):
319319
Create a new instance, based on *inst* with *changes* applied.
320320
321321
:param inst: Instance of a class with ``attrs`` attributes.
322-
:param changes: Keyword changes in the new copy. Nested ``attrs`` classes
323-
can be updated by passing (nested) dicts of values.
322+
:param changes: Keyword changes in the new copy.
324323
325324
:return: A copy of inst with *changes* incorporated.
326325
@@ -338,13 +337,8 @@ def evolve(inst, **changes):
338337
continue
339338
attr_name = a.name # To deal with private attributes.
340339
init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
341-
value = getattr(inst, attr_name)
342340
if init_name not in changes:
343-
# Add original value to changes
344-
changes[init_name] = value
345-
elif has(value):
346-
# Evolve nested attrs classes
347-
changes[init_name] = evolve(value, **changes[init_name])
341+
changes[init_name] = getattr(inst, attr_name)
348342

349343
return cls(**changes)
350344

tests/test_funcs.py

+31-24
Original file line numberDiff line numberDiff line change
@@ -598,43 +598,50 @@ class C(object):
598598

599599
assert evolve(C(1), a=2).a == 2
600600

601-
def test_recursive(self):
601+
def test_regression_attrs_classes(self):
602602
"""
603-
evolve() recursively evolves nested attrs classes when a dict is
604-
passed for an attribute.
603+
evolve() can evolve fields that are instances of attrs classes.
604+
605+
Regression test for #804
605606
"""
606607

607608
@attr.s
608-
class N2(object):
609-
e = attr.ib(type=int)
609+
class Cls1(object):
610+
param1 = attr.ib()
610611

611612
@attr.s
612-
class N1(object):
613-
c = attr.ib(type=N2)
614-
d = attr.ib(type=int)
613+
class Cls2(object):
614+
param2 = attr.ib()
615615

616-
@attr.s
617-
class C(object):
618-
a = attr.ib(type=N1)
619-
b = attr.ib(type=int)
616+
obj2a = Cls2(param2="a")
617+
obj2b = Cls2(param2="b")
620618

621-
c1 = C(N1(N2(1), 2), 3)
622-
c2 = evolve(c1, a={"c": {"e": 23}}, b=42)
619+
obj1a = Cls1(param1=obj2a)
623620

624-
assert c2 == C(N1(N2(23), 2), 42)
621+
assert Cls1(param1=Cls2(param2="b")) == attr.evolve(
622+
obj1a, param1=obj2b
623+
)
625624

626-
def test_recursive_dict_val(self):
625+
def test_dicts(self):
627626
"""
628-
evolve() only attempts recursion when the current value is an ``attrs``
629-
class. Dictionaries as values can be replaced like any other value.
627+
evolve() can replace an attrs class instance with a dict.
628+
629+
See #806
630630
"""
631631

632632
@attr.s
633-
class C(object):
634-
a = attr.ib(type=dict)
635-
b = attr.ib(type=int)
633+
class Cls1(object):
634+
param1 = attr.ib()
635+
636+
@attr.s
637+
class Cls2(object):
638+
param2 = attr.ib()
636639

637-
c1 = C({"spam": 1}, 2)
638-
c2 = evolve(c1, a={"eggs": 2}, b=42)
640+
obj2a = Cls2(param2="a")
641+
obj2b = {"foo": 42, "param2": 42}
639642

640-
assert c2 == C({"eggs": 2}, 42)
643+
obj1a = Cls1(param1=obj2a)
644+
645+
assert Cls1({"foo": 42, "param2": 42}) == attr.evolve(
646+
obj1a, param1=obj2b
647+
)

0 commit comments

Comments
 (0)