Issubclass doesn't like type unions as argument 1

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?

Example:

>>> type(UInt32BE|UInt32BE)
<class 'abc.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.

1 Like

Ah, that looks very useful. Thank you.

Use isinstance() instead of issubclass().

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.

1 Like