类型提示,与前向引用联合
Type hinting, union with forward references
好了 python 现在可以使用 3 类型提示了。
在我的小脚本中,我希望使用类型提示。但是,特定变量可以有两种类型。 np.ndarray
(表示位置)或 CelestialBody
(从中获取位置)。
该函数在 CelestialOrbit
class.
内
现在 CelestialBody 对象没有在 class 之前定义,所以我使用前向引用,如 pep
所述
from math import pi
import numpy as np
import math as m
from numpy import cos, sin, sqrt, power, square, arctan2, arccos, arcsin, arcsinh, radians, degrees
from scipy.optimize import *
import scipy as sp
import celestial_body as CB
import typing
#....
def get_total_max_distance(self, ancestor_body: "CB.CelestialBody", eps=3*np.finfo(float).eps):
if self.parent == ancestor_body:
return self.apoapsis_distance
orbit_list = list(self.create_tree_branch(ancestor_body))
orbit_list.reverse()
return orbit_list[0]._get_total_max_distance(ancestor_body.getGlobalPositionAtTime(),
ancestor_body.getGlobalPositionAtTime(),
orbit_list[1:], eps)
这很好用。 Pycharm 理解类型并且看起来正确?现在我想改变它,让它理解它可以同时采用 CB.CelestialBody
和 np.ndarray
类型。 (第二个已经声明)。我尝试根据 pep:
使用联合
def get_total_max_distance(self, ancestor_body: typing.Union["CB.CelestialBody",np.ndarray], eps=3*np.finfo(float).eps):
然而,这失败了,并显示了以下评论:"AttributeError: 'module' object has no attribute 'CelestialBody'"
完整追溯:
Traceback (most recent call last):
File "C:/Users/Paul/PycharmProjects/KSP_helper/main.py", line 5, in <module>
from celestial_body import *
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_body.py", line 7, in <module>
import celestial_orbit as CO
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_orbit.py", line 123, in <module>
class CelestialOrbit:
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_orbit.py", line 579, in CelestialOrbit
def get_total_max_distance(self, ancestor_body: typing.Union["CB.CelestialBody",np.ndarray], eps=3*np.finfo(float).eps):
File "C:\Python34\lib\site-packages\typing.py", line 537, in __getitem__
dict(self.__dict__), parameters, _root=True)
File "C:\Python34\lib\site-packages\typing.py", line 494, in __new__
for t2 in all_params - {t1} if not isinstance(t2, TypeVar)):
File "C:\Python34\lib\site-packages\typing.py", line 494, in <genexpr>
for t2 in all_params - {t1} if not isinstance(t2, TypeVar)):
File "C:\Python34\lib\site-packages\typing.py", line 185, in __subclasscheck__
self._eval_type(globalns, localns)
File "C:\Python34\lib\site-packages\typing.py", line 172, in _eval_type
eval(self.__forward_code__, globalns, localns),
File "<string>", line 1, in <module>
AttributeError: 'module' object has no attribute 'CelestialBody'
我该怎么做?
按照 Kevin 的建议显示了一个更容易出现的错误(原因,因为 celestial_orbit 模块是由 celestial_body 模块导入的,所以当 python 时 CelestialBody class 没有被实例化尝试实例化 CelestialOrbit class)。
C:\Python35\python.exe C:/Users/Paul/PycharmProjects/KSP_helper/main.py
Traceback (most recent call last):
File "C:/Users/Paul/PycharmProjects/KSP_helper/main.py", line 5, in <module>
from celestial_body import *
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_body.py", line 7, in <module>
import celestial_orbit as CO
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_orbit.py", line 125, in <module>
class CelestialOrbit:
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_orbit.py", line 581, in CelestialOrbit
def get_total_max_distance(self, ancestor_body: typing.Union[CB.CelestialBody, np.ndarray], eps=3*np.finfo(float).eps):
AttributeError: module 'celestial_body' has no attribute 'CelestialBody'
Bakuriu 的建议 - 将 CB.CelestialBody
更改为 celestial_body.CelestialBody
似乎有效。然而,这对我来说是非常不合逻辑的 - 特别是因为非联合版本使用 CB
别名。
该行为的原因是 CB.CelestialBody
不会抛出 NameError
,因为全局范围包含名称 CB
,但会抛出 AttributeError
,因为模块包含没有属性 CelestialBody
.
来自typing.py
的__subclasscheck__
是
def __subclasscheck__(self, cls):
if not self.__forward_evaluated__:
globalns = self.__forward_frame__.f_globals
localns = self.__forward_frame__.f_locals
try:
self._eval_type(globalns, localns)
except NameError:
return False # Too early.
return issubclass(cls, self.__forward_value__)
Union.__new__
尝试在实例化时尽早评估类型。但是,typing
不希望抛出 AttributeError
。事实上,在咨询 PEP 0484 之后,这似乎是一个实际提出的用于处理循环导入问题的用例;然而 Union
.
没有正确处理实际操作
无论如何,在你的情况下,如果你实际上不需要 celestial_body
在那个模块中,除了类型推断,你可能根本不需要导入那个模块,或者在结束时导入它文件,例如
def get_total_max_distance(self, ancestor_body: "CB.CelestialBody", eps=3*np.finfo(float).eps):
...
import celestial_body as CB
非Union
版本没有任何问题 - 这是因为他们自己的函数注释对[=43=没有任何意义]. typing
中的 类 本身确实关心给予他们的东西。
另一方面,PyCharm 使用独立于 typing
模块的类型推断。
注意:此错误最近已修复,将成为 Python 3.5.3 的一部分。从该版本开始,以下答案已过时。
前向引用无法解析,因为您的 CB
模块引用存在,但没有 CelestialBody
属性,因此引发 AttributeError
异常。前向引用解析(由 Union
类型间接触发)仅允许 NameError
异常;据说是因为这是确定名称是否(尚未)可用的规范方法。
但是考虑到 PEP 为您提供了一个 example,其中前向引用用于解决两个模块之间的循环依赖(因此您希望 AttributeErrors
在过早引用name),我有点惊讶你尝试的方法实际上不起作用。您几乎肯定发现了错误。
发生的是 Union[...]
类型检查联合中的元素是否是联合中另一种类型的子类,正是该检查触发了查找前向引用的尝试。如果 'a.A'
和 'b.B'
(如在循环引用示例中)起作用,则前向引用检查应接受 AttributeError
作为此处处理的有效异常。事实上,任何 异常此时都应该被忽略,因为,正如 PEP 所述:
The string literal should contain a valid Python expression [...] and it should evaluate without errors once the module has been fully loaded.
强调我的。创建 Union[..]
对象时,模块尚未完全加载,因此假设允许任何有效的 Python 表达式,代码 应该 对待 any 异常表示前向引用尚未准备好并忽略它。
解决方法是创建将 AttributeError
转换为 NameError
的函数:
def _CelestialBody_forward_ref():
try:
return CB.CelestialBody
except AttributeError:
# not yet, raise NameError instead
raise NameError('CelestialBody')
然后在您的转发引用中使用它:
typing.Union['_CelestialBody_forward_ref()', np.ndarray]
这是可行的,因为前向引用允许是任何有效的 Python 表达式。或者您可以将整个 Union
声明设为一个字符串;稍后将在所有导入完成后对其进行评估:
def get_total_max_distance(self, ancestor_body: "typing.Union[CB.CelestialBody,np.ndarray]", eps=3*np.finfo(float).eps):
我 filed this as a bug 参与了 Python 项目。
至于为什么爆流建议改成celestial_body.CelestialBody
;这只是 'works' 因为名称 celestial_body
引发了 NameError
异常。该名称将 永远不会 在您的代码上下文中工作,因此前向表达式不符合 PEP(它 不会 在没有模块完全加载后出现错误)。
如果 PyCharm 无论如何都接受该引用并正确地对函数进行类型检查(例如,它只会让您在编写调用时使用 numpy ndarray
或 CelestialObject
实例),那是因为 PyCharm 超出了此处的规范。其他工具可能不会那么宽容。
换句话说,就 typing
而言,您还不如将 frobnar.FlubberdyFlub
用作那里的前向参考,它会同样抑制这个特定的错误影响;无效的前向引用。
好了 python 现在可以使用 3 类型提示了。
在我的小脚本中,我希望使用类型提示。但是,特定变量可以有两种类型。 np.ndarray
(表示位置)或 CelestialBody
(从中获取位置)。
该函数在 CelestialOrbit
class.
现在 CelestialBody 对象没有在 class 之前定义,所以我使用前向引用,如 pep
所述from math import pi
import numpy as np
import math as m
from numpy import cos, sin, sqrt, power, square, arctan2, arccos, arcsin, arcsinh, radians, degrees
from scipy.optimize import *
import scipy as sp
import celestial_body as CB
import typing
#....
def get_total_max_distance(self, ancestor_body: "CB.CelestialBody", eps=3*np.finfo(float).eps):
if self.parent == ancestor_body:
return self.apoapsis_distance
orbit_list = list(self.create_tree_branch(ancestor_body))
orbit_list.reverse()
return orbit_list[0]._get_total_max_distance(ancestor_body.getGlobalPositionAtTime(),
ancestor_body.getGlobalPositionAtTime(),
orbit_list[1:], eps)
这很好用。 Pycharm 理解类型并且看起来正确?现在我想改变它,让它理解它可以同时采用 CB.CelestialBody
和 np.ndarray
类型。 (第二个已经声明)。我尝试根据 pep:
def get_total_max_distance(self, ancestor_body: typing.Union["CB.CelestialBody",np.ndarray], eps=3*np.finfo(float).eps):
然而,这失败了,并显示了以下评论:"AttributeError: 'module' object has no attribute 'CelestialBody'"
完整追溯:
Traceback (most recent call last):
File "C:/Users/Paul/PycharmProjects/KSP_helper/main.py", line 5, in <module>
from celestial_body import *
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_body.py", line 7, in <module>
import celestial_orbit as CO
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_orbit.py", line 123, in <module>
class CelestialOrbit:
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_orbit.py", line 579, in CelestialOrbit
def get_total_max_distance(self, ancestor_body: typing.Union["CB.CelestialBody",np.ndarray], eps=3*np.finfo(float).eps):
File "C:\Python34\lib\site-packages\typing.py", line 537, in __getitem__
dict(self.__dict__), parameters, _root=True)
File "C:\Python34\lib\site-packages\typing.py", line 494, in __new__
for t2 in all_params - {t1} if not isinstance(t2, TypeVar)):
File "C:\Python34\lib\site-packages\typing.py", line 494, in <genexpr>
for t2 in all_params - {t1} if not isinstance(t2, TypeVar)):
File "C:\Python34\lib\site-packages\typing.py", line 185, in __subclasscheck__
self._eval_type(globalns, localns)
File "C:\Python34\lib\site-packages\typing.py", line 172, in _eval_type
eval(self.__forward_code__, globalns, localns),
File "<string>", line 1, in <module>
AttributeError: 'module' object has no attribute 'CelestialBody'
我该怎么做?
按照 Kevin 的建议显示了一个更容易出现的错误(原因,因为 celestial_orbit 模块是由 celestial_body 模块导入的,所以当 python 时 CelestialBody class 没有被实例化尝试实例化 CelestialOrbit class)。
C:\Python35\python.exe C:/Users/Paul/PycharmProjects/KSP_helper/main.py
Traceback (most recent call last):
File "C:/Users/Paul/PycharmProjects/KSP_helper/main.py", line 5, in <module>
from celestial_body import *
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_body.py", line 7, in <module>
import celestial_orbit as CO
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_orbit.py", line 125, in <module>
class CelestialOrbit:
File "C:\Users\Paul\PycharmProjects\KSP_helper\celestial_orbit.py", line 581, in CelestialOrbit
def get_total_max_distance(self, ancestor_body: typing.Union[CB.CelestialBody, np.ndarray], eps=3*np.finfo(float).eps):
AttributeError: module 'celestial_body' has no attribute 'CelestialBody'
Bakuriu 的建议 - 将 CB.CelestialBody
更改为 celestial_body.CelestialBody
似乎有效。然而,这对我来说是非常不合逻辑的 - 特别是因为非联合版本使用 CB
别名。
该行为的原因是 CB.CelestialBody
不会抛出 NameError
,因为全局范围包含名称 CB
,但会抛出 AttributeError
,因为模块包含没有属性 CelestialBody
.
来自typing.py
的__subclasscheck__
是
def __subclasscheck__(self, cls):
if not self.__forward_evaluated__:
globalns = self.__forward_frame__.f_globals
localns = self.__forward_frame__.f_locals
try:
self._eval_type(globalns, localns)
except NameError:
return False # Too early.
return issubclass(cls, self.__forward_value__)
Union.__new__
尝试在实例化时尽早评估类型。但是,typing
不希望抛出 AttributeError
。事实上,在咨询 PEP 0484 之后,这似乎是一个实际提出的用于处理循环导入问题的用例;然而 Union
.
无论如何,在你的情况下,如果你实际上不需要 celestial_body
在那个模块中,除了类型推断,你可能根本不需要导入那个模块,或者在结束时导入它文件,例如
def get_total_max_distance(self, ancestor_body: "CB.CelestialBody", eps=3*np.finfo(float).eps):
...
import celestial_body as CB
非Union
版本没有任何问题 - 这是因为他们自己的函数注释对[=43=没有任何意义]. typing
中的 类 本身确实关心给予他们的东西。
PyCharm 使用独立于 typing
模块的类型推断。
注意:此错误最近已修复,将成为 Python 3.5.3 的一部分。从该版本开始,以下答案已过时。
前向引用无法解析,因为您的 CB
模块引用存在,但没有 CelestialBody
属性,因此引发 AttributeError
异常。前向引用解析(由 Union
类型间接触发)仅允许 NameError
异常;据说是因为这是确定名称是否(尚未)可用的规范方法。
但是考虑到 PEP 为您提供了一个 example,其中前向引用用于解决两个模块之间的循环依赖(因此您希望 AttributeErrors
在过早引用name),我有点惊讶你尝试的方法实际上不起作用。您几乎肯定发现了错误。
发生的是 Union[...]
类型检查联合中的元素是否是联合中另一种类型的子类,正是该检查触发了查找前向引用的尝试。如果 'a.A'
和 'b.B'
(如在循环引用示例中)起作用,则前向引用检查应接受 AttributeError
作为此处处理的有效异常。事实上,任何 异常此时都应该被忽略,因为,正如 PEP 所述:
The string literal should contain a valid Python expression [...] and it should evaluate without errors once the module has been fully loaded.
强调我的。创建 Union[..]
对象时,模块尚未完全加载,因此假设允许任何有效的 Python 表达式,代码 应该 对待 any 异常表示前向引用尚未准备好并忽略它。
解决方法是创建将 AttributeError
转换为 NameError
的函数:
def _CelestialBody_forward_ref():
try:
return CB.CelestialBody
except AttributeError:
# not yet, raise NameError instead
raise NameError('CelestialBody')
然后在您的转发引用中使用它:
typing.Union['_CelestialBody_forward_ref()', np.ndarray]
这是可行的,因为前向引用允许是任何有效的 Python 表达式。或者您可以将整个 Union
声明设为一个字符串;稍后将在所有导入完成后对其进行评估:
def get_total_max_distance(self, ancestor_body: "typing.Union[CB.CelestialBody,np.ndarray]", eps=3*np.finfo(float).eps):
我 filed this as a bug 参与了 Python 项目。
至于为什么爆流建议改成celestial_body.CelestialBody
;这只是 'works' 因为名称 celestial_body
引发了 NameError
异常。该名称将 永远不会 在您的代码上下文中工作,因此前向表达式不符合 PEP(它 不会 在没有模块完全加载后出现错误)。
如果 PyCharm 无论如何都接受该引用并正确地对函数进行类型检查(例如,它只会让您在编写调用时使用 numpy ndarray
或 CelestialObject
实例),那是因为 PyCharm 超出了此处的规范。其他工具可能不会那么宽容。
换句话说,就 typing
而言,您还不如将 frobnar.FlubberdyFlub
用作那里的前向参考,它会同样抑制这个特定的错误影响;无效的前向引用。