由于Python是动态类型的,因此我们当然可以做出这样的事情:
def f(x):
return 2 if x else "s"
但这是否是 Python 实际上预期的使用方式呢?换句话说,联合类型是否以它们在 Racket 中的方式存在?还是我们只是像这样使用它们:
def f(x):
if x:
return "s"
我们唯一需要的“联合”是与None吗?
由于Python是动态类型的,因此我们当然可以做出这样的事情:
def f(x):
return 2 if x else "s"
但这是否是 Python 实际上预期的使用方式呢?换句话说,联合类型是否以它们在 Racket 中的方式存在?还是我们只是像这样使用它们:
def f(x):
if x:
return "s"
我们唯一需要的“联合”是与None吗?
只有在使用静态类型语言时,才需要使用Union Typing,因为你需要声明一个对象可以返回多种类型之一(例如int
或str
,或者在另一个例子中是str
或NoneType
)。
Python只处理对象,所以根本不需要考虑“union types”。Python函数返回什么就是什么,如果程序员希望针对不同的结果返回不同的类型,那就是他们的选择。这个选择是架构的选择,并不会对Python解释器产生影响(因此这里没有什么可以“基准测试”的东西)。
Python 3.5引入了创建可选类型提示的标准,该标准包括Union[...]
和Optional[...]
注释。类型提示在运行时外添加可选的静态类型检查,就像TypeScript中的类型不属于JavaScript运行时一样。
True、False、None 或整数
,则需要重新思考函数设计。 - Martijn Pieterstyping.Union
不是运行时类型。 - Martijn Pieters由于Python是一种动态类型语言,因此类型本身并不存在,但在更新的Python版本中,联合类型是用于类型提示的选项之一。
from typing import Union,TypeVar
T = TypeVar('T')
def f(x: T) -> Union[str, None]:
if x:
return "x"
T = TypeVar('T')
生成了一个具名泛型。该答案提供的方法接受任何输入,并在所提供的内容不是 None
时返回字符串 "x"。这里使用具名泛型完全没有必要,但我建议您研究一下它们,因为它们允许创建非常有用的模板函数。 - WebWanderer注意: 如其他人所提到的,Python类型提示(默认情况下)对运行时行为没有任何影响,它用于静态分析等。
从Python 3.10开始,您可以使用|
符号表示联合类型。以下是来自Python 3.10新特性的示例:
def square(number: int | float) -> int | float:
return number ** 2
# Instead of
def square(number: Union[int, float]) -> Union[int, float]:
return number ** 2
此外,如果您正在使用Python 3.7+,则可以通过使用__future__
包来使用该功能,但有一些限制:
from __future__ import annotations
# Works in Python 3.7+
def square(number: int | float) -> int | float:
return number ** 2
# Works only in Python 3.10+
isinstance(3.10, int | float)
numeric = int | float
更多信息请参阅Union Types文档和PEP 604。
Enum + Tuples
from enum import Enum
Token = Enum('Token', ['Number', 'Operator', 'Identifier', 'Space', 'Expression'])
(Token.Number, 42) # int
(Token.Operator, '+') # str
(Token.Identifier, 'foo') # str
(Token.Space, ) # void
(Token.Expression, ('lambda', 'x', 'x+x')) # tuple[str]
A slight variation on this uses a dedicated SumType
class instead of a tuple:
from dataclasses import dataclass
from typing import Any
@dataclass
class SumType:
enum: Enum
data: Any
SumType(Token.Number, 42)
isinstance
if isinstance(data, int):
...
if isinstance(data, str):
...
Or in combination with the "enum" idea from above:
token = SumType(Token.Number, 42)
if token.enum == Token.Number:
...
sumtypes module
当然,这些方法都有各自的缺点。
之前的回答没有讨论到一种用例:从现有类型中构建一个 union 类型,并要求 isinstance()
将任何现有类型的实例视为 union 类型的实例。
Python 通过 抽象基类 支持此功能。例如:
>>> import abc
>>> class IntOrString(abc.ABC): pass
...
>>> IntOrString.register(int)
<class 'int'>
>>> IntOrString.register(str)
<class 'str'>
int
和str
可以被看作是IntOrString
的子类:>>> issubclass(int, IntOrString)
True
>>> isinstance(42, IntOrString)
True
>>> isinstance("answer", IntOrString)
True
补充@MartijnPieters的答案:
但这是Python实际上要使用的方式吗?
在任何语言中,根据参数返回不同类型永远都不是一个好习惯。这使得测试、维护和扩展代码变得非常困难,并且在我看来是一种反模式(但当然有时是必要的恶)。结果至少应该通过具有共同接口来相关联。
union
被引入到C中唯一的原因是性能提升。但是在Python中,由于语言的动态性质(如Martijn所指出的),您不会获得这种性能提升。实际上,引入union
会降低性能,因为union
的大小始终是最大成员的大小。因此,Python永远不会像C一样拥有union
。
json.loads()
是一个必要的恶。使用None
的“联合”是一种普遍的做法,但在我看来也应该避免。特别是在更大的项目中,你无法停止阅读这些“NoneType object has no attribute xxx”的日志。我的个人观点是:一个函数==一个返回类型。 - freakishjson.loads()
正好是你要找的。更确切地说,json.loads()
返回任意类型,包括那些你作为程序员甚至没有想到过的类型。因此,你甚至不能原则上定义一个联合类型来覆盖它可以返回的所有内容。 - John Y根据Python 3.10的更新,我们可以通过使用“|”将对象类型分隔来在Python中创建联合类型。
例如:
def method(mobject: int | str) -> int | str | None:
pass