Reputation: 11612
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.
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
Reputation: 10437
As of 2024, Pyright seems to support this.
Your code, for a simple example, works as-is:
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 @overload
s, although this is not entirely useful since key
is most often a string and such attributes can be defined directly in the class body:
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 name
s, even at runtime, technically you can still reach for __getattr__()
directly and call it with whatever you prefer, so allowing it to accept name
s of other types is not an error:
class Lorem:
def __getattr__(self, name: Literal[42]) -> bool: ...
reveal_type(Lorem().__getattr__(42)) # bool
As far as I know, this "feature" is not standardized. Some test results:
(self, name: Literal['bar']) -> int: ...
and (self, name: Literal[42]) -> bool: ...
"invalid signatures" but allows (self, name: str) -> str: ...
.Upvotes: 2