python / python/cpython

Type annotations in `exec`d code do not resolve as expected

Đang mở
#145,426 7 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

3.14 3.15 stdlib topic-typing type-bug
Ngôn ngữ chính
Python
Star
77.2k
Fork
35.9k
Chỉ số merge pull request
Chỉ số pull request đang chờ

Mô tả

Bug report

Bug description:

We've counted on the ability to exec code to test that various type annotations lead to the correct success/error scenarios

@pytest.mark.parametrize(
    "annotation, value, raises, match",
    [
        pytest.param("Annotated[int, at.Gt(1)]", "0", True, "0 is not greater than 1", id="Gt fail"),
        pytest.param("Annotated[int, at.Gt(1)]", "2", False, "", id="Gt ok"),
        pytest.param("Annotated[int, at.Lt(1)]", "2", True, "2 is not less than 1", id="Lt fail"),
        pytest.param("Annotated[int, at.Lt(1)]", "0", False, "", id="Lt ok"),
        pytest.param("Annotated[int, at.Ge(1)]", "0", True, "0 is not greater than or equal to 1", id="Ge fail"),
        pytest.param("Annotated[int, at.Ge(1)]", "1", False, "", id="Ge ok"),
        pytest.param("Annotated[int, at.Le(1)]", "2", True, "2 is not less than or equal to 1", id="Le fail"),
        pytest.param("Annotated[int, at.Le(1)]", "1", False, "", id="Le ok"),
        pytest.param("Annotated[int, at.MultipleOf(2)]", "3", True, "3 is not a multiple of 2", id="MultipleOf fail"),
        pytest.param("Annotated[int, at.MultipleOf(2)]", "4", False, "", id="MultipleOf ok"),
        pytest.param("Annotated[str, at.MinLen(2)]", "'a'", True, "a is not longer than 2", id="MinLen fail"),
        pytest.param("Annotated[str, at.MinLen(2)]", "'abc'", False, "", id="MinLen ok"),
        pytest.param("Annotated[str, at.MaxLen(2)]", "'abc'", True, "abc is not shorter than 2", id="MaxLen fail"),
        pytest.param("Annotated[str, at.MaxLen(2)]", "'a'", False, "", id="MaxLen ok"),
        pytest.param(
            "Annotated[int, at.Predicate(lambda x: False)]", "0", True, "0 does not satisfy", id="Predicate fail"
        ),
        pytest.param("Annotated[int, at.Predicate(lambda x: True)]", "0", False, "", id="Predicate ok"),
    ],
)
async def test_test_with_annotated_type(annotation: str, value: str, raises: bool, match: str):
    loc: dict = {}
    exec(
        dedent(
            f"""
            from typing import Annotated
            import annotated_types as at

            @Test("test")
            async def test(test: TestInstance, foo: {annotation}) -> None:
                pass

            test_instance = test.use_options(foo={value})
        """
        ),
        globals(),
        loc,
    )
    test = loc["test_instance"]

    ctx: AbstractContextManager
    if raises:
        ctx = pytest.raises(ValueError, match=match)
    else:
        ctx = nullcontext()

    with ctx:
        await parameterize(test, Environment(TestConfig("test")))

However, this now fails in python 3.14+

A "minimal" reproduction:

import inspect

from textwrap import dedent
from typing import TypeAlias, Callable, Coroutine, Self, get_type_hints

TestFunc: TypeAlias = Callable[..., Coroutine[None, None, None]]

class Test:
    def __call__(self, func: TestFunc) -> Self:
        print(inspect.signature(func).parameters)
        print(get_type_hints(func))

def main():
    exec(
        dedent(
            f"""
            from typing import Annotated
            import annotated_types as at

            @Test()
            async def test(foo: Annotated[int, at.Gt(1)]) -> None:
                pass

            print("After function call")
        """
        ),
        globals(),
        {},
    )


if __name__ == "__main__":
    main()

Despite the types Annotated and at being imported in the exec'd code, the values do not get resolved when trying to get annotations.

Traceback (most recent call last):
  File "/home/vfazio/development/xtf/314annotations.py", line 33, in <module>
    main()
    ~~~~^^
  File "/home/vfazio/development/xtf/314annotations.py", line 14, in main
    exec(
    ~~~~^
        dedent(
        ^^^^^^^
    ...<12 lines>...
        {},
        ^^^
    )
    ^
  File "<string>", line 5, in <module>
  File "/home/vfazio/development/xtf/314annotations.py", line 10, in __call__
    print(inspect.signature(func).parameters)
          ~~~~~~~~~~~~~~~~~^^^^^^
  File "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/inspect.py", line 3322, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                   globals=globals, locals=locals, eval_str=eval_str,
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                   annotation_format=annotation_format)
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/inspect.py", line 3037, 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 "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/inspect.py", line 2512, 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 "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/inspect.py", line 2335, in _signature_from_function
    annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str,
                                  format=annotation_format)
  File "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/annotationlib.py", line 966, in get_annotations
    ann = _get_dunder_annotations(obj)
  File "/home/vfazio/.pyenv/versions/3.14.3/lib/python3.14/annotationlib.py", line 1146, in _get_dunder_annotations
    ann = getattr(obj, "__annotations__", None)
  File "<string>", line 6, in __annotate__
NameError: name 'Annotated' is not defined

This seems to imply that code generators/exec can no longer rely on imports in code to satisfy annotations? I don't know if this is due to changes in exec or PEP 649/749 but it is inconvenient.

CPython versions tested on:

3.14, 3.13, 3.12, 3.11

Operating systems tested on:

Linux

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu bằng cách chạy bản tái hiện tối thiểu trên Python 3.14 và truy vết inspect.signature qua annotationlib.get_annotations, đặc biệt là đường dẫn annotate được hiển thị trong traceback. So sánh hành vi với Python 3.13 và bài kiểm thử hiện có dựa trên exec; được xem là hoàn tất khi các chú thích được import bên trong mã được thực thi bằng exec được phân giải chính xác mà không làm hỏng các kịch bản kiểm thử đã báo cáo.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
python
Lĩnh vực
compilers
Loại issue
Lỗi
Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
45/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.