Take 2: Rules for subclassing Any

Ouch. In this case my own (often naive!) intuition thinks of Any as a “flag” that switches on some extra behavior for the class (and its subclasses) rather than as an actual class to be inserted in the MRO. So I guess that the proposed addition to the spec, moving Any to the end of the MRO, just isn’t a good enough explanation for the intuitively expected behavior.

There’s another problem with just using what I suggested over on the PR itself for __getattr__, it breaks the fundamental rule of when you can replace a fully typed class with Any and not create a new type error.

This is extremely reasonable. The attempt to solve it by reordering is an approximation of the more theory driven lower-bound approach that was clarified here but not what was chosen. A few of us in the thread reasoned would have the same behavior. It seems we all missed the interaction, and that it isn’t actually equivalent.

@mikeshardmind @carljm how bad of a delay would it be on intersections to make reconciling the lower-bound problem part of that pep instead, and clarify the behavior of Any as part of that? It seems to me that intersections using ordering rather than the lower bound will have the same problems.

I don’t know that we can or should adopt that definition as part of intersections. That particular definition is much more closely related to ideas in set-theoretic typing than we currently have language for in Python’s typing specification, doing this would be a much larger set of changes to the specification and would more significantly codify the behavior of gradual types as a class (possibly a good thing, but a larger change).

One example of something else that would be affected is Callable[..., Any]. In this ... would be an expression of potential args and kwargs having bounds Lower: (), Upper: (*args: Any, **kwargs: Any), and something that was glossed over before, but relevant here: being contravariant, the upper bound would be what was safe: (edit: safe to assign with, the lower bound safe to call with, sorry, expressing variance related stuff where we don’t have language for why it’s the case…), not the lower. (having worked this out, I need to go add detail to another current issue as well)

Adopting the definition has extremely predictable outcomes on intersections, but this might have surfaced as a large enough necessary change that might be worth an actual pep itself, and I think that those definitions also present a larger changeset for type checkers, but (as hinted at above) might solve multiple issues at once.

If the typing council would be comfortable with these changes being bundled initially, then split apart later if necessary, I could do it that way, but I don’t feel personally comfortable advocating for this being bundled with intersections rather than intersections relying on it.

Edit: I’m not going to pursue this approach right now, even if the typing council were comfortable with it, I’m not, to a degree that I wouldn’t do it.

Just to confirm, the mro in current python here is:

[<class '__main__.Class1'>, typing.Any, <class 'object'>]

But in the proposed version it’s:

[<class '__main__.Class1'>, <class 'object'>,  typing.Any]

That seems quite counter-intuitive to me, although I understand Any is a special case. For any other class it follows that:

class Class1(Class2):
    pass

[<class '__main__.Class1'>, <class '__main__.Class2'>, <class 'object'>]

Wouldn’t this be a breaking change for anything that relies on the mro order of Any (e.g. anything that uses super)? Apologies if I’m missing something from the other thread. I also thought it was a rule that all classes have object as the final item in the mro (everything inherits from object, including the Any class) - although this part I suppose is not a necessity.