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
How do I specify that the return type of a method is the same as the class itself?
提问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 Position
must 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 annotations
will 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 typing
you 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 self
or 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:
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 NameError
and 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 annotations
and 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 annotations
to 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.
通过这样做,重命名类并不意味着修改类型提示。但我个人不希望聪明的代码编辑器能很好地处理这种形式。