Note: See also this related section in the descriptor guide: Functions and methods.
Say we have a simple class C with a function definition f inside its body:
class C:
def f(self, x: int) -> str:
return "a"Whenever we access the f attribute through the class object itself (C.f) or through an instance
(C().f), this access happens via the descriptor protocol. Functions are (non-data) descriptors
because they implement a __get__ method. This is crucial in making sure that method calls work as
expected. In general, the signature of the __get__ method in the descriptor protocol is
__get__(self, instance, owner). The self argument is the descriptor object itself (f). The
passed value for the instance argument depends on whether the attribute is accessed from the class
object (in which case it is None), or from an instance (in which case it is the instance of type
C). The owner argument is the class itself (C of type Literal[C]). To summarize:
C.fis equivalent togetattr_static(C, "f").__get__(None, C)C().fis equivalent togetattr_static(C, "f").__get__(C(), C)
Here, inspect.getattr_static is used to bypass the descriptor protocol and directly access the
function attribute. The way the special __get__ method on functions works is as follows. In the
former case, if the instance argument is None, __get__ simply returns the function itself. In
the latter case, it returns a bound method object:
from inspect import getattr_static
reveal_type(getattr_static(C, "f")) # revealed: def f(self, x: int) -> str
# revealed: <method-wrapper '__get__' of function 'f'>
reveal_type(getattr_static(C, "f").__get__)
reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: def f(self, x: int) -> str
reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: bound method C.f(x: int) -> strIn conclusion, this is why we see the following two types when accessing the f attribute on the
class object C and on an instance C():
reveal_type(C.f) # revealed: def f(self, x: int) -> str
reveal_type(C().f) # revealed: bound method C.f(x: int) -> strA bound method is a callable object that contains a reference to the instance that it was called
on (can be inspected via __self__), and the function object that it refers to (can be inspected
via __func__):
bound_method = C().f
reveal_type(bound_method.__self__) # revealed: C
reveal_type(bound_method.__func__) # revealed: def f(self, x: int) -> strWhen we call the bound method, the instance is implicitly passed as the first argument (self):
reveal_type(C().f(1)) # revealed: str
reveal_type(bound_method(1)) # revealed: strWhen we call the function object itself, we need to pass the instance explicitly:
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `C`, found `Literal[1]`"
# error: [missing-argument]
C.f(1)
reveal_type(C.f(C(), 1)) # revealed: strWhen we access methods from derived classes, they will be bound to instances of the derived class:
class D(C):
pass
reveal_type(D().f) # revealed: bound method D.f(x: int) -> strIf we access an attribute on a bound method object itself, it will defer to types.MethodType:
reveal_type(bound_method.__hash__) # revealed: bound method MethodType.__hash__() -> intIf an attribute is not available on the bound method object, it will be looked up on the underlying
function object. We model this explicitly, which means that we can access __kwdefaults__ on bound
methods, even though it is not available on types.MethodType:
reveal_type(bound_method.__kwdefaults__) # revealed: dict[str, Any] | Noneclass Base:
def method_on_base(self, x: int | None) -> str:
return "a"
class Derived(Base):
def method_on_derived(self, x: bytes) -> tuple[int, str]:
return (1, "a")
reveal_type(Base().method_on_base(1)) # revealed: str
reveal_type(Base.method_on_base(Base(), 1)) # revealed: str
Base().method_on_base("incorrect") # error: [invalid-argument-type]
Base().method_on_base() # error: [missing-argument]
Base().method_on_base(1, 2) # error: [too-many-positional-arguments]
reveal_type(Derived().method_on_base(1)) # revealed: str
reveal_type(Derived().method_on_derived(b"abc")) # revealed: tuple[int, str]
reveal_type(Derived.method_on_base(Derived(), 1)) # revealed: str
reveal_type(Derived.method_on_derived(Derived(), b"abc")) # revealed: tuple[int, str]reveal_type(True.bit_length()) # revealed: int
reveal_type(True.as_integer_ratio()) # revealed: tuple[int, Literal[1]]reveal_type((42).bit_length()) # revealed: intreveal_type("abcde".find("abc")) # revealed: int
reveal_type("foo".encode(encoding="utf-8")) # revealed: bytes
"abcde".find(123) # error: [invalid-argument-type]reveal_type(b"abcde".startswith(b"abc")) # revealed: boolfrom typing_extensions import LiteralString
def f(s: LiteralString) -> None:
reveal_type(s.find("a")) # revealed: intdef f(t: tuple[int, str]) -> None:
reveal_type(t.index("a")) # revealed: intfrom typing import Any
class A:
def f(self) -> int:
return 1
class B:
def f(self) -> str:
return "a"
def f(a_or_b: A | B, any_or_a: Any | A):
reveal_type(a_or_b.f) # revealed: (bound method A.f() -> int) | (bound method B.f() -> str)
reveal_type(a_or_b.f()) # revealed: int | str
reveal_type(any_or_a.f) # revealed: Any | (bound method A.f() -> int)
reveal_type(any_or_a.f()) # revealed: Any | int[environment]
python-version = "3.12"type IntOrStr = int | str
reveal_type(IntOrStr.__or__) # revealed: bound method TypeAliasType.__or__(right: Any, /) -> _SpecialFormVery few methods are defined on object, None, and other types not disjoint from None. However,
descriptor-binding behavior works on these types in exactly the same way as descriptor binding on
other types. This is despite the fact that None is used as a sentinel internally by the descriptor
protocol to indicate that a method was accessed on the class itself rather than an instance of the
class:
from typing import Protocol, Literal
from ty_extensions import AlwaysFalsy
class Foo: ...
class SupportsStr(Protocol):
def __str__(self) -> str: ...
class Falsy(Protocol):
def __bool__(self) -> Literal[False]: ...
def _(a: object, b: SupportsStr, c: Falsy, d: AlwaysFalsy, e: None, f: Foo | None):
a.__str__()
b.__str__()
c.__str__()
d.__str__()
e.__str__()
f.__str__()from typing_extensions import assert_type, Any
class SubclassOfAny(Any):
def method(self) -> int:
return 1
a = SubclassOfAny()
assert_type(a.method(), int)
assert_type(a.non_existing_method(), Any)The __get__ method on types.FunctionType has the following overloaded signature in typeshed:
from types import FunctionType, MethodType
from typing import overload
@overload
def __get__(self, instance: None, owner: type, /) -> FunctionType: ...
@overload
def __get__(self, instance: object, owner: type | None = None, /) -> MethodType: ...Here, we test that this signature is enforced correctly:
from inspect import getattr_static
class C:
def f(self, x: int) -> str:
return "a"
method_wrapper = getattr_static(C, "f").__get__
reveal_type(method_wrapper) # revealed: <method-wrapper '__get__' of function 'f'>
# All of these are fine:
method_wrapper(C(), C)
method_wrapper(C())
method_wrapper(C(), None)
method_wrapper(None, C)
reveal_type(object.__str__.__get__(object(), None)()) # revealed: str
# TODO: passing `None` without an `owner` argument fails at runtime.
# Ideally we would emit a diagnostic here:
method_wrapper(None)
# Passing something that is not assignable to `type` as the `owner` argument is an
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
method_wrapper(None, 1)
# TODO: passing `None` as the `owner` argument when `instance` is `None` fails at runtime.
# Ideally we would emit a diagnostic here.
method_wrapper(None, None)
# Calling `__get__` without any arguments is an
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
method_wrapper()
# Calling `__get__` with too many positional arguments is an
# error: [no-matching-overload] "No overload of method wrapper `__get__` of function `f` matches arguments"
method_wrapper(C(), C, "one too many")When a method is accessed on a class object, it is looked up on the metaclass if it is not found on the class itself. This also creates a bound method that is bound to the class object itself:
from __future__ import annotations
class Meta(type):
def f(cls, arg: int) -> str:
return "a"
class C(metaclass=Meta):
pass
reveal_type(C.f) # revealed: bound method <class 'C'>.f(arg: int) -> str
reveal_type(C.f(1)) # revealed: strThe method f cannot be accessed from an instance of the class:
# error: [unresolved-attribute] "Object of type `C` has no attribute `f`"
C().fA metaclass function can be shadowed by a method on the class:
from typing import Any, Literal
class D(metaclass=Meta):
def f(arg: int) -> Literal["a"]:
return "a"
reveal_type(D.f(1)) # revealed: Literal["a"]If the class method is possibly missing, we union the return types:
def flag() -> bool:
return True
class E(metaclass=Meta):
if flag():
def f(arg: int) -> Any:
return "a"
reveal_type(E.f(1)) # revealed: str | AnyWhen a @classmethod attribute is accessed, it returns a bound method object, even when accessed on
the class object itself:
from __future__ import annotations
class C:
@classmethod
def f(cls: type[C], x: int) -> str:
return "a"
reveal_type(C.f) # revealed: bound method <class 'C'>.f(x: int) -> str
reveal_type(C().f) # revealed: bound method type[C].f(x: int) -> strThe cls method argument is then implicitly passed as the first argument when calling the method:
reveal_type(C.f(1)) # revealed: str
reveal_type(C().f(1)) # revealed: strWhen the class method is called incorrectly, we detect it:
C.f("incorrect") # error: [invalid-argument-type]
C.f() # error: [missing-argument]
C.f(1, 2) # error: [too-many-positional-arguments]If the cls parameter is wrongly annotated, we emit an error at the call site:
class D:
@classmethod
def f(cls: D):
# This function is wrongly annotated, it should be `type[D]` instead of `D`
pass
# error: [invalid-argument-type] "Argument to bound method `f` is incorrect: Expected `D`, found `<class 'D'>`"
D.f()When a class method is accessed on a derived class, it is bound to that derived class:
class Derived(C):
pass
reveal_type(Derived.f) # revealed: bound method <class 'Derived'>.f(x: int) -> str
reveal_type(Derived().f) # revealed: bound method type[Derived].f(x: int) -> str
reveal_type(Derived.f(1)) # revealed: str
reveal_type(Derived().f(1)) # revealed: strAccessing a @classmethod-decorated function at runtime returns a classmethod object. We
currently don't model this explicitly:
from inspect import getattr_static
class C:
@classmethod
def f(cls): ...
reveal_type(getattr_static(C, "f")) # revealed: def f(cls) -> Unknown
# revealed: <method-wrapper '__get__' of function 'f'>
reveal_type(getattr_static(C, "f").__get__)But we correctly model how the classmethod descriptor works:
reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: bound method <class 'C'>.f() -> Unknown
reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: bound method <class 'C'>.f() -> Unknown
reveal_type(getattr_static(C, "f").__get__(C())) # revealed: bound method type[C].f() -> UnknownThe owner argument takes precedence over the instance argument:
reveal_type(getattr_static(C, "f").__get__("dummy", C)) # revealed: bound method <class 'C'>.f() -> UnknownImplicit cls parameters should stay in class-object space when a classmethod is accessed through
type[T]:
from typing import Any, Type, TypeVar
Model = TypeVar("Model", bound="BaseModel")
class BaseModel:
@classmethod
def normalize(cls, obj: Any) -> Any:
return obj
@classmethod
def parse_obj(cls: Type[Model], obj: Any) -> Model:
reveal_type(cls.normalize) # revealed: bound method type[Model@parse_obj].normalize(obj: Any) -> Any
cls.normalize(obj)
cls.normalize.__func__(cls, obj)
# error: [invalid-argument-type]
cls.normalize.__func__(cls(), obj)
return cls()[environment]
python-version = "3.12"When a @classmethod is additionally decorated with another decorator, it is still treated as a
class method:
def does_nothing[T](f: T) -> T:
return f
class C:
@classmethod
@does_nothing
def f1(cls, x: int) -> str:
return "a"
@does_nothing
@classmethod
def f2(cls, x: int) -> str:
return "a"
reveal_type(C.f1(1)) # revealed: str
reveal_type(C().f1(1)) # revealed: str
reveal_type(C.f2(1)) # revealed: str
reveal_type(C().f2(1)) # revealed: strWhen a classmethod is decorated with a decorator that returns a callable type (like
@contextmanager), Self in the return type should correctly resolve to the subclass when accessed
on a derived class.
from contextlib import contextmanager
from typing import Iterator
from typing_extensions import Self
class Base:
@classmethod
@contextmanager
def create(cls) -> Iterator[Self]:
yield cls()
class Child(Base): ...
reveal_type(Base.create()) # revealed: _GeneratorContextManager[Base, None, None]
with Base.create() as base:
reveal_type(base) # revealed: Base
reveal_type(Base().create()) # revealed: _GeneratorContextManager[Base, None, None]
with Base().create() as base:
reveal_type(base) # revealed: Base
reveal_type(Child.create()) # revealed: _GeneratorContextManager[Child, None, None]
with Child.create() as child:
reveal_type(child) # revealed: Child
reveal_type(Child().create()) # revealed: _GeneratorContextManager[Child, None, None]
with Child().create() as child:
reveal_type(child) # revealed: ChildThe __init_subclass__ method is implicitly a classmethod:
class Base:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.custom_attribute: int = 0
class Derived(Base):
pass
reveal_type(Derived.custom_attribute) # revealed: intSubclasses must be constructed with arguments matching the required arguments of the base
__init_subclass__ method.
class Empty: ...
class RequiresArg:
def __init_subclass__(cls, arg: int): ...
class NoArg:
def __init_subclass__(cls): ...
# Single-base definitions
class MissingArg(RequiresArg): ... # error: [missing-argument]
class InvalidType(RequiresArg, arg="foo"): ... # error: [invalid-argument-type]
class Valid(RequiresArg, arg=1): ...
# error: [missing-argument]
# error: [unknown-argument]
class IncorrectArg(RequiresArg, not_arg="foo"):
a = 1
b = 2
c = 3
d = 4
e = 5
f = 6
g = 7
h = 8
i = 9
j = 10
class NotCallableInitSubclass:
__init_subclass__ = None
# TODO: this should be an error because `__init_subclass__` on the superclass is not callable
class Bad(NotCallableInitSubclass):
a = 1
b = 2
c = 3The metaclass keyword is ignored, as it has special meaning and is not passed to
__init_subclass__ at runtime.
class Base:
def __init_subclass__(cls, arg: int): ...
class Valid(Base, arg=5, metaclass=object): ...
# error: [invalid-argument-type]
class Invalid(Base, metaclass=type, arg="foo"): ...Overload matching is performed correctly:
from typing import Literal, overload
class Base:
@overload
def __init_subclass__(cls, mode: Literal["a"], arg: int) -> None: ...
@overload
def __init_subclass__(cls, mode: Literal["b"], arg: str) -> None: ...
def __init_subclass__(cls, mode: str, arg: int | str) -> None: ...
class Valid(Base, mode="a", arg=5): ...
class Valid(Base, mode="b", arg="foo"): ...
# error: [no-matching-overload]
class InvalidType(Base, mode="b", arg=5):
a = 1
b = 2
c = 3
d = 4
e = 5For multiple inheritance, the first resolved __init_subclass__ method is used.
[environment]
python-version = "3.12"class Empty: ...
class RequiresArg:
def __init_subclass__(cls, arg: int): ...
class NoArg:
def __init_subclass__(cls): ...
class Valid(NoArg, RequiresArg): ...
class MissingArg(RequiresArg, NoArg): ... # error: [missing-argument]
class InvalidType(RequiresArg, NoArg, arg="foo"): ... # error: [invalid-argument-type]
class Valid(RequiresArg, NoArg, arg=1): ...
# Ensure base class without __init_subclass__ is ignored
class Valid(Empty, NoArg): ...
class Valid(Empty, RequiresArg, NoArg, arg=1): ...
class MissingArg(Empty, RequiresArg): ... # error: [missing-argument]
class MissingArg(Empty, RequiresArg, NoArg): ... # error: [missing-argument]
class InvalidType(Empty, RequiresArg, NoArg, arg="foo"): ... # error: [invalid-argument-type]
# Multiple inheritance with args
class Base(Empty, RequiresArg, NoArg, arg=1): ...
class Valid(Base, arg=1): ...
class MissingArg(Base): ... # error: [missing-argument]
class InvalidType(Base, arg="foo"): ... # error: [invalid-argument-type]Keyword splats are allowed if their type can be determined:
from typing import TypedDict
class RequiresKwarg:
def __init_subclass__(cls, arg: int): ...
class WrongArg(TypedDict):
kwarg: int
class InvalidType(TypedDict):
arg: str
wrong_arg: WrongArg = {"kwarg": 5}
# error: [missing-argument]
# error: [unknown-argument]
class MissingArg(RequiresKwarg, **wrong_arg): ...
invalid_type: InvalidType = {"arg": "foo"}
# error: [invalid-argument-type]
class InvalidType(RequiresKwarg, **invalid_type): ...So are generics:
from typing import Generic, TypeVar, Literal, overload
class Base[T]:
def __init_subclass__(cls, arg: T): ...
class Valid(Base[int], arg=1): ...
class InvalidType(Base[int], arg="x"): ... # error: [invalid-argument-type]
# Old generic syntax
T = TypeVar("T")
class Base(Generic[T]):
def __init_subclass__(cls, arg: T) -> None: ...
class Valid(Base[int], arg=1): ...
class InvalidType(Base[int], arg="x"): ... # error: [invalid-argument-type]When a @staticmethod attribute is accessed, it returns the underlying function object. This is
true whether it's accessed on the class or on an instance of the class.
from __future__ import annotations
class C:
@staticmethod
def f(x: int) -> str:
return "a"
reveal_type(C.f) # revealed: def f(x: int) -> str
reveal_type(C().f) # revealed: def f(x: int) -> strThe method can then be called like a regular function from either the class or an instance, with no implicit first argument passed.
reveal_type(C.f(1)) # revealed: str
reveal_type(C().f(1)) # revealed: strWhen the static method is called incorrectly, we detect it:
C.f("incorrect") # error: [invalid-argument-type]
C.f() # error: [missing-argument]
C.f(1, 2) # error: [too-many-positional-arguments]When a static method is accessed on a derived class, it behaves identically:
class Derived(C):
pass
reveal_type(Derived.f) # revealed: def f(x: int) -> str
reveal_type(Derived().f) # revealed: def f(x: int) -> str
reveal_type(Derived.f(1)) # revealed: str
reveal_type(Derived().f(1)) # revealed: strAssigning a staticmethod(...) object directly in the class body should preserve the callable
behavior of the wrapped function when accessed on both classes and instances.
def foo(*args, **kwargs) -> None:
print("foo", args, kwargs)
class A:
__call__ = staticmethod(foo)
bar = staticmethod(foo)
a = A()
a()
a.bar()
a(5)
a.bar(5)
a(x=10)
a.bar(x=10)
A.bar()
A.bar(5)
A.bar(x=10)from inspect import getattr_static
class C:
@staticmethod
def f(): ...Accessing the staticmethod as a static member. This will reveal the raw function, as staticmethod
is transparent when accessed via getattr_static.
reveal_type(getattr_static(C, "f")) # revealed: def f() -> UnknownThe __get__ of a staticmethod object simply returns the underlying function. It ignores both the
instance and owner arguments.
reveal_type(getattr_static(C, "f").__get__(None, C)) # revealed: def f() -> Unknown
reveal_type(getattr_static(C, "f").__get__(C(), C)) # revealed: def f() -> Unknown
reveal_type(getattr_static(C, "f").__get__(C())) # revealed: def f() -> Unknown
reveal_type(getattr_static(C, "f").__get__("dummy", C)) # revealed: def f() -> Unknown[environment]
python-version = "3.12"When a @staticmethod is additionally decorated with another decorator, it is still treated as a
static method:
from __future__ import annotations
def does_nothing[T](f: T) -> T:
return f
class C:
@staticmethod
@does_nothing
def f1(x: int) -> str:
return "a"
@does_nothing
@staticmethod
def f2(x: int) -> str:
return "a"
reveal_type(C.f1(1)) # revealed: str
reveal_type(C().f1(1)) # revealed: str
reveal_type(C.f2(1)) # revealed: str
reveal_type(C().f2(1)) # revealed: strWhen a @staticmethod is decorated with @contextmanager, accessing it from an instance should not
bind self:
from contextlib import contextmanager
from collections.abc import Iterator
class D:
@staticmethod
@contextmanager
def ctx(num: int) -> Iterator[int]:
yield num
def use_ctx(self) -> None:
# Accessing via self should not bind self
with self.ctx(10) as x:
reveal_type(x) # revealed: int
# Accessing via class works
reveal_type(D.ctx(5)) # revealed: _GeneratorContextManager[int, None, None]
# Accessing via instance should also work (no self-binding)
reveal_type(D().ctx(5)) # revealed: _GeneratorContextManager[int, None, None]__new__ is an implicit @staticmethod; accessing it on an instance does not bind the cls
argument:
from typing_extensions import Self
reveal_type(object.__new__) # revealed: def __new__[Self](cls) -> Self
reveal_type(object().__new__) # revealed: def __new__[Self](cls) -> Self
# revealed: Overload[[Self](cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self, [Self](cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self]
reveal_type(int.__new__)
# revealed: Overload[[Self](cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self, [Self](cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self]
reveal_type((42).__new__)
class X:
def __init__(self, val: int): ...
def make_another(self) -> Self:
reveal_type(self.__new__) # revealed: def __new__[Self](cls) -> Self
return self.__new__(type(self))Some builtin functions and methods are heavily special-cased by ty. This mdtest checks that various properties are understood correctly for these functions and methods.
import types
from typing import Callable
from ty_extensions import static_assert, RegularCallableTypeOf, is_assignable_to, TypeOf
def f(obj: type) -> None: ...
class MyClass:
@property
def my_property(self) -> int:
return 42
@my_property.setter
def my_property(self, value: int | str) -> None: ...
static_assert(is_assignable_to(types.FunctionType, Callable))
# revealed: <wrapper-descriptor '__get__' of 'function' objects>
reveal_type(types.FunctionType.__get__)
static_assert(is_assignable_to(TypeOf[types.FunctionType.__get__], Callable))
# revealed: def f(obj: type) -> None
reveal_type(f)
static_assert(is_assignable_to(TypeOf[f], Callable))
# revealed: <method-wrapper '__get__' of function 'f'>
reveal_type(f.__get__)
static_assert(is_assignable_to(TypeOf[f.__get__], Callable))
# revealed: def __call__(self, *args: Any, **kwargs: Any) -> Any
reveal_type(types.FunctionType.__call__)
static_assert(is_assignable_to(TypeOf[types.FunctionType.__call__], Callable))
# revealed: <method-wrapper '__call__' of function 'f'>
reveal_type(f.__call__)
static_assert(is_assignable_to(TypeOf[f.__call__], Callable))
# revealed: <wrapper-descriptor '__get__' of 'property' objects>
reveal_type(property.__get__)
static_assert(is_assignable_to(TypeOf[property.__get__], Callable))
# revealed: property
reveal_type(MyClass.my_property)
static_assert(is_assignable_to(TypeOf[property], Callable))
static_assert(not is_assignable_to(TypeOf[MyClass.my_property], Callable))
# revealed: <method-wrapper '__get__' of property 'my_property'>
reveal_type(MyClass.my_property.__get__)
static_assert(is_assignable_to(TypeOf[MyClass.my_property.__get__], Callable))
# revealed: <wrapper-descriptor '__set__' of 'property' objects>
reveal_type(property.__set__)
static_assert(is_assignable_to(TypeOf[property.__set__], Callable))
# revealed: <method-wrapper '__set__' of property 'my_property'>
reveal_type(MyClass.my_property.__set__)
static_assert(is_assignable_to(TypeOf[MyClass.my_property.__set__], Callable))
# revealed: def startswith(self, prefix: str | tuple[str, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool
reveal_type(str.startswith)
static_assert(is_assignable_to(TypeOf[str.startswith], Callable))
# revealed: <method-wrapper 'startswith' of string 'foo'>
reveal_type("foo".startswith)
static_assert(is_assignable_to(TypeOf["foo".startswith], Callable))
def _(
a: RegularCallableTypeOf[types.FunctionType.__get__],
b: RegularCallableTypeOf[f],
c: RegularCallableTypeOf[f.__get__],
d: RegularCallableTypeOf[types.FunctionType.__call__],
e: RegularCallableTypeOf[f.__call__],
f: RegularCallableTypeOf[property],
g: RegularCallableTypeOf[property.__get__],
h: RegularCallableTypeOf[MyClass.my_property.__get__],
i: RegularCallableTypeOf[property.__set__],
j: RegularCallableTypeOf[MyClass.my_property.__set__],
k: RegularCallableTypeOf[str.startswith],
l: RegularCallableTypeOf["foo".startswith],
):
# revealed: Overload[(self: FunctionType, instance: None, owner: type, /) -> Unknown, (self: FunctionType, instance: object, owner: type | None = None, /) -> Unknown]
reveal_type(a)
# revealed: (obj: type) -> None
reveal_type(b)
# TODO: ideally this would have precise return types rather than `Unknown`
# revealed: Overload[(instance: None, owner: type, /) -> Unknown, (instance: object, owner: type | None = None, /) -> Unknown]
reveal_type(c)
# revealed: (self, *args: Any, **kwargs: Any) -> Any
reveal_type(d)
# revealed: (obj: type) -> None
reveal_type(e)
# revealed: (fget: ((Any, /) -> Any) | None = None, fset: ((Any, Any, /) -> None) | None = None, fdel: ((Any, /) -> None) | None = None, doc: str | None = None) -> property
reveal_type(f)
# revealed: Overload[(self: property, instance: None, owner: type, /) -> Unknown, (self: property, instance: object, owner: type | None = None, /) -> Unknown]
reveal_type(g)
# TODO: ideally this would have precise return types rather than `Unknown`
# revealed: Overload[(instance: None, owner: type, /) -> Unknown, (instance: object, owner: type | None = None, /) -> Unknown]
reveal_type(h)
# TODO: ideally this would have `-> None` rather than `-> Unknown`
# revealed: (self: property, instance: object, value: object, /) -> Unknown
reveal_type(i)
# TODO: ideally this would have a more precise input type and `-> None` rather than `-> Unknown`
# revealed: (instance: object, value: object, /) -> Unknown
reveal_type(j)
# revealed: (self, prefix: str | tuple[str, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool
reveal_type(k)
# revealed: (prefix: str | tuple[str, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool
reveal_type(l)