Two more bits of bikeshedding… On Sep 5, 2019, at 12:12, Andrew Barnert via Python-ideas <python-ideas@python.org> wrote:
On Sep 5, 2019, at 04:24, Steven D'Aprano <steve@pearwood.info> wrote:
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.).
Also: Are runtime union types actually types, unlike the things in typing, or are they still non-type values that just have special handling as the second argument of isinstance and issubclass and maybe except statements? I’d expect issubclass(int|str, int|str|bytes) to be true, and issubclass(int|str, int) to be false, not for both of them to raise exceptions about the first argument not being a type. And I don’t see any reason that “things designed to be used as types for runtime type checks” shouldn’t be types. And their type (types.UnionType or whatever) a perfectly normal metaclass that inherits from type. But, other than the issubclass question, I’m having a hard time imagining anywhere that it would make a difference. While we’re at it: issubclass(int|str, types.UnionType) I think this should be false because UnionType is not like typing.Union (which is a typing.Generic, and therefore on its own it has to have the useless meaning of “the type that includes all values of any unions of any 1 or more types), it’s just a normal metaclass (with the normal meaning “the type of all union types”). Finally, do we still need the existing Generic, typing.Union, at all? If types.UnionType defines a __getitem__, we could just do Union = types.UnionType. Would this do the right thing in every case, or could it break anything? I don’t know; I think it’s safer to leave typing.Union as-is (except for defining its __getitem__ to return the | of all of its arguments, instead of inheriting the _SpecialForm.__getitem__ behavior).