-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document descriptors #2566
Comments
Is the documentation available somewhere? I cannot find it. |
@pawelswiecki No, the issue is still open. The general Python docs are here, from this you can guess how it translates to static typing. |
Thanks for the answer. I have the following problem, to which I did not find an answer to in the Descriptor HowTo Guide you've linked to. So, running mypy 0.700 on the following code... # desc.py
class StringField:
def __get__(self, obj, objtype) -> str:
return "string"
class MyClass:
x: str = StringField()
reveal_type(MyClass.x)
m = MyClass()
reveal_type(m.x) ... gives me the following output:
So mypy correctly reveals the type of the When I remove the annotation That is why I thought I need the documentation, because I must be doing something wrong. Or the feature is not yet implemented? Or is it a bug? Let me know if it makes sense to create a separate issue. |
@pawelswiecki mypy is perfectly right here and your annotation is wrong. Descriptor protocol is invoked only when accessing an attribute, not inside the class body. To understand it, try this: class MyClass:
x = StringField()
print(x) # <__main__.StringField object at ...>
print(MyClass.x) # "string" |
Right, so how do I tell mypy that |
I'd like to avoid using Like something like this: from typing import TypeVar, cast
T = TypeVar("T")
class _StringField:
def __get__(self, obj, objtype) -> str:
return "string"
def StringField() -> T:
return cast(T, _StringField())
class MyClass:
x: str = StringField() # no error
y: int = StringField() # no error
reveal_type(MyClass.x) # 'builtins.str'
m = MyClass()
reveal_type(m.x) # 'builtins.str' This is a partial solucion, since return type of |
Just an hour ago you wrote
class StringField:
def __get__(self, obj, objtype) -> str:
return "string"
class MyClass:
x = StringField()
y = MyClass()
y.x = "test" # OK
y.x = 42 # Error: Incompatible types in assignment (expression has type "int", variable has type "str") What else do you need? Also please stop spamming in this issue. If you have questions about mypy, there is a Gitter channel for this. |
What you wrote looks like the solution. I just wanted to use the same typing pattern as dataclasses (it seemed natural at the time), which is just not adequate in the descriptor context, as you pointed out. Thank you very much for your responses. And sorry for the overabundance of comments, I'm stopping here. |
Increased priority to high since this is a pretty fundamental Python feature and annotating descriptors is non-obvious. |
We should also add an example with an overloaded |
I'm trying to define a simple descriptor base class, but after 3 hours of trying, still can't make all the annotations work.....It's just so hard for a normal user. This is the best I came up with: from __future__ import annotations
from typing import TypeVar, Generic, Type, Any
from weakref import WeakKeyDictionary
S = TypeVar('S', bound='Descriptorable')
class Descriptorable:
# This is the most precise one, but I can't make it to work:
# _descriptor_instances: WeakKeyDictionary[Any, S]
# Also not working, although it looks innocent to me:
# _descriptor_instances: WeakKeyDictionary[Any, Descriptorable]
_descriptor_instances: WeakKeyDictionary
def __init__(self):
self._descriptor_instances = WeakKeyDictionary()
# This is not precise enough, see reveal_type()s below
# def __get__(self, instance, owner) -> Descriptorable:
def __get__(self: S, instance, owner) -> S:
if instance is None:
return self
if instance in self._descriptor_instances:
descriptor_instance = self._descriptor_instances[instance]
else:
descriptor_instance = self._create_descriptor_instance(instance)
self._descriptor_instances[instance] = descriptor_instance
return descriptor_instance
def _create_descriptor_instance(self, instance):
raise NotImplementedError
T = TypeVar('T')
# This class subclasses Descriptorable, so it can be used as descriptor.
class DataContainer(Descriptorable, Generic[T]):
data_type: Type[T]
data: T
def __init__(self, data: T, data_type: Type[T]):
super().__init__()
self.data_type = data_type
self.data = data
def _create_descriptor_instance(self, instance) -> DataContainer[T]:
return DataContainer(self.data, self.data_type)
def get_data(self) -> T:
return self.data
def set_data(self, data: T) -> None:
self.data = data
class MyClass:
data = DataContainer(2.0, float)
m = MyClass()
reveal_type(m.data) # Should be: DataContainer[float]
reveal_type(m.data._descriptor_instances) # Should be WeakKeyDictionary[Any, DataContainer[float]] The hardest part is how to annotate Any help would be much appreciated. And I really think that descriptors deserve multiple paragraphs, if not a whole page, in the documentation. |
Was the verdict here that code using the dataclass-style pattern is wrong by design? class _StringField:
def __get__(self, obj, objtype) -> str:
return "string"
class MyClass:
x: str = StringField() # what is the ruling on this annotation That is, is the marked annotation quite deliberately rejected by mypy now and for all time, or is there a possibility that a patch to make this legal would be considered? |
I think we'd probably consider it? |
Because it took me about 90 minutes trying to figure out how to do this today, I'm leaving this here for posterity: from typing import Any, Generic, TypeVar, Union, overload
T = TypeVar("T")
class MyDescriptor(Generic[T]):
...
@overload
def __get__(self, instance: None, owner: Any) -> "MyDescriptor": ...
@overload
def __get__(self, instance: object, owner: Any) -> T: ...
def __get__(self, instance: Any, owner: Any) -> Union["MyDescriptor", T]:
# actual implementation
... |
Dataclasses and descriptors are described in https://docs.python.org/3/library/dataclasses.html#descriptor-typed-fields
I've tried many suggestions from this issue and SO threads, but nothing is working correctly. How dataclasses with descriptors should be annotated? |
@starhel I'm encountering this now too. I have a generic descriptor class
or
The first one seems to be the preferred declaration, based on the example in the dataclasses docs, but mypy isn't inferring type correctly from the descriptor's |
We probably want to first make sure that mypy supports some of the most common real-world descriptor use cases.
The text was updated successfully, but these errors were encountered: