输入提示,与前向引用联合

时间:2016-03-03 15:51:04

标签: 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.CelestialBodynp.ndarray类型。 (已经宣布第二个)。我根据pep尝试使用联合:

def get_total_max_distance(self, ancestor_body: typing.Union["CB.CelestialBody",np.ndarray], eps=3*np.finfo(float).eps):

然而,这个评论失败了:"属性错误:'模块'对象没有属性' 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别名。

2 个答案:

答案 0 :(得分:2)

注意:此错误最近已修复,将成为Python 3.5.3的一部分。以下答案在该版本中已过时。

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

但鉴于PEP为您提供了example,其中前向引用用于解决两个模块之间的循环依赖关系(因此,当您过早地引用时,您会发生AttributeErrors。名字),我真的很惊讶你所尝试的并不起作用。你几乎肯定发现了一个错误。

Union[...]类型检查union中的元素是否是union中另一个类型的子类时,会发生什么:触发尝试查找前向引用的检查。如果'a.A''b.B'(如循环参考示例中)要起作用,则前向引用检查应接受AttributeError作为此处处理的有效异常。实际上,此时应该吞下任何异常,因为,正如PEP所述:

  

字符串文字应该包含有效的Python表达式 [...],并且一旦模块完全加载,它应该在没有错误的情况下进行评估

强调我的。当创建Union[..]对象时,模块尚未完全加载,因此,只要允许任何有效的Python表达式,代码任何异常视为指示前向引用尚未准备好并忽略它。

解决方法是为您创建将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):

我使用Python项目filed this as a bug

至于为什么Bakuriu建议将表达式改为celestial_body.CelestialBody;只有&#39;工作&#39;因为名称celestial_body会引发NameError例外。该名称​​永远不会在您的代码上下文中工作,因此正向表达式不符合PEP(一旦模块,它赢得评估没有错误已经满载了。)

如果PyCharm无论如何都接受了这个引用并且正确地检查了该函数(例如,它只允许你在写出一个电话时使用一个numpy ndarrayCelestialObject实例),那么&#39 ;由于PyCharm超出了规范。其他工具可能不会那么宽容。

换句话说,就typing而言,您也可以使用frobnar.FlubberdyFlub作为前向参考,它会同样地抑制这个特定的错误;无效的前向引用。

答案 1 :(得分:0)

该行为的原因是CB.CelestialBody不会抛出NameError,因为全局范围包含名称CB,而AttributeError因为模块没有属性CelestialBody

来自__subclasscheck__的{​​{1}}是

typing.py

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。事实上,在咨询PEP 0484之后,似乎这是一个实际的拟议用例,用于处理循环进口问题;但是AttributeError未正确处理实际操作。

无论如何,在你的情况下,如果你在该模块中实际上不需要Union,除了类型推理,你可能根本不需要导入该模块,或者在文件的末尾导入它,例如

celestial_body

def get_total_max_distance(self, ancestor_body: "CB.CelestialBody", eps=3*np.finfo(float).eps): ... import celestial_body as CB 版本的工作原理没有任何问题 - 这是因为自身的函数注释对Python没有任何意义。 Union中的课程本身就关心给予他们的内容。

另一方面,PyCharm使用独立于typing模块的类型推断。