Wizard.Ritvik
Wizard.Ritvik

Reputation: 11612

How to type-hint return type of __getattr__ method?

I am curious if there is a way in Python 3 to type hint the return type of __getattr__() method in Python.

I have the following dummy code I was using for test purposes:

class MyClass:

    def __getattr__(self, item: str) -> str:
        return 'test'

    def __getattribute__(self, item: str) -> str:
        return 'test'


c = MyClass()

print(c.hello, type(c.hello))  # test <class 'str'>

Unfortunately, Pycharm is unable to resolve the type for hello attribute from above. I'm not sure but I think it currently is processed as Any type.

I also came up with the following variation on the above code, but still unable to get type hinting to work as expected:

from typing import Callable

ReturnsString = Callable[[], str]


class MyClass:

    my_test_fn: ReturnsString = lambda self: 'hello'
    __getattr__: ReturnsString = lambda self, _item: 'test'
    __getattribute__: ReturnsString


c = MyClass()

# type hinting works as expected - return type is `str`
print(c.my_test_fn())

# type hinting doesn't work here still - help?
print(c.hello, type(c.hello))  # test <class 'str'>

I am interested to know if anyone knows a workaround where we can type hint the return type of __getattribute()__ in Python 3.x, perhaps using typing Generics or otherwise.

Additional Info

Just to clarify, my use case is that I'm trying to build out a dict subclass which supports attribute (dot) access. So I have noticed that type hinting and type-specific suggestions for __getitem__ works, but not for __getattr__. For example:

from typing import Callable

ReturnsString = Callable[[], str]


class MyClass(dict):
    __getattr__: ReturnsString = lambda self, _item: 'test'
    __getattribute__: ReturnsString
    __getitem__: ReturnsString


c = MyClass(hello=123)

# works as expected - return type of c['hello'] is `str`
print(c['hello'], type(c['hello']))  # test <class 'str'>

# type hinting doesn't work here still - help?
print(c.hello, type(c.hello))  # test <class 'str'>

Upvotes: 14

Views: 4569

Answers (1)

InSync
InSync

Reputation: 10437

As of 2024, Pyright seems to support this.

Pyright

Your code, for a simple example, works as-is:

(playground)

class C:
    def __getattr__(self, name: str) -> str: ...
    def __getattribute__(self, name: str) -> str: ...
reveal_type(C().hello)  # str

More detailed behaviour can be specified using @overloads, although this is not entirely useful since key is most often a string and such attributes can be defined directly in the class body:

(playground)

class Foo:
    @overload
    def __getattr__(self, name: Literal['bar']) -> int: ...
    @overload
    def __getattr__(self, name: str) -> Any: ...
    def __getattr__(self, name: str) -> Any: ...
reveal_type(Foo().bar)              # int

getattr() does not seem to trigger this, however:

reveal_type(getattr(Foo(), 'bar'))  # Any

For the record, while getattr() only accepts string names, even at runtime, technically you can still reach for __getattr__() directly and call it with whatever you prefer, so allowing it to accept names of other types is not an error:

(playground)

class Lorem:
    def __getattr__(self, name: Literal[42]) -> bool: ...
reveal_type(Lorem().__getattr__(42))  # bool

Other type checkers

As far as I know, this "feature" is not standardized. Some test results:

Upvotes: 2

Related Questions