This is really a question about how to make a particular weird type of subclass check. In particular, how to see if something is a type union or a typing.Union.
So here I am defining binary data structures much as one does a data class. The current exercise is making this support data structures whose internals can vary depending on an earlier data field. As such, the type annotation I naively stuck on the field looks like this:
@binclass
class MVHDBoxBody(FullBoxBody2):
''' An 'mvhd' Movie Header box - ISO14496 section 8.2.2.
'''
creation_time: Union[TimeStamp32, TimeStamp64]
and so on. Or of course its variant:
creation_time: TimeStamp32 | TimeStamp64
Over where the resulting class is constructed I’ve got this sanity check:
assert all(
issubclass(field.type, AbstractBinary) for field in fieldmap.values()
), 'all fields must subclass AbstractBinary'
Now, I had naively expected a union type to resolve as a subclass if (all?) its members satisfy the subclass check; TimeStamp32 and TimeStamp64 are both subclasses of AbstractBinary.
However, this raises a TypeError because the first argument is not a class.
How might I inspect the field.type to see if it’s a Union or (worse) a type union, whose type seems to be ABCMeta?
To introspect typing objects, you’ll want typing.get_origin() and get_args(). You’ll want to check the origin is either types.UnionType or typing.Union, and if so the args will be the child types.
Since the first argument of issubclass() is required to be a class, you should always test isinstance(x, type) before calling issubclass(x, y), unless you know that x is a class.
In my case I’m sanity checking a type annotation; conceptually I expect it to be a class. So I can’t use isinstance to see if something’s a subclass of my base class.
I see your point about isinstance in the general case, but since I’m examinining type annotations, a quick test:
>>> isinstance(int|float,typing.Union)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/typing.py", line 510, in __instancecheck__
raise TypeError(f"{self} cannot be used with isinstance()")
TypeError: typing.Union cannot be used with isinstance()
Currently my sanity check is looking like this (it is incomplete):
for field_name,field in fieldmap.items():
field_type=field.type
typing_class = get_origin(field_type)
if typing_class is None:
if not issubclass(field_type,AbstractBinary):
raise TypeError(f'field {field_name!r}, type {field_type} should be a subclass of
AbstractBinary')
elif typing_class is typing.Union:
It is going to check that Unions are made entirely of AbstractBinary subclasses or None (for optional fields). And a final else: to raise a TypeError for anything else.
You’ll also need to check for types.UnionType, which is the type of int | str. In Python 3.14 both Union[int, str] and int | str will evaluate to an instance of types.UnionType.