attrs icon indicating copy to clipboard operation
attrs copied to clipboard

attr.evolve does not handle custom __init__s

Open Julian opened this issue 8 years ago • 4 comments

attr.assoc appears to have been deprecated in #169 and is now scheduled for removal in favor of attr.evolve, but the latter appears to not be able to do all that the former can do (and the documentation seems to indicate that this gap is intentional).

Is there then a reason to deprecate the former, it seems like it should stay, or the functionality should go somewhere else? I can think of workarounds to change the way the below works, but want to make sure it's being broken intentionally -- as-is, it does work correctly with attr.assoc.

Sample code:

import attr
@attr.s(init=False)
class F(object):
    args = attr.ib()
    other = attr.ib()

    def __init__(self, *args, **kwargs):
        self.args = args
        self.other = kwargs.pop("other")
        if kwargs:
            raise TypeError(kwargs)

    def assoc_new_other(self, new):
        return attr.assoc(self, other=new)

    def evolve_new_other(self, new):
        return attr.evolve(self, other=new)

f = F(1, 2, 3, other=12)

for which print f.assoc_new_other(new=13) succeeds (but warns will go away) and f.evolve_new_other(new=13) fails.

Julian avatar Jun 07 '17 21:06 Julian

Yes, the new evolve depends on an appropriate __init__ on the class. To keep the explanation short, we believe evolve should construct a fresh instance, and that is generally properly done by going through the initializer. If that's a little too abstract, practical reasons to use the initializer are validators (and maybe converters), and this way is actually significantly faster. You can see the original discussion at https://github.com/python-attrs/attrs/issues/116.

Now, if you need the old functionality for whatever reason, you can always use copy.copy at your discretion, taking on all the caveats. Maybe we could stick this into the docs to help folks out. Making your own assoc-like using copy.copy is very simple:

import copy
def my_assoc(inst, **kwargs):
    res = copy.copy(inst)
    for k, v in kwargs.items():
        setattr(res, k, v)
    return res

It's just that we basically don't want to encourage or maintain this.

Tinche avatar Jun 07 '17 23:06 Tinche

Understood, I don't really want copying (and didn't / don't expect something like assoc to be trying to do it) -- in that model [without copying], there isn't really any caveats with the old functionality in my case AFAICT. I can definitely understand not wanting to maintain some code though :) -- do you think the no-copy model would be something attrs could support?

If not, feel free to close obviously.

Julian avatar Jun 15 '17 16:06 Julian

Maybe we could just rename it so it’s clearer what it does? The function definitely isn’t a burden :) but I find the ambiguity problematic.

hynek avatar Jul 29 '17 10:07 hynek

I'm fine with renaming it obviously :P, but I don't have an immediate suggestion on a good name.

Julian avatar Jul 31 '17 18:07 Julian