Package: reification
Reification (Python library)Reified generics in Python to get type parameters at runtimefromreificationimportReifiedclassReifiedList[T](Reified,list[T]):passxs=ReifiedList[int](range(10))print(xs)# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]print(xs.targ)# <class 'int'>RequirementsPython >= 3.12This library is written in pure Python and does not require any external modules.Installpip3installreificationAPIThe public API is defined under the root of thereificationpackage.Reified(class)Usage:from reification import ReifiedReifiedis a Mixin class designed to facilitate the creation of new types based on reified type parameters.This class is thread-safe so that inheriting classes can be used in multiple threads.You cannot directly instantiate this class.targ: type | tuple[type | Any, ...] | Any(class property)This class property represents the type argument(s) specified for the reified generic class.
If there's more than one type argument,targwill be a tuple containing each given type or type-like value.
If a type argument is not specified, it may returnAny.type_args: tuple[type | Any, ...](class property)This is another class property that carries the type argument(s) provided for the reified generic class.
Unliketarg,type_argsalways returns a tuple of the specified type arguments, even when there's only one type argument.
If no type arguments are given, it may contain a singleAny.__class_getitem__(cls, params: type | tuple[type | Any, ...] | Any) -> type(special class method, for Mixin)This method, which the class overrides, is used for creating new types each time it is called with distinct type arguments.
It serves a key role in handling parameterized generic classes, enabling the different identities on different type arguments of the same base class.Example Usage: Type-Checked Generic StackfromreificationimportReifiedclassReifiedStack[T](Reified):def__init__(self)->None:super().__init__()self.items:list[T]=[]defpush(self,item:T)->None:# We can do runtime checkifisinstance(item,self.targ):self.items.append(item)else:raiseTypeError()defpop(self)->T:ifself.items:returnself.items.pop()else:raiseIndexError("pop from empty stack")stack=ReifiedStack[str]()stack.push("spam")# OKstack.push(42)# raise TypeErrorTheReifiedStackclass created here is generic and derived from theReifiedbase class, and implements a simple stack withpushandpopmethods.In thepushmethod, we are checking at runtime if the item being pushed is of the specified generic type (this type is accessible via thetargattribute inherited fromReified).
If the type of the item does not match, aTypeErroris raised.In the example usage, we create an instance of the ReifiedStack class with a type argument as string. When we try to push a string"spam", the item is accepted since it matches with the stack's specified type argument.
However, when we try to push an integer42, aTypeErroris raised because the type of item does not match with the stack's type argument.This demonstrates the use of reified generics in Python where we can have runtime access to the type parameters, enabling us to type check dynamically at runtime.
This is useful in situations where we need to enforce type safety in our code or use type information at runtime.TypingWithReifiedgeneric types, type parameters are considered for understanding and respecting the typing semantics as much as possible.Python's nativeisinstancefunction works seamlessly with reified generic types.In context of reified generics:>>>isinstance(ReifiedList[int](),ReifiedList[int])TrueThe above expression returnsTrueas aReifiedListobject of integer type is indeed an instance of aReifiedListof integer type.On the other hand:>>>isinstance(ReifiedList[str](),ReifiedList[int])FalseThis returnsFalsebecause, while both the objects are instances of theReifiedListclass, their type parameters are different (string vs integer).Type EquivalenceIt treats two instances of theReifiedderived same class as equivalent only if the type parameters provided in their instantiation are exactly the same.
That is,ReifiedClass[T, ...] == ReifiedClass[S, ...]if and only if(T, ...) == (S, ...).>>>ReifiedList[float]==ReifiedList[float]True>>>ReifiedList[float]==ReifiedList[int]False>>>ReifiedList[tuple[int,str]]==ReifiedList[tuple[int,str]]True>>>ReifiedList[tuple[int,str]]==ReifiedList[tuple[int,float]]False>>>ReifiedList[ReifiedList[int]]==ReifiedList[ReifiedList[int]]True>>>ReifiedList[ReifiedList[int]]==ReifiedList[ReifiedList[str]]FalseSubtypingTheReifiedMixin supports nominal subtyping.Let typeAandBbeReifiedderived class.
TypeAis a subtype of typeBifA == BorAis directly derived fromB.AReifiedderived class with type parameters is considered a subtype of the same class without type parameters.
This means thatReifiedClass[T, ...]is a subtype ofReifiedClass.>>>issubclass(ReifiedList[int],ReifiedList[int])True>>>issubclass(ReifiedList,ReifiedList[int])False>>>issubclass(ReifiedList[int],ReifiedList)True>>>issubclass(ReifiedList[str],ReifiedList[int])False>>>classReifiedListSub(ReifiedList[int]):...pass...>>>issubclass(ReifiedListSub,ReifiedList[int])TrueType VarianceReifiedMixin only considers direct equivalence of type parameters for subtyping and does not cater for type variance.>>>issubclass(bool,int)True>>>classReifiedTuple[T](Reified,tuple[T]):...pass...>>>issubclass(ReifiedTuple[bool],ReifiedTuple[int])FalseLicenseWTFPL