[Python] How to create different type for class variable and instance variable

Discussão em 'Python' iniciado por Stack, Junho 16, 2025.

  1. Stack

    Stack Membro Participativo

    I want to explain to Pyright that my variables in class and instance have different types.

    I managed to overload __get__ method to achieve this, but now Pyright complains about initialization of instances.

    My files:

    module.py

    import dataclasses
    import typing as ty


    class CmpOperator:
    sign: str

    def __init__(self, field_name: str, value: ty.Any):
    self.field_name = field_name
    self.value = value


    class GtCmpOperator(CmpOperator):
    sign = ">"


    class Field[T]:
    def __init__(
    self,
    model_name: str,
    name: str,
    default_value: T,
    python_type: ty.Type[T],
    ):
    self.default_value = default_value
    self.model_name = model_name
    self.name = name
    self.python_type = python_type

    def __gt__(self, other: T) -> GtCmpOperator:
    return GtCmpOperator(f"{self.model_name}.{self.name}", other)


    def init_model(model: ty.Type[ty.Any]) -> None:
    if not dataclasses.is_dataclass(model):
    raise TypeError(f"Model `{model.__name__}` is not a dataclass")
    for field_name, field_type in ty.get_type_hints(model).items():
    field = Field(
    model.__name__,
    field_name,
    getattr(model, field_name, None),
    field_type,
    )
    setattr(model, field_name, field)


    def some_work[T](model: ty.Type[T], cmp: CmpOperator) -> T:
    assert isinstance(cmp, CmpOperator)



    module.pyi

    import typing as ty

    class CmpOperator:
    sign: str
    def __init__(self, field_name: str, value: ty.Any):
    self.field_name: str
    self.value: ty.Any

    class GtCmpOperator(CmpOperator): ...

    class Field[T]:
    def __init__(
    self,
    model_name: str,
    name: str,
    default_value: T,
    python_type: ty.Type[T],
    ):
    self.default_value: T
    self.model_name: str
    self.name: str
    self.python_type: ty.Type[T]
    @ty.overload
    def __get__(self, obj: None, owner: type) -> ty.Self: ...
    @ty.overload
    def __get__(self, obj: object, owner: type) -> T: ...
    @ty.overload
    def __set__(self, obj: None, value: Field) -> None: ...
    @ty.overload
    def __set__(self, obj: object, value: T) -> None: ...
    def __gt__(self, other: T) -> GtCmpOperator: ...

    def init_model(model: ty.Type[ty.Any]) -> None: ...
    def some_work[T](model: ty.Type[T], cmp: CmpOperator) -> T: ...



    main.py

    import typing as ty
    from dataclasses import dataclass

    from module import Field, init_model, some_work


    @dataclass
    class Model1:
    field: int = 0


    @dataclass
    class Model2:
    # Literal[0] is not assignable to Field[int]
    field: Field[int] = 0


    if __name__ == "__main__":
    init_model(Model1)
    assert isinstance(Model1.field, Field)
    assert isinstance(Model1().field, int)
    # "bool" is not assignable to "CmpOperator"
    obj = some_work(Model1, Model1.field > 0)
    # It`s fine
    obj = Model1(field=1)

    init_model(Model2)
    assert isinstance(Model2.field, Field)
    assert isinstance(Model2().field, int)
    # It`s fine
    obj = some_work(Model2, Model2.field > 0)
    # Literal[1] is not assignable to Field[int]
    obj = Model2(field=1)



    Asserts are true, but Pyright complains about something in both cases.

    Continue reading...

Compartilhe esta Página