接受的答案回答了问题,因为它提供了所提问题的答案。
问:检查给定对象是否属于给定类型的最佳方法是什么?如何检查对象是否继承自给定类型?
A:使用isinstance, issubclass, type根据类型进行检查。
然而,正如其他答案和 cmets 很快指出的那样,“类型检查”的概念比 Python 中的概念要多得多。自从添加了 Python 3 和 type hints 之后,也发生了很大的变化。下面,我将讨论类型检查、鸭子类型和异常处理的一些困难。对于那些认为不需要类型检查的人(通常不需要,但我们在这里),我还指出了如何使用类型提示来代替。
类型检查
类型检查在 python 中并不总是合适的。考虑以下示例:
def sum(nums):
"""Expect an iterable of integers and return the sum."""
result = 0
for n in nums:
result += n
return result
要检查输入是否是整数的可迭代,我们遇到了一个主要问题。检查每个元素是否为整数的唯一方法是循环检查每个元素。但是如果我们循环遍历整个迭代器,那么预期的代码将一无所有。在这种情况下,我们有两种选择。
-
在我们循环时检查。
-
事先检查,但在检查时存储所有内容。
选项 1 的缺点是使我们的代码复杂化,尤其是当我们需要在许多地方执行类似的检查时。它迫使我们将类型检查从函数的顶部移到我们在代码中使用可迭代对象的任何地方。
选项 2 的明显缺点是它破坏了迭代器的全部用途。关键是不要存储数据,因为我们不需要。
也有人可能认为检查是否检查所有元素太多,那么也许我们可以只检查输入本身是否为可迭代类型,但实际上并没有任何可迭代的基类。任何实现__iter__ 的类型都是可迭代的。
异常处理和 Duck 类型
另一种方法是完全放弃类型检查,而是专注于异常处理和鸭子类型。也就是说,将您的代码包装在一个 try-except 块中并捕获发生的任何错误。或者,不要做任何事情,让异常从您的代码中自然产生。
这是捕获异常的一种方法。
def sum(nums):
"""Try to catch exceptions?"""
try:
result = 0
for n in nums:
result += n
return result
except TypeError as e:
print(e)
与之前的选项相比,这当然更好。我们在运行代码时进行检查。如果任何地方有TypeError,我们就会知道。我们不必在循环输入的任何地方进行检查。而且我们不必在迭代时存储输入。
此外,这种方法可以实现鸭式打字。我们不再检查specific types,而是检查specific behaviors,并寻找输入何时无法按预期运行(在这种情况下,循环通过nums并能够添加n)。
但是,使异常处理变得很好的确切原因也可能是它们的失败。
-
float 不是int,但它满足行为要求。
-
用 try-except 块包装整个代码也是不好的做法。
起初这些似乎不是问题,但这里有一些可能会改变你想法的原因。
-
用户不能再期望我们的函数按预期返回int。这可能会破坏其他地方的代码。
-
由于异常可能来自多种来源,因此在整个代码块上使用 try-except 可能最终会捕获您不打算捕获的异常。我们只想检查nums 是否是可迭代的并且有整数元素。
-
理想情况下,我们希望在我们的代码生成器中捕获异常,并在它们的位置引发更多信息异常。当其他人的代码引发异常时,除了您没有编写的行之外没有任何解释并且发生了一些TypeError,这并不有趣。
为了解决上述问题的异常处理,我们的代码将变成这样......可恶。
def sum(nums):
"""
Try to catch all of our exceptions only.
Re-raise them with more specific details.
"""
result = 0
try:
iter(nums)
except TypeError as e:
raise TypeError("nums must be iterable")
for n in nums:
try:
result += int(n)
except TypeError as e:
raise TypeError("stopped mid iteration since a non-integer was found")
return result
你可以看到这是怎么回事。我们尝试“正确”检查的次数越多,我们的代码看起来就越糟糕。与原始代码相比,这根本不可读。
我们可以说这可能有点极端。但另一方面,这只是一个非常简单的例子。实际上,您的代码可能比这复杂得多。
输入提示
我们已经看到当我们尝试修改我们的小示例以“启用类型检查”时会发生什么。类型提示不是专注于尝试强制特定类型,而是允许一种使用户清楚类型的方法。
from typing import Iterable
def sum(nums: Iterable[int]) -> int:
result = 0
for n in nums:
result += n
return result
以下是使用类型提示的一些优点。
-
现在的代码实际上看起来不错!
-
如果您使用类型提示,您的编辑器可能会执行静态类型分析!
-
它们存储在函数/类中,使它们可以动态使用,例如typeguard 和 dataclasses。
-
它们在使用help(...) 时出现在函数中。
-
无需根据描述检查您的输入类型是否正确或更糟糕的是缺少描述。
-
您可以根据structure “输入”提示,例如“有这个属性吗?”无需用户进行子类化。
类型提示的缺点?
- 类型提示只不过是语法和特殊文本。 它与类型检查不同。
换句话说,它实际上并没有回答问题,因为它不提供类型检查。然而,无论如何,如果您在这里进行类型检查,那么您也应该进行类型提示。当然,如果您得出的结论是类型检查实际上不是必需的,但您想要一些类似的类型,那么类型提示适合您。