Skip to content
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

Dataclass __init__ annotation eagerly evaluated #128184

Open
nickdrozd opened this issue Dec 22, 2024 · 3 comments
Open

Dataclass __init__ annotation eagerly evaluated #128184

nickdrozd opened this issue Dec 22, 2024 · 3 comments
Assignees
Labels
3.14 new features, bugs and security fixes stdlib Python modules in the Lib dir topic-dataclasses topic-typing type-bug An unexpected behavior, bug, or error

Comments

@nickdrozd
Copy link
Contributor

nickdrozd commented Dec 22, 2024

Bug report

Bug description:

In 3.14, evaluation of annotations is deferred. Except, annotations for the __init__ method of a dataclass are eagerly evaluated.

from dataclasses import dataclass
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    Z = int

@dataclass
class X:
    def __init__(self, _: Z):  # <-- NameError: name 'Z' is not defined
        pass

    def f(self, _: Z):  # <-- no __init__, no problem
        pass

class Y:
    def __init__(self, _: Z):  # <-- no dataclass, no problem
        pass

Running this blows up with a scary stack trace:

Traceback (most recent call last):
  File "/home/nick/test/annotation.py", line 7, in <module>
    @dataclass
     ^^^^^^^^^
  File "/usr/local/lib/python3.14/dataclasses.py", line 1365, in dataclass
    return wrap(cls)
  File "/usr/local/lib/python3.14/dataclasses.py", line 1355, in wrap
    return _process_class(cls, init, repr, eq, order, unsafe_hash,
                          frozen, match_args, kw_only, slots,
                          weakref_slot)
  File "/usr/local/lib/python3.14/dataclasses.py", line 1166, in _process_class
    text_sig = str(inspect.signature(cls)).replace(' -> None', '')
                   ~~~~~~~~~~~~~~~~~^^^^^
  File "/usr/local/lib/python3.14/inspect.py", line 3281, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                   globals=globals, locals=locals, eval_str=eval_str,
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                   annotation_format=annotation_format)
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.14/inspect.py", line 2999, in from_callable
    return _signature_from_callable(obj, sigcls=cls,
                                    follow_wrapper_chains=follow_wrapped,
                                    globals=globals, locals=locals, eval_str=eval_str,
                                    annotation_format=annotation_format)
  File "/usr/local/lib/python3.14/inspect.py", line 2524, in _signature_from_callable
    return _get_signature_of(init)
  File "/usr/local/lib/python3.14/inspect.py", line 2421, in _signature_from_callable
    sig = _get_signature_of(obj.__func__)
  File "/usr/local/lib/python3.14/inspect.py", line 2492, in _signature_from_callable
    return _signature_from_function(sigcls, obj,
                                    skip_bound_arg=skip_bound_arg,
                                    globals=globals, locals=locals, eval_str=eval_str,
                                    annotation_format=annotation_format)
  File "/usr/local/lib/python3.14/inspect.py", line 2315, in _signature_from_function
    annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str,
                                  format=annotation_format)
  File "/usr/local/lib/python3.14/annotationlib.py", line 707, in get_annotations
    ann = _get_dunder_annotations(obj)
  File "/usr/local/lib/python3.14/annotationlib.py", line 847, in _get_dunder_annotations
    ann = getattr(obj, "__annotations__", None)
  File "/home/nick/test/annotation.py", line 9, in __annotate__
    def __init__(self, _: Z):  # <-- NameError: name 'Z' is not defined
                          ^
NameError: name 'Z' is not defined

AFAICT, this behavior is specific to __init__ with dataclass. I don't know what's intended, but as a user it feels like a bug.

Adding from __future__ import annotations causes the problem to go away. But that shouldn't be required in 3.14.

CPython versions tested on:

3.14, CPython main branch

Operating systems tested on:

Linux

Linked PRs

@nickdrozd nickdrozd added the type-bug An unexpected behavior, bug, or error label Dec 22, 2024
@ZeroIntensity ZeroIntensity added topic-typing 3.14 new features, bugs and security fixes topic-dataclasses stdlib Python modules in the Lib dir labels Dec 22, 2024
@sobolevn sobolevn self-assigned this Dec 22, 2024
@sobolevn
Copy link
Member

I will take a look, thanks for the report!

@sobolevn
Copy link
Member

Looks like this is the root cause:

cpython/Lib/dataclasses.py

Lines 1162 to 1169 in f420bdd

# Create a class doc-string.
try:
# In some cases fetching a signature is not possible.
# But, we surely should not fail in this case.
text_sig = str(inspect.signature(cls)).replace(' -> None', '')
except (TypeError, ValueError):
text_sig = ''
cls.__doc__ = (cls.__name__ + text_sig)

I guess we can just ignore NameError there.

@JelleZijlstra
Copy link
Member

Ignoring NameError would be unfortunate, as it leaves the docstring empty. It would be more appropriate to evaluate the annotations in the SOURCE format, which inspect.signature already supports.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.14 new features, bugs and security fixes stdlib Python modules in the Lib dir topic-dataclasses topic-typing type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

4 participants