python / python/cpython

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

未關閉
#145,426 7 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

3.14 3.15 stdlib topic-typing type-bug
主要語言
Python
星號
77.2k
分支
36k
PR 合併指標
PR 指標待擷取

描述

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

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

先在 Python 3.14 上執行最小重現,並追蹤 inspect.signature 經由 annotationlib.get_annotations 的路徑,特別關注 traceback 中顯示的 annotate 路徑。將其行為與 Python 3.13 以及現有的基於 exec 的測試進行比較;當在透過 exec 執行的程式碼中匯入的註解能夠正確解析,且不會破壞報告中的測試情境時,即表示完成。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
python
領域
compilers
Issue 類型
缺陷
難度
4/5
預估耗時
3-5 天
活躍度
冷清
描述清晰度
基本清楚
新手友好度
45/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。