Make __replace__ stop interfering with variance inference

You’re right, I had entirely the wrong semantics of replace in mind, so the above issue doesn’t exist like that. However, there still is some weirdness with the code. While the object that is being created is a valid member of C[A], the actual call to __replace__ is wrong in a sense. The calling function assumes that it looks like __replace__(self, *, attr: A) -> Self so passing attr=A() looks totally fine. But the passed object actually is a C[B] with __replace__(self, *, attr: B) -> Self.

But you might be right that this is a purely theoretical concern. We know that the actual function body of the replace method of C[B] is fine with an A being passed since it just creates a new instance of C[A]. Taking this reasoning further, I’m not sure if there even is the need to restrict the argument passed in the replace method at all. Having it be synthesised as __replace__[S](self, *, attr: S) -> C[S] would also work fine. Doing that would also mean that there’s no real need to special case the method further than just synthesizing it, which has to happen regardless.

Right, although I would add that the typecheckers would need to copy any constraints and bounds from the class-level typevar to the replace typevar.

I’m not advocating against a safer replace here, I’m just advocating for the @final approach to also work.

I can share some context: very recently I’ve spearheaded an effort to make all core models at my day job frozen. The end result leans very heavily on attrs.evolve and dataclasses.replace, significantly reducing readability. We still did it since the trade-offs are worth it for us, but having to switch to an even more verbose replacewould have made the readability problem even worse.

However, making the models also @final would have been no problem at all in 95% of cases, and arguably still the way to go in the other 5%. I think the old ‘favor composition over inheritance’ adage really is good fundamentals regardless.

The plural of anecdote is not data and all that, but still, this was my situation.

2 Likes