rv.*_*tch 7 python type-hinting forward-declaration python-3.x python-typing
我正在开发一个当前支持Python 3.6+ 的库,但在如何在 Python 3.6 的模块中定义前向引用方面遇到了一些麻烦typing。我已经pyenv在本地 Windows 计算机上进行了设置,以便可以轻松地在不同的 Python 版本之间切换以进行本地测试,因为我的系统解释器默认为 Python 3.9。
这里的用例本质上是我尝试使用TypeVar有效的前向引用类型定义 a ,然后我可以将其用于类型注释目的。我已经确认当我在 3.7+ 上并直接从模块导入时,以下代码运行没有问题,但我无法在 Python 3.6 上获取它,因为我注意到前向引用不能用作某些参数原因。我还尝试将前向引用类型作为参数传递给,但遇到了类似的问题。ForwardReftypingTypeVarUnion
TypeVar以下是我试图在 python 3.6.0 以及更新版本(如 3.6.8)上工作的导入和定义- 我确实注意到我在次要版本之间遇到了不同的错误:
from typing import _ForwardRef as PyForwardRef, TypeVar
# Errors on PY 3.6:
# 3.6.2+ -> AttributeError: type object '_ForwardRef' has no attribute '_gorg'
# 3.6.2 or earlier -> AssertionError: assert isinstance(a, GenericMeta)
FREF = TypeVar('FREF', str, PyForwardRef)
Run Code Online (Sandbox Code Playgroud)
下面是我已经测试过的示例用法,它似乎按照 Python 3.7+ 的预期进行了类型检查:
class MyClass: ...
def my_func(typ: FREF):
pass
# Type checks
my_func('testing')
my_func(PyForwardRef('MyClass'))
# Does not type check
my_func(23)
my_func(MyClass)
Run Code Online (Sandbox Code Playgroud)
这是我当前用于支持 Python 3.6 的解决方法。这不太漂亮,但似乎至少可以让代码运行而不会出现任何错误。然而,这似乎并没有按预期进行类型检查——至少在 Pycharm 中是这样。
import typing
# This is needed to avoid an`AttributeError` when using PyForwardRef
# as an argument to `TypeVar`, as we do below.
if hasattr(typing, '_gorg'): # Python 3.6.2 or lower
_gorg = typing._gorg
typing._gorg = lambda a: None if a is PyForwardRef else _gorg(a)
else: # Python 3.6.3+
PyForwardRef._gorg = None
Run Code Online (Sandbox Code Playgroud)
想知道我是否走在正确的轨道上,或者是否有一个更简单的解决方案可以用来支持 ForwardRef 类型作为 Python 3.6 的参数TypeVar或Union在 Python 3.6 中。
显而易见的是,这里的问题似乎是由于typingPython 3.6 和 Python 3.7 之间模块的一些变化造成的。
在 Python 3.6 和 Python 3.7 中:
\n在 a 上的所有约束都使用该 函数TypeVar 进行检查(链接到 GitHub 上源代码的 3.6 分支)typing._type_checkTypeVar在允许实例化
TypeVar.__init__在 3.6 分支中看起来像这样:
class TypeVar(_TypingBase, _root=True):\n\n # <-- several lines skipped -->\n\n def __init__(self, name, *constraints, bound=None,\n covariant=False, contravariant=False):\n\n # <-- several lines skipped -->\n\n if constraints and bound is not None:\n raise TypeError("Constraints cannot be combined with bound=...")\n if constraints and len(constraints) == 1:\n raise TypeError("A single constraint is not allowed")\n msg = "TypeVar(name, constraint, ...): constraints must be types."\n self.__constraints__ = tuple(_type_check(t, msg) for t in constraints)\n\n # etc.\nRun Code Online (Sandbox Code Playgroud)\n在 Python 3.6 中:
\n_ForwardRef. 此类的名称带有前导下划线,以警告用户它是模块的实现细节,因此该类的 API 可能会在 Python 版本之间发生意外更改。typing._type_check 没有考虑到可能传递给它的可能性_ForwardRef,因此奇怪的AttributeError: type object \'_ForwardRef\' has no attribute \'_gorg\',因此出现了奇怪的错误消息。我认为没有考虑到这种可能性,因为假设用户知道不使用标记为实现细节的类。在 Python 3.7 中:
\n_ForwardRef 已被替换为类ForwardRef:该类不再是实现细节它现在是模块公共 API 的一部分。
typing._type_check现在明确地解释了这种可能性ForwardRef可能传递给它的
def _type_check(arg, msg, is_argument=True):\n """Check that the argument is a type, and return it (internal helper).\n As a special case, accept None and return type(None) instead. Also wrap strings\n into ForwardRef instances. Consider several corner cases, for example plain\n special forms like Union are not valid, while Union[int, str] is OK, etc.\n The msg argument is a human-readable error message, e.g::\n "Union[arg, ...]: arg should be a type."\n We append the repr() of the actual value (truncated to 100 chars).\n """\n\n # <-- several lines skipped -->\n\n if isinstance(arg, (type, TypeVar, ForwardRef)):\n return arg\n\n # etc.\nRun Code Online (Sandbox Code Playgroud)\n我很想说,鉴于 Python 3.6 现在已经有点过时了,并且从 2021 年 12 月起将不再受正式支持,因此目前不值得花精力支持 Python 3.6。但是,如果您确实想要为了继续支持Python 3.6,一个稍微干净的解决方案可能是猴子补丁typing._type_check而不是猴子补丁_ForwardRef。(我所说的“更干净”是指“更接近于解决问题的根源,而不是问题的症状”\xe2\x80\x94,它显然比现有的解决方案更简洁。)
import sys \nfrom typing import TypeVar\n\nif sys.version_info < (3, 7):\n import typing\n from typing import _ForwardRef as PyForwardRef\n from functools import wraps\n\n _old_type_check = typing._type_check\n\n @wraps(_old_type_check)\n def _new_type_check(arg, message):\n if arg is PyForwardRef:\n return arg\n return _old_type_check(arg, message)\n\n typing._type_check = _new_type_check\n # ensure the global namespace is the same for users\n # regardless of the version of Python they\'re using\n del _old_type_check, _new_type_check, typing, wraps\nelse:\n from typing import ForwardRef as PyForwardRef\nRun Code Online (Sandbox Code Playgroud)\n然而,虽然这种事情作为运行时解决方案工作得很好,但老实说,我不知道是否有办法让类型检查器对这种猴子修补感到满意。Pycharm、MyPy 等肯定不会期望你做这样的事情,并且可能得到他们的支持TypeVar为每个版本的 Python 提供了对 s 的硬编码支持。