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
from __future__ import annotations makes dataclasses.Field.type a string, not type #83623
Comments
I've checked this behaviour under Python 3.7.5 and 3.8.1.
With annotations imported, the Without annotations, the I found this out when using dataclasses_serialization module. Following code works fine when we remove import of annotations:
TypeError: issubclass() arg 1 must be a class |
Isn't that the entire point of "from __future__ import annotations"? Also, please show the traceback when reporting errors so that I can see what's going on. |
That's the error I get trying to use dataclasses_serialization module: $ cat test.py
from __future__ import annotations
from dataclasses import dataclass
from dataclasses_serialization.json import JSONSerializer @dataclass
class Foo:
x: int
JSONSerializer.deserialize(Foo, {'x': 42})
$ python3 test.py
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dataclasses_serialization/serializer_base.py", line 125, in dict_to_dataclass
for fld, fld_type in zip(flds, fld_types)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dataclasses_serialization/serializer_base.py", line 126, in <dictcomp>
if fld.name in dct
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/toolz/functoolz.py", line 303, in __call__
return self._partial(*args, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dataclasses_serialization/serializer_base.py", line 234, in deserialize
if issubclass(cls, type_):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dataclasses_serialization/serializer_base.py", line 72, in issubclass
return original_issubclass(cls, classinfo)
TypeError: issubclass() arg 1 must be a class During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test.py", line 9, in <module>
JSONSerializer.deserialize(Foo, {'x': 42})
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/toolz/functoolz.py", line 303, in __call__
return self._partial(*args, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dataclasses_serialization/serializer_base.py", line 238, in deserialize
return self.deserialization_functions[dataclass](cls, serialized_obj)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/toolz/functoolz.py", line 303, in __call__
return self._partial(*args, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dataclasses_serialization/serializer_base.py", line 131, in dict_to_dataclass
cls
dataclasses_serialization.serializer_base.DeserializationError: Missing one or more required fields to deserialize {'x': 42} as <class '__main__.Foo'> |
Well the type comes from the annotation, so this makes sense to me. If dataclasses were to call get_type_hints() for every field, it would defeat the purpose of PEP-563 (at least for dataclasses). |
Should |
I think not. But maybe a function that evaluates all of the field types. Or maybe an @DataClass parameter to cause it to happen at definition time. At this point, this seems more like fodder for python-ideas. |
I thought of this behaviour as a bug, because PEP-563 mentions breaking "applications depending on arbitrary objects to be directly present in annotations", while it is also breaking users of dataclasses.fields(), that is a part of the standard library. But if it's not something worth fighting for, feel free to close this issue. |
One problem I have with the current behaviour is that users of library code need to know the exact namespace in which a library has defined a dataclass. An example is if a library writer had to deconflict the name of a type he used in a user-facing dataclass. Below is a "typical" use case which will become very fragile to implement.(E.g. imagine the dataclass with dynamically generated fields, the implementation of which I have neglected for the sake of brevity.) === some_library_typing.py === mytype = str # library author defines some type alias === some_library_module_a.py === from __future__ import annotations
import dataclasses
from some_library_typing import mytype as mytype_deconflicted
mytype = int
@dataclasses.dataclass
class MyClass:
var1: mytype_deconflicted = 'foo'
def method1(self, val: mytype) -> mytype:
return val + 1 === user_code.py === from __future__ import annotations
import dataclasses
from some_library_typing import mytype
from some_library_module_a import MyClass
inst = MyClass('bar')
for f in dataclasses.fields(inst):
if f.type is mytype:
print('mytype found')
break
else:
print('mytype not found') The Of course, the library writer could have written the following to make the code work: === some_library_module_a.py === from __future__ import annotations
import dataclasses
from some_library_typing import mytype as mytype_deconflicted
mytype = int
@dataclasses.dataclass
class MyClass:
var1: mytype = 'foo'
def method1(self, val: mytype)
return val + 1 That is a phenomenally obscure and counter-intuitive way of writing code! Whichever way one turns this, the current behaviour either seems to require library authors to take extraordinary care with their namespaces when defining dataclasses or forces them to write hard-to-read code or seems to require from users detailed knowledge about the implementation specifics of a library they use. If this behaviour is kept as is, some clear warnings and guidance on how to deal with this in practice should be given in the docs. From what I can see in the 3.10 docs, that is not yet the case. |
Another counter-intuitive behaviour is the different behaviour of dataclasses depending on whether they were defined with the decorator or the make_dataclass factory method: from __future__ import annotations
import dataclasses
mytype = int
@dataclasses.dataclass
class MyClass1:
foo: mytype = 1
MyClass2 = dataclasses.make_dataclass(
f'MyClass2',
[('foo', mytype, 1)]
) print(dataclasses.fields(MyClass1)[0].type) Results in: mytype |
I don't know if it helps, but I just ran in to this when I followed the advice at (1) because I wanted to type hint a method with the type of the enclosing class. This broke a package I'm working on in parallel (2) because it uses dataclasses.fields internally. I'm not sure what the advice would be here, should my package detect if the caller has (1) https://stackoverflow.com/questions/33533148/how-do-i-type-hint-a-method-with-the-type-of-the-enclosing-class/33533514#33533514 |
I think pydantic approach is the best practice. |
This is particularly annoying if you are using For instance:
In the code above it looks like the only way to get the |
Actually, sorry I realise I can pass |
Are there any plans for fixing this? The behavior described in documentation ( I understand there is a workaround using |
Do not use PEP 649 will be accepted, but you can not use it for now. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: