Python 如何指定方法的返回类型与类本身相同?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/33533148/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 13:31:35  来源:igfitidea点击:

How do I specify that the return type of a method is the same as the class itself?

pythonpython-3.xpycharmtypingpython-3.5

提问by Michael van Gerwen

I have the following code in python 3:

我在python 3中有以下代码:

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: Position) -> Position:
        return Position(self.x + other.x, self.y + other.y)

But my editor (PyCharm) says that the reference Position can not be resolved (in the __add__method). How should I specify that I expect the return type to be of type Position?

但是我的编辑器(PyCharm)说参考 Position 无法解析(在__add__方法中)。我应该如何指定我希望返回类型为 type Position

Edit: I think this is actually a PyCharm issue. It actually uses the information in its warnings, and code completion

编辑:我认为这实际上是 PyCharm 问题。它实际上使用了警告中的信息和代码完成

But correct me if I'm wrong, and need to use some other syntax.

但是如果我错了,请纠正我,并且需要使用其他一些语法。

采纳答案by Paulo Scardine

TL;DR: if you are using Python 4.0 it just works. As of today (2019) in 3.7+ you must turn this feature on using a future statement (from __future__ import annotations) - for Python 3.6 or below use a string.

TL;DR:如果您使用的是 Python 4.0,它就可以工作。从今天(2019 年)开始,在 3.7+ 中,您必须使用 future 语句 ( from __future__ import annotations)打开此功能- 对于 Python 3.6 或更低版本,请使用字符串。

I guess you got this exception:

我猜你有这个例外:

NameError: name 'Position' is not defined

This is because Positionmust be defined before you can use it in an annotation unless you are using Python 4.

这是因为Position除非您使用的是 Python 4,否则必须先定义才能在注释中使用它。

Python 3.7+: from __future__ import annotations

Python 3.7+: from __future__ import annotations

Python 3.7 introduces PEP 563: postponed evaluation of annotations. A module that uses the future statement from __future__ import annotationswill store annotations as strings automatically:

Python 3.7 引入了PEP 563:延迟评估注释。使用 future 语句的模块from __future__ import annotations将自动将注释存储为字符串:

from __future__ import annotations

class Position:
    def __add__(self, other: Position) -> Position:
        ...

This is scheduled to become the default in Python 4.0. Since Python still is a dynamically typed language so no type checking is done at runtime, typing annotations should have no performance impact, right? Wrong! Before python 3.7 the typing module used to be one of the slowest python modules in coreso if you import typingyou will see up to 7 times increase in performancewhen you upgrade to 3.7.

这计划成为 Python 4.0 中的默认设置。由于 Python 仍然是一种动态类型语言,因此在运行时不进行类型检查,因此类型注释应该不会影响性能,对吗?错误的!蟒蛇3.7曾经是打字模块之前,核心的最低Python模块之一所以如果import typing你将会看到多达7倍的性能提升,当你升级到3.7。

Python <3.7: use a string

Python <3.7:使用字符串

According to PEP 484, you should use a string instead of the class itself:

根据 PEP 484,您应该使用字符串而不是类本身:

class Position:
    ...
    def __add__(self, other: 'Position') -> 'Position':
       ...

If you use the Django framework this may be familiar as Django models also use strings for forward references (foreign key definitions where the foreign model is selfor is not declared yet). This should work with Pycharm and other tools.

如果您使用 Django 框架,这可能很熟悉,因为 Django 模型还使用字符串进行前向引用(外部模型已self声明或尚未声明的外键定义)。这应该适用于 Pycharm 和其他工具。

Sources

来源

The relevant parts of PEP 484and PEP 563, to spare you the trip:

PEP 484PEP 563的相关部分,让你省心:

Forward references

When a type hint contains names that have not been defined yet, that definition may be expressed as a string literal, to be resolved later.

A situation where this occurs commonly is the definition of a container class, where the class being defined occurs in the signature of some of the methods. For example, the following code (the start of a simple binary tree implementation) does not work:

前向引用

当类型提示包含尚未定义的名称时,该定义可以表示为字符串文字,以便稍后解析。

通常发生这种情况的情况是容器类的定义,其中正在定义的类出现在某些方法的签名中。例如,下面的代码(一个简单的二叉树实现的开始)不起作用:

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

To address this, we write:

为了解决这个问题,我们写:

class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

The string literal should contain a valid Python expression (i.e., compile(lit, '', 'eval') should be a valid code object) and it should evaluate without errors once the module has been fully loaded. The local and global namespace in which it is evaluated should be the same namespaces in which default arguments to the same function would be evaluated.

字符串字面量应该包含一个有效的 Python 表达式(即 compile(lit, '', 'eval') 应该是一个有效的代码对象),并且一旦模块完全加载,它应该没有错误地进行评估。评估它的本地和全局命名空间应该是相同的命名空间,在其中评估同一函数的默认参数。

and PEP 563:

和 PEP 563:

In Python 4.0, function and variable annotations will no longer be evaluated at definition time. Instead, a string form will be preserved in the respective __annotations__dictionary. Static type checkers will see no difference in behavior, whereas tools using annotations at runtime will have to perform postponed evaluation.

...

The functionality described above can be enabled starting from Python 3.7 using the following special import:

在 Python 4.0 中,将不再在定义时评估函数和变量注释。相反,字符串形式将保留在相应的__annotations__字典中。静态类型检查器在行为上没有区别,而在运行时使用注释的工具将不得不执行延迟评估。

...

可以使用以下特殊导入从 Python 3.7 开始启用上述功能:

from __future__ import annotations

Things that you may be tempted to do instead

你可能想做的事情

A. Define a dummy Position

A. 定义一个假人 Position

Before the class definition, place a dummy definition:

在类定义之前,放置一个虚拟定义:

class Position(object):
    pass


class Position(object):
    ...

This will get rid of the NameErrorand may even look OK:

这将摆脱NameError甚至可能看起来不错:

>>> Position.__add__.__annotations__
{'other': __main__.Position, 'return': __main__.Position}

But is it?

但是是吗?

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: False
other is Position: False

B. Monkey-patch in order to add the annotations:

B. Monkey-patch 以添加注释:

You may want to try some Python meta programming magic and write a decorator to monkey-patch the class definition in order to add annotations:

您可能想尝试一些 Python 元编程魔法并编写一个装饰器来修补类定义以添加注释:

class Position:
    ...
    def __add__(self, other):
        return self.__class__(self.x + other.x, self.y + other.y)

The decorator should be responsible for the equivalent of this:

装饰者应该负责相当于:

Position.__add__.__annotations__['return'] = Position
Position.__add__.__annotations__['other'] = Position

At least it seems right:

至少看起来是对的:

>>> for k, v in Position.__add__.__annotations__.items():
...     print(k, 'is Position:', v is Position)                                                                                                                                                                                                                  
return is Position: True
other is Position: True

Probably too much trouble.

恐怕太麻烦了。

Conclusion

结论

If you are using 3.6 or below use a string literal containing the class name, in 3.7 use from __future__ import annotationsand it will just work.

如果您使用 3.6 或更低版本,请使用包含类名的字符串文字,在 3.7 中使用from __future__ import annotations它就可以了。

回答by jsbueno

The name 'Position' is not avalilable at the time the class body itself is parsed. I don't know how you are using the type declarations, but Python's PEP 484 - which is what most mode should use if using these typing hints say that you can simply put the name as a string at this point:

在解析类主体本身时,名称“Position”不可用。我不知道您是如何使用类型声明的,但是 Python 的 PEP 484 - 如果使用这些输入提示,您可以简单地将名称作为字符串,这是大多数模式应该使用的:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

Check https://www.python.org/dev/peps/pep-0484/#forward-references- tools conforming to that will know to unwrap the class name from there and make use of it.(It is always important to have in mind that the Python language itself does nothing of these annotations - they are usually meant for static-code analysis, or one could have a library/framework for type checking in run-time - but you have to explicitly set that).

检查https://www.python.org/dev/peps/pep-0484/#forward-references- 符合该标准的工具将知道从那里解开类名并使用它。(拥有请记住,Python 语言本身对这些注释没有任何作用——它们通常用于静态代码分析,或者可以有一个用于在运行时进行类型检查的库/框架——但你必须明确设置它)。

updateAlso, as of Python 3.8, check pep-563- as of Python 3.8 it is possible to write from __future__ import annotationsto defer the evaluation of annotations - forward referencing classes should work straightforward.

更新此外,从 Python 3.8 开始,检查pep-563- 从 Python 3.8 开始,可以编写from __future__ import annotations延迟注释的评估 - 前向引用类应该可以直接工作。

回答by vbraun

Specifying the type as string is fine, but always grates me a bit that we are basically circumventing the parser. So you better not misspell any one of these literal strings:

将类型指定为字符串很好,但总是让我感到有些恼火,因为我们基本上是在绕过解析器。所以你最好不要拼错这些文字字符串中的任何一个:

def __add__(self, other: 'Position') -> 'Position':
    return Position(self.x + other.x, self.y + other.y)

A slight variation is to use a bound typevar, at least then you have to write the string only once when declaring the typevar:

一个轻微的变化是使用绑定类型变量,至少在声明类型变量时你必须只写一次字符串:

from typing import TypeVar

T = TypeVar('T', bound='Position')

class Position:

    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __add__(self, other: T) -> T:
        return Position(self.x + other.x, self.y + other.y)

回答by Yvon DUTAPIS

When a string-based type hint is acceptable, the __qualname__item can also be used. It holds the name of the class, and it is available in the body of the class definition.

当可以接受基于字符串的类型提示时,__qualname__也可以使用该项目。它保存类的名称,并且在类定义的主体中可用。

class MyClass:
    @classmethod
    def make_new(cls) -> __qualname__:
        return cls()

By doing this, renaming the class does not imply modifying the type hints. But I personally would not expect smart code editors to handle this form well.

通过这样做,重命名类并不意味着修改类型提示。但我个人不希望聪明的代码编辑器能很好地处理这种形式。