TLDR:
- 除非您有理由不这样做,否则请使用
if isinstance(x, int):。
- 如果您只需要精确的类型相等,请使用
if type(x) is int:。
- 如果您可以转换为目标类型,请使用
try: ix = int(x)。
Python 中的类型检查有一个很大的“取决于”。处理类型的方法有很多种,各有利弊。在 Python3 中,出现了更多。
显式类型相等
类型是一流的对象,您可以像对待任何其他值一样对待它们。
因此,如果您希望某物的类型等于int,只需对其进行测试:
if type(x) is int:
这是最严格的测试类型:它要求精确类型相等。通常,这不是您想要的:
- 它排除了替代类型:
float 将无效,即使它在许多方面的行为类似于 int。
- 它排除了子类和抽象类型:打印漂亮的
int 子类或enum 将被拒绝,即使它们在逻辑上是整数。
- 这严重限制了可移植性:Python2 字符串可以是任一
str 或unicode,整数可以是任一int 或long。李>
请注意,显式类型相等可用于低级操作:
- 某些类型不能被子类化,例如
slice。一个明确的检查,嗯,在这里更明确。
- 一些低级操作(例如序列化或 C-API)需要特定类型。
变体
还可以针对__class__ 属性进行比较:
if x.__class__ is int:
请注意,如果一个类定义了 __class__ 属性,这与 type(x) 不同。
当有多个类要检查时,使用dict 来调度动作比显式检查更具可扩展性并且可以更快(≥5-10 种类型)。
这对于转换和序列化特别有用:
dispatch_dict = {float: round, str: int, int: lambda x: x}
def convert(x):
converter = self.dispatch_dict[type(x)] # lookup callable based on type
return converter(x)
显式类型的实例检查
惯用类型测试使用isinstancebuiltin:
if isinstance(x, int):
此检查既准确又高效。这通常是人们想要检查类型的:
- 它可以正确处理子类型。一个打印漂亮的
int 子类仍然可以通过这个测试。
- 它允许一次检查多种类型。在 Python2 中,执行
isinstance(x, (int, long)) 可以获得所有内置整数。
最重要的是,大多数时候缺点可以忽略不计:
- 它仍然接受行为怪异的时髦子类。由于任何东西都可以以奇怪的方式表现,因此防范是徒劳的。
- 它很容易过于限制:当任何序列(例如
tuple)甚至可迭代(例如generator)都可以时,许多人检查isinstance(x, list)。与脚本或应用程序相比,通用库更需要关注这一点。
变体
如果您已经有一个类型,issubclass 的行为相同:
if issubclass(x_type, int):
抽象类型的实例检查
Python 有一个abstract base classes 的概念。粗略地说,这些表达了类型的含义,而不是它们的层次结构:
if isinstance(x, numbers.Real): # accept anything you can sum up like a number
换句话说,type(x) 不一定继承来自numbers.Real,但必须表现喜欢它。
不过,这是一个非常复杂和困难的概念:
- 如果您正在寻找基本类型,这通常是多余的。大多数情况下,整数只是
int。
- 来自其他语言的人经常混淆它的概念。
- 将其与例如C++,重点是 abstract base 类而不是 abstract 基类。
- ABC 可以像 Java 接口一样使用,但仍可能具有具体功能。
但是,它对于泛型库和抽象非常有用。
- 许多函数/算法不需要显式类型,只需要它们的行为。
- 如果您只需要按键查找内容,
dict 会将您限制为特定的内存类型。相比之下,collections.abc.Mapping 还包括数据库包装器、大型磁盘支持的字典、惰性容器...... - 和 dict。
- 它允许表达部分类型约束。
- 没有严格的基类型实现迭代。但是,如果您根据
collections.abc.Iterable 检查对象,它们都会在 for 循环中工作。
- 它允许创建显示为相同抽象类型的单独、优化的实现。
虽然一次性脚本通常不需要它,但我强烈建议将它用于除了几个 python 版本之外的任何东西。
暂定转换
处理类型的惯用方式不是测试它们,而是假设它们是兼容的。如果您已经预料到输入中有一些错误的类型,只需跳过所有不兼容的内容:
try:
ix = int(x)
except (ValueError, TypeError):
continue # not compatible with int, try the next one
else:
a.append(ix)
这实际上不是类型检查,但通常具有相同的目的。
-
保证您的输出中有预期的类型。
- 它在转换错误类型方面有一些有限的回旋余地,例如专门将
float 转为int。
- 它可以在您不知道哪些类型符合
int 的情况下工作。
主要的缺点是它是一个显式转换。
- 您可以静默接受“错误”值,例如转换包含文字的
str。
- 它甚至会不必要地转换足够好的类型,例如
float 到 int 当您只需要数字时。
转换是一些特定用例的有效工具。如果您大致了解您的输入是什么,并且必须对您的输出做出保证,则效果最好。
函数调度
有时类型检查的目标只是选择一个合适的函数。在这种情况下,functools.singledispatch 之类的函数调度允许针对特定类型专门化函数实现:
@singledispatch
def append_int(value, sequence):
return
@append_int.register
def _(value: int, sequence):
sequence.append(value)
这是isinstance 和dict 调度的组合。它对大型应用程序最有用:
- 无论分派类型的数量如何,它都会使使用站点保持较小。
- 它允许以后为其他类型注册特化,即使在其他模块中也是如此。
不过,它并非没有缺点:
- 源于函数式和强类型语言,许多 Python 程序员不熟悉单调度甚至多调度。
- 调度需要单独的函数,因此不适合在使用现场定义。
- 创建函数和“预热”调度缓存需要显着的运行时开销。调度函数应该定义一次并经常重复使用。
- 即使是预热的调度表也比手写的 if/else 或
dict 查找要慢。
控制输入
最好的做法是确保您从一开始就不必检查类型。这有点像元主题,因为它在很大程度上取决于用例。
这里,somelist 的来源不应该将非数字放入其中。