On Sep 5, 2019, at 04:24, Steven D'Aprano <steve@pearwood.info> wrote:
But having said all that, I'm not sure that we should be rejecting this proposal on the basis of performance when we haven't got any working code to measure performance of :)
Good point. But then if we never get any realistic use cases, it’ll probably already be rejected for that reason. :) So this is just something we should keep in mind when considering examples, not something we should use as an open-and-shut argument on its own, but I think it was still worth raising by [whoever raised it that I quoted in snipped text].
isinstance is a wrapper around PyObject_IsInstance(obj, class_or_tuple), and if I'm reading the C code correctly, PyObject_IsInstance is roughly equivalent to this Python pseudo-code:
# Except in C, not Python def isinstance(obj, class_or_tuple): if type(class_or_tuple) is tuple: for C in class_or_tuple: if isinstance(obj, C): return True else: ...
Wow, I didn’t realize it handled tuples recursively, but the docs say it does, and of course the docs and the code are right: >>> isinstance(2, (dict, (int, str))) True Not really relevant to anything (Unions already explicitly handle that same thing at construction time: `Union[dict, Union[int, str]]` just returns `Union[dict, int, str]`), I’m just surprised that I never noticed that.
If Union is a built-in, we could have something like this:
def isinstance(obj, class_or_tuple): if type(class_or_tuple) is Union: class_or_tuple = class_or_tuple.__union_params__ # followed by the same code as above
typing.Union already defines .__union_params__ which returns a tuple of the classes used to construct the union, so in principle at least, there need be no significant performance hit from supporting Unions.
That’s a great point. And if type.__or__ is going to return a union type, we probably don’t want that to go lazily importing typing and pulling something out of it to call __getitem__ on, so we probably want that result to be builtin. I don’t think we need Union itself to be a builtin. But typing.Union.__getitem__ needs to return the same kind of builtin as type.__or__ (which is presumably exposed as something like types.UnionType, not only exposed in the typing module) instead of returning a typing._GenericAlias, or the whole point of this proposal (that `int|str == Union[int, str]`) breaks. That does raise some more bikeshedding questions (what the constructor for types.union accepts, or whether it refuses to be constructed and forces you to use type.__or__ or Union.__getitem__; what its repr looks like; etc.). And I suppose it also helps answer the question of why typing.Union is special for isinstance: it’s returning a completely different thing than every other generic’s __getitem__, so it’s not really the same kind of generic, so maybe nobody expects it to follow the same rules in the first place? More generally, it changes the proposal to “create a new runtime union type that works the way you’d expect, then add syntax for that, and change typing.Union to take advantage of it”, which sounds conceptually better than “add syntax to creating typing.Union static types, and then add special support to just this one kind of static type to make it usable at runtime”, even if they’re close to equivalent in practice.