What’s the best way to check if a type is `PyStructSequence`

In Python documentation Struct Sequence Objects:

Struct sequence objects are the C equivalent of namedtuple() objects, i.e. a sequence whose items can also be accessed through attributes. To create a struct sequence, you first have to create a specific struct sequence type.

Both namedtuple and PyStructSequence types are subclasses of tuple and can access the items by attributes. However, they are not equivalent in some aspects. For example, they have different signatures to create a new instance:

structseq.__new__(sequence: Iterable, dict: Optional[dict] = None) -> structseq
namedtuple.__new__(field1, field2, ..., fieldN) -> namedtuple

values: Iterable[Any]
new_tuple = somestructseq(values)
new_tuple = somenamedtuple(*values)

For libraries involved with “pytree” manipulation, e.g., JAX, PyTorch, OpTree, we need to reconstruct the Python object with an iterable of leaf values. Due to this difference in the __new__ method, we need to handle namedtuple and PyStructSequence types differently.

Ref:

Here are the criteria I’m using:

  • namedtuple: a subclass of tuple and has an attribute _fields.
  • PyStructSequence: a subclass of tuple and has attributes n_*fields and cannot be subclassed.
import inspect

def is_namedtuple(obj: object | type) -> bool:
    cls = obj if inspect.isclass(cls) else type(obj)
    return (
        issubclass(cls, tuple)
        and isinstance(getattr(cls, '_fields', None), tuple)
        and all(isinstance(field, str) for field in cls._fields)
    )

def is_structseq(obj: object | type) -> bool:
    cls = obj if inspect.isclass(cls) else type(obj)
    if (
        cls.__base__ is tuple
        and isinstance(getattr(cls, 'n_sequence_fields', None), int)
        and isinstance(getattr(cls, 'n_fields', None), int)
        and isinstance(getattr(cls, 'n_unnamed_fields', None), int)
    ):
        try:

            class subcls(cls):
                pass

        except (
            TypeError,       # CPython
            AssertionError,  # PyPy
        ):
            return True

    return False
>>> is_structseq(torch.return_types.max)
True
>>> is_structseq(time.struct_time)
True

Would there be a C API, such as PyStructSequence_Check, to check if an object is an instance of PyStructSequence?

Have a look at PyObject_TypeCheck.

int PyObject_TypeCheck(PyObject *o, PyTypeObject *type) only works when I have the type object to check for. However, there can be an unlimited number of PyStructSequence types. E.g.:

type(sys.float_info)
type(sys.int_info)
type(sys.thread_info)
os.stat_result
time.struct_time
...

They are inherited directly from PyTuple_Type rather than have a common base type, e.g., PyStructSequence_Type. So I cannot use:

PyObject_TypeCheck(obj, &PyStructSequence_Type)

to determine whether an object is a struct sequence or not. Also

PyTuple_Check(obj)
PyObject_TypeCheck(obj, &PyTuple_Type)

will not fulfill my use case here for struct sequences.