Files
ruff/crates/ty_python_semantic/resources/mdtest/classes.md
2025-12-31 12:14:45 -05:00

1.8 KiB

Class definitions

__new__ return type

Python's __new__ method can return any type, not just an instance of the class. When __new__ returns a type that is not assignable to the class instance type, we use that return type directly.

__new__ returning a different type

class ReturnsInt:
    def __new__(cls) -> int:
        return 42

reveal_type(ReturnsInt())  # revealed: int

x: int = ReturnsInt()  # OK
y: ReturnsInt = ReturnsInt()  # error: [invalid-assignment]

__new__ returning a union type

class MaybeInt:
    def __new__(cls, value: str) -> "int | MaybeInt":
        try:
            return int(value)
        except ValueError:
            return object.__new__(cls)

reveal_type(MaybeInt("42"))  # revealed: int | MaybeInt

a: int | MaybeInt = MaybeInt("42")  # OK
b: int = MaybeInt("42")  # error: [invalid-assignment]

__new__ returning the class type

When __new__ returns the class type (or Self), the normal instance type is used.

class Normal:
    def __new__(cls) -> "Normal":
        return object.__new__(cls)

reveal_type(Normal())  # revealed: Normal

__new__ with no return type annotation

When __new__ has no return type annotation, we fall back to the instance type.

class NoAnnotation:
    def __new__(cls):
        return object.__new__(cls)

reveal_type(NoAnnotation())  # revealed: NoAnnotation

Deferred resolution of bases

Only the stringified name is deferred

If a class base contains a stringified name, only that name is deferred. Other names are resolved normally.

[environment]
python-version = "3.12"
from ty_extensions import reveal_mro

A = int

class G[T]: ...
class C(A, G["B"]): ...

A = str
B = bytes

reveal_mro(C)  # revealed: (<class 'C'>, <class 'int'>, <class 'G[bytes]'>, typing.Generic, <class 'object'>)