Python 类中的类型暗示

Python 类中的类型暗示,python,Python,如何执行node:node?因为当我运行它时,它说,没有定义名称“Node” 我是否应该删除:节点,并在函数中对其进行实例检查? 但是,我如何访问节点的属性(我希望它是节点类的实例) 顺便说一句,我不知道如何在Python中实现类型转换。类型检查中的“self”引用通常使用字符串完成: class Node: def append_child(self, node: Node): if node != None: self.first_child = nod

如何执行
node:node
?因为当我运行它时,它说,
没有定义名称“Node”

我是否应该删除
:节点
,并在函数中对其进行实例检查? 但是,我如何访问
节点的属性(我希望它是
节点
类的实例)

顺便说一句,我不知道如何在Python中实现类型转换。类型检查中的“self”引用通常使用字符串完成:

class Node:
    def append_child(self, node: Node):
       if node != None:
        self.first_child = node
    self.child_nodes += [node]
PEP-0484一节对此进行了说明

请注意,这不会进行任何类型检查或转换。这是python(通常)完全忽略的类型提示1。但是,第三方工具(例如mypy)使用类型提示对代码进行静态分析,并可能在运行前生成错误

此外,从python3.7开始,您可以使用来自未来导入注释的
(在python4.0中,这将是默认值)将所有类型提示隐式转换为模块内的字符串


1提示是可内省的——因此,如果您真的愿意,您可以使用它们来使用装饰器或类似工具构建某种运行时检查器,但python在默认情况下不会这样做。

如果您只想得到问题的答案,请阅读。

mgilson的回答很好地解释了如何克服Python的这一限制。但我认为,很好地理解为什么这不起作用也很重要,所以我将提供这一解释

Python与其他语言略有不同。在Python中,实际上没有“声明”这样的东西。就Python而言,代码就是代码。导入模块时,Python会创建一个新的名称空间(全局变量可以存在的地方),然后从上到下执行模块的每一行
def foo(args):code
只是一个复合语句,它将一组源代码捆绑到一个函数中,并将该函数绑定到名称
foo
。类似地,
class Bar(base):code
创建一个类,立即执行所有代码(在一个单独的名称空间内,该名称空间包含可能由代码创建的任何类级变量,特别是包括使用
def
创建的方法),然后将该类绑定到名称
Bar
。它必须立即执行代码,因为所有方法都需要立即创建。因为代码是在名称绑定之前执行的,所以不能引用代码顶层的类。但是,在方法内部引用类是完全正确的,因为在调用该方法之前,该代码不会运行

(您可能想知道为什么我们不能先绑定名称,然后再执行代码。事实证明,由于Python实现类的方式,在创建类对象之前,您必须知道前面存在哪些方法。可以创建一个空类,然后将所有方法一次绑定一个th属性赋值(实际上,您可以手动执行此操作,方法是编写
类Bar:pass
,然后执行
def method1():…;Bar.method1=method1
等等),但这将导致更复杂的实现,并且更难概念化,因此Python不会执行此操作。)

要用代码进行总结:

class Node:
    def append_child(self, node: 'Node'):
       if node != None:
        self.first_child = node
    self.child_nodes += [node]
Python3.7和Python4.03.10及以后版本 PEP 563引入了延迟评估,以字符串形式存储在
\uuuuuuu注释中。用户可以通过以下指令启用此功能:

class C:
    C  # NameError: C doesn't exist yet.
    def method(self):
        return C  # This is fine.  By the time the method gets called, C will exist.
C  # This is fine; the class has been created by the time we hit this line.
这样就可以编写:

from __future__ import annotations
从Python3.10(计划发布2021-10-04)开始,这种行为将是默认的


<强>编辑2020-11-15:最初它被宣布为在Python 4中强制启动,但现在它出现了,这使我感到惊讶,因为它似乎违反了《代码》中的承诺:“YuxFuturux”,直到Python 4,这种向后兼容性才会被打破。也许开发者认为3.10比4是4,或者他们改变了主意。另请参见。

在Python>3.7中,您可以使用
dataclass
。您还可以注释
dataclass

在这个特定的示例中,
节点
引用自身,如果您运行它,您将得到

class C:
    a: C
    def foo(self, b: C):
        ...
要克服此错误,您必须包括:

NameError: name 'Node' is not defined
它必须是模块中的第一行。在Python 4.0及更高版本中,您不必包含
注释

from __future__ import annotations
例如:

from __future__ import annotations
from dataclasses import dataclass

@dataclass
class Node:
    value: int
    left: Node
    right: Node

    @property
    def is_leaf(self) -> bool:
        """Check if node is a leaf"""
        return not self.left and not self.right

谢谢!老兄…从这种语言中可以学到很多东西。即使是其他语言中最简单的通用概念在Python arrrgg中也有其独特的方式。我同意。从这种语言中可以学到很多东西。我对Python有着复杂的感受。很棒的社区支持,广泛的库生态系统。但是,由于打字太差,这种语言被点燃了“神奇”的故事快捷方式。我也猜了很多,但这是一种黑客行为还是有意义?到目前为止,这是Python 3.9中的默认设置,对吗?@lcnittl似乎是这样,这让我感到惊讶,因为它似乎违反了
\uuuu future\uuuuu
中的承诺,即它在Python 4.0之前不会成为默认设置,因为它破坏了向后兼容性。@lcnittl明白了,啊,该死,搞混了。谢谢你的澄清和跟进
node5 = Node(5, None, None)
node25 = Node(25, None, None)
node40 = Node(40, None, None)
node10 = Node(10, None, None)

# balanced tree
node30 = Node(30, node25, node40)
root = Node(20, node10, node30)

# unbalanced tree
node30 = Node(30, node5, node40)
root = Node(20, node10, node30)