如何在 Python 3.6 中将 ForwardRef 作为参数传递给 TypeVar?
How to pass ForwardRef as args to TypeVar in Python 3.6?
我正在开发一个目前支持 Python 3.6+ 的库,但是在 [=14] 中如何定义前向引用时遇到了一些麻烦=] Python 3.6 中的模块。我在我的本地 Windows 机器上设置了 pyenv
,这样我就可以在不同的 Python 版本之间轻松切换以进行本地测试,因为我的系统解释器默认为 Python 3.9。
这里的用例本质上是我试图用有效的前向引用类型定义一个 TypeVar
,然后我可以将其用于类型注释目的。当我在 3.7+ 上并直接从 typing
模块导入 ForwardRef
时,我已经确认以下代码 运行s 没有问题 ,但是我无法在 Python 3.6 上获得它,因为我注意到出于某种原因不能将前向引用用作 TypeVar
的参数。我还尝试将前向引用类型作为参数传递给 Union
,但我 运行 遇到了类似的问题。
这里是 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)
这是我已经能够测试的示例用法,它似乎按 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)
到目前为止我做了什么
这是我目前用来支持 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
想知道我是否在正确的轨道上,或者是否有更简单的解决方案可以用来支持 ForwardRef 类型作为 Python 3.6 中 TypeVar
或 Union
的参数。
显而易见的是,这里的问题似乎是由于 typing
模块在 Python 3.6 和 Python 3.7 之间发生了一些变化。
在 Python 3.6 和 Python 3.7 中:
TypeVar
are checked using the typing._type_check
function 上的所有约束(链接到 GitHub 上源代码的 3.6 分支)在 TypeVar
之前允许实例化。
TypeVar.__init__
在 3.6 分支中看起来像这样:
class TypeVar(_TypingBase, _root=True):
# <-- several lines skipped -->
def __init__(self, name, *constraints, bound=None,
covariant=False, contravariant=False):
# <-- several lines skipped -->
if constraints and bound is not None:
raise TypeError("Constraints cannot be combined with bound=...")
if constraints and len(constraints) == 1:
raise TypeError("A single constraint is not allowed")
msg = "TypeVar(name, constraint, ...): constraints must be types."
self.__constraints__ = tuple(_type_check(t, msg) for t in constraints)
# etc.
在 Python 3.6:
- 有a class called
_ForwardRef
。此 class 的名称带有前导下划线,以警告用户它是模块的实现细节,因此 class 的 API 可能会在 [=77 之间意外更改=] 版本。
- 似乎
typing._type_check
did not account 因为 _ForwardRef
可能会传递给它,因此出现了奇怪的 AttributeError: type object '_ForwardRef' has no attribute '_gorg'
错误消息。我假设没有考虑到这种可能性,因为假设用户不会使用标记为实现细节的 classes。
在 Python 3.7:
_ForwardRef
has been replaced with a ForwardRef
class:这个class不再是实现细节;它现在是模块 public API.
的一部分
typing._type_check
现在 explicitly accounts 因为 ForwardRef
可能被传递给它的可能性:
def _type_check(arg, msg, is_argument=True):
"""Check that the argument is a type, and return it (internal helper).
As a special case, accept None and return type(None) instead. Also wrap strings
into ForwardRef instances. Consider several corner cases, for example plain
special forms like Union are not valid, while Union[int, str] is OK, etc.
The msg argument is a human-readable error message, e.g::
"Union[arg, ...]: arg should be a type."
We append the repr() of the actual value (truncated to 100 chars).
"""
# <-- several lines skipped -->
if isinstance(arg, (type, TypeVar, ForwardRef)):
return arg
# etc.
解决方案
我很想争辩说目前不值得支持 Python 3.6,因为 Python 3.6 现在有点老了,而且会 officially unsupported 从 2021 年 12 月开始。但是,如果您确实想继续支持 Python 3.6,一个更简洁的解决方案可能是猴子补丁 typing._type_check
而不是猴子补丁 _ForwardRef
。 (我所说的“更干净”是指“更接近于解决问题的根源,而不是问题的症状”——它显然不如您现有的解决方案简洁。)
import sys
from typing import TypeVar
if sys.version_info < (3, 7):
import typing
from typing import _ForwardRef as PyForwardRef
from functools import wraps
_old_type_check = typing._type_check
@wraps(_old_type_check)
def _new_type_check(arg, message):
if arg is PyForwardRef:
return arg
return _old_type_check(arg, message)
typing._type_check = _new_type_check
# ensure the global namespace is the same for users
# regardless of the version of Python they're using
del _old_type_check, _new_type_check, typing, wraps
else:
from typing import ForwardRef as PyForwardRef
然而,虽然这种东西作为运行时解决方案运行良好,但老实说,我不知道是否有办法让类型检查器对这种猴子修补感到满意。 Pycharm、MyPy 之类的肯定不会 期望 你做这样的事情,并且可能对 TypeVar
s 的每个版本的硬编码支持Python.
我正在开发一个目前支持 Python 3.6+ 的库,但是在 [=14] 中如何定义前向引用时遇到了一些麻烦=] Python 3.6 中的模块。我在我的本地 Windows 机器上设置了 pyenv
,这样我就可以在不同的 Python 版本之间轻松切换以进行本地测试,因为我的系统解释器默认为 Python 3.9。
这里的用例本质上是我试图用有效的前向引用类型定义一个 TypeVar
,然后我可以将其用于类型注释目的。当我在 3.7+ 上并直接从 typing
模块导入 ForwardRef
时,我已经确认以下代码 运行s 没有问题 ,但是我无法在 Python 3.6 上获得它,因为我注意到出于某种原因不能将前向引用用作 TypeVar
的参数。我还尝试将前向引用类型作为参数传递给 Union
,但我 运行 遇到了类似的问题。
这里是 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)
这是我已经能够测试的示例用法,它似乎按 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)
到目前为止我做了什么
这是我目前用来支持 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
想知道我是否在正确的轨道上,或者是否有更简单的解决方案可以用来支持 ForwardRef 类型作为 Python 3.6 中 TypeVar
或 Union
的参数。
显而易见的是,这里的问题似乎是由于 typing
模块在 Python 3.6 和 Python 3.7 之间发生了一些变化。
在 Python 3.6 和 Python 3.7 中:
TypeVar
are checked using thetyping._type_check
function 上的所有约束(链接到 GitHub 上源代码的 3.6 分支)在TypeVar
之前允许实例化。
TypeVar.__init__
在 3.6 分支中看起来像这样:class TypeVar(_TypingBase, _root=True): # <-- several lines skipped --> def __init__(self, name, *constraints, bound=None, covariant=False, contravariant=False): # <-- several lines skipped --> if constraints and bound is not None: raise TypeError("Constraints cannot be combined with bound=...") if constraints and len(constraints) == 1: raise TypeError("A single constraint is not allowed") msg = "TypeVar(name, constraint, ...): constraints must be types." self.__constraints__ = tuple(_type_check(t, msg) for t in constraints) # etc.
在 Python 3.6:
- 有a class called
_ForwardRef
。此 class 的名称带有前导下划线,以警告用户它是模块的实现细节,因此 class 的 API 可能会在 [=77 之间意外更改=] 版本。 - 似乎
typing._type_check
did not account 因为_ForwardRef
可能会传递给它,因此出现了奇怪的AttributeError: type object '_ForwardRef' has no attribute '_gorg'
错误消息。我假设没有考虑到这种可能性,因为假设用户不会使用标记为实现细节的 classes。
在 Python 3.7:
的一部分_ForwardRef
has been replaced with aForwardRef
class:这个class不再是实现细节;它现在是模块 public API.typing._type_check
现在 explicitly accounts 因为ForwardRef
可能被传递给它的可能性:def _type_check(arg, msg, is_argument=True): """Check that the argument is a type, and return it (internal helper). As a special case, accept None and return type(None) instead. Also wrap strings into ForwardRef instances. Consider several corner cases, for example plain special forms like Union are not valid, while Union[int, str] is OK, etc. The msg argument is a human-readable error message, e.g:: "Union[arg, ...]: arg should be a type." We append the repr() of the actual value (truncated to 100 chars). """ # <-- several lines skipped --> if isinstance(arg, (type, TypeVar, ForwardRef)): return arg # etc.
解决方案
我很想争辩说目前不值得支持 Python 3.6,因为 Python 3.6 现在有点老了,而且会 officially unsupported 从 2021 年 12 月开始。但是,如果您确实想继续支持 Python 3.6,一个更简洁的解决方案可能是猴子补丁 typing._type_check
而不是猴子补丁 _ForwardRef
。 (我所说的“更干净”是指“更接近于解决问题的根源,而不是问题的症状”——它显然不如您现有的解决方案简洁。)
import sys
from typing import TypeVar
if sys.version_info < (3, 7):
import typing
from typing import _ForwardRef as PyForwardRef
from functools import wraps
_old_type_check = typing._type_check
@wraps(_old_type_check)
def _new_type_check(arg, message):
if arg is PyForwardRef:
return arg
return _old_type_check(arg, message)
typing._type_check = _new_type_check
# ensure the global namespace is the same for users
# regardless of the version of Python they're using
del _old_type_check, _new_type_check, typing, wraps
else:
from typing import ForwardRef as PyForwardRef
然而,虽然这种东西作为运行时解决方案运行良好,但老实说,我不知道是否有办法让类型检查器对这种猴子修补感到满意。 Pycharm、MyPy 之类的肯定不会 期望 你做这样的事情,并且可能对 TypeVar
s 的每个版本的硬编码支持Python.