python - 类型提示、与前向引用的联合

标签 python python-3.5 type-hinting

现在可以在 python 3 中使用类型提示了。

在我的小脚本中,我希望使用类型提示。然而,特定变量可以有两种类型。要么是 np.ndarray(表示位置),要么是 CelestialBody(从中获取位置)。 该函数位于 CelestialOrbit 类中。

现在,CelestialBody 对象未在类之前定义,因此我使用前向引用,如 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'对象没有属性'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 尝试实例化 CelestialOrbit 类时,CelestialBody 类尚未实例化)。

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 别名一起使用。

最佳答案

注意:此错误最近已修复,并将成为 Python 3.5.3 的一部分。下面的答案从该版本开始已经过时。


前向引用无法解析,因为您的 CB 模块引用存在,但没有 CelestialBody 属性,因此出现 AttributeError 异常上调。前向引用解析(由 Union 类型间接触发)仅允许 NameError 异常;据说是因为这是确定名称是否(尚未)可用的规范方法。

但考虑到 PEP 为您提供了 example其中前向引用用于解决两个模块之间的循环依赖关系(因此您希望在过早引用名称时发生 AttributeErrors ),令我有点惊讶的是您尝试的方法并没有实际上,工作。您几乎肯定已经发现了一个错误。

发生的情况是,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 项目。


至于为什么Bakuriu建议将表达式改为celestial_body.CelestialBody;这只“有效”,因为名称 celestial_body 会引发 NameError 异常。该名称​​永远在您的代码上下文中不起作用,因此前向表达式不符合 PEP(一旦模块被调用,它不会无错误地进行计算)满载)。

如果 PyCharm 无论如何接受该引用并正确地对函数进行类型检查(例如,它只允许您在编写调用时使用 numpy ndarrayCelestialObject 实例),那是因为 PyCharm 超出了此处的规范。其他工具可能不会那么宽容。

换句话说,就打字而言,您可能还使用了frobnar.FlubberdyFlub作为前向引用,它会抑制这个特定的错误一样多,效果一样;无效的前向引用。

关于python - 类型提示、与前向引用的联合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35776791/

相关文章:

python - 像 spyder 那样打包一个 python 应用程序

php - PDO 可以有带有类型化字符串参数的方法,但我不能在我自己的函数中执行此操作,这是怎么回事?

python - 如何在 Vim 中通过 ALE 运行 isort?

python - 如何将 timedelta 转换为字符串并再次返回

python - Pytube 库 - 尝试访问视频数据时收到 "pytube.exceptions.RegexMatchError: regex pattern"错误

python - 输入未导入的模块

PHP 提示动态返回类型(基于 $class 参数)

Python:比较两个字符串并返回它们共有的最长段

python - python-rtmidi示例代码中的AttributeError

arrays - 如何从文本 linux 命令的拆分在 python3 中创建 3d 数组?