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:
- pytorch/pytorch#75982: API to determine if a torch.return_type is a “structseq”.
- metaopt/optree#29: Consider
PyStructSequence
types as internal node types
Here are the criteria I’m using:
-
namedtuple
: a subclass oftuple
and has an attribute_fields
. -
PyStructSequence
: a subclass oftuple
and has attributesn_*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
?