[Python] Initializing a field in generic class derived from generic type parameter in python

Discussão em 'Python' iniciado por Stack, Julho 10, 2025.

  1. Stack

    Stack Membro Participativo

    I have a Python generic class which uses T as generic Type parameter whose values can be B or C for this specific class ( default value is C when T is not passed )

    @dataclass
    class A[T: B,C = C]:
    var: BT | CT


    I want to set the type of var according to the T passed into the class A and also initialize var with an instance of BT or an instance of CT according to the T passed

    So for example:


    • if B is passed to the class like: a = A() then the type of var should be BT and should be initialized with BT()


      [*]
      if C is passed to the class a = A[C]() then the type of var should be CT and should be initialized with CT()


      [*]
      If nothing is passed to the class a = A() then also the type of var should be CTand should be initialized with CT()



    I am using this for sort of an ORM structure for a graph DB

    So the class A is a base class for Vertex in the DB and B and C are the data type class for the DB variables ( those are python class types like Int, FixedString ). and the type of the id of the DB model depends on the type passed as generic argument so if FixedString is passed the type of the id should be str and should be initialized by a randomised string and if it is passed as Int then type of the id shouldb be int and should be set to a randomized int upon object initialization

    I am also using pydantic so the class A is inherited from pydantic BaseModel but removed here just for similicity ( as i don't think the issue is related to pydantic)

    Environment:
    Python 3.13

    I tried several things but none of the seem to do both the type annotations correctly or set the value of the field correctly according to the passed generic type parameter correctly without breaking any other property

    Workarounds Tried

    • Overriding _init_

    @dataclass
    class A[T: (B, C) = C]:
    var: BT | CT

    def __init__(self) -> None:
    args = get_args(self)
    if args and args[0] is B:
    self.var = BT()
    elif args and args[0] is C:
    self.var = CT()
    else:
    error_message = f"Unsupported type: {args}"
    raise TypeError(error_message)


    This didn't worked ( neither for the type, as there is no way to define the type but also didn't worked for the initialzation of var

    • Field descriptors

    class Descriptor:
    @overload
    def __get__(self, instance: "A", owner: type["A"]) -> BT: ...
    @overload
    def __get__(self, instance: "A[C]", owner: type["A[C]"]) -> CT: ...

    def __get__(self, instance: "A | A[C] | None", owner: type["A | A[C]"]) -> BT | CT:
    args = get_args(owner)
    if args and args[0] is B:
    return BT()
    if args and args[0] is C:
    return CT()
    msg = f"Unsupported type {args} for A. Expected B or C."
    raise TypeError(msg)

    @dataclass
    class A[T: (B, C) = C]:
    var: Descriptor = Descriptor()


    This solved type hinting but didn't solved the automatatic intialization of variable and also i think this method initialized B or C again and again when var is accessed ( though i think that can be solved by a class variable inside Descriptor

    • Overriding _class_getitem_

    @dataclass
    class A[T: (B, C) = C]:
    var: BT | CT = field(init=False)
    _initializer: ClassVar[type[BT | CT]]

    @classmethod
    def __class_getitem__(cls, item: type[T]) -> type[Self]:
    if item is B:
    cls._initializer = BT
    elif item is C:
    cls._initializer = CT
    else:
    error_message = (
    f"Unsupported type: {item}. Expected B or C, got {item.__name__}"
    )
    raise TypeError(error_message)
    return cls

    def __init__(self) -> None:
    self.var = self._initializer()


    a = A[C]()
    print(a.var) # Should print an instance of CT
    print(get_args(A)) # breaks this


    This solves the initialization issue, but does not solve the type hiniting and also breaks the get_args and get_origin etc. functions so i am not very sure if it breaks anything else important like the behaviour on inheriting the class

    PS:
    One way i can think of is i can use descriptor for type hinting and __class_getitem__ for intialization, but the later part breaks get_args, get_origin etc. and i am not sure if it breaks any thing else if i wanted to inherit the class and this does seem very hacky

    So is there an easier way to have the field initialization as well as type annotation ( if possible ) ?

    Continue reading...

Compartilhe esta Página