在Python中检查类型的规范方法是什么?

如何检查一个对象是否属于给定类型,或者它是否继承自给定类型?

如何检查对象o是否为类型str


初学者经常错误地期望字符串已经是“一个数字”-要么期望Python 3. xinput转换类型,要么期望像'1'这样的字符串同时也是一个整数。对于这些问题来说,这是错误的规范。请仔细阅读问题,然后使用如何检查字符串是否代表数字(浮点数或整数)?如何将输入读取为数字?和/或向用户询问输入,直到他们给出有效的响应

1280917 次浏览
isinstance(o, str)

链接到文档

如果ostr或者是继承自str的类型,则isinstance(o, str)将返回True

type(o) is str将返回True当且仅当o是一个str。如果o是从str继承的类型,它将返回False

使用#0检查o是否是str的实例或str的任何子类:

if isinstance(o, str):

要检查o的类型是否正好是str不包括#1的子类

if type(o) is str:

上述的另一种选择:

if issubclass(type(o), str):

有关相关信息,请参阅Python库参考中的内置函数


在Python 2中检查字符串

对于Python 2,这是检查o是否为字符串的更好方法:

if isinstance(o, basestring):

#0不是str的子类;而strunicode都是#4的子类。在Python 3中,basestring不再存在,因为有str0个字符串(str1)和二进制数据(str2)。

或者,isinstance接受类的元组。如果o(str, unicode)中任何一个的任何子类的实例,这将返回True

if isinstance(o, (str, unicode)):

我认为使用像Python这样的动态语言的很酷的事情是你真的不应该检查这样的东西。

我只会调用你的对象所需的方法并捕获一个AttributeError。稍后,这将允许你使用其他(看似不相关的)对象调用你的方法来完成不同的任务,例如模拟对象进行测试。

当使用urllib2.urlopen()从Web获取数据时,我经常使用它,它返回一个文件如对象。这可以反过来传递给几乎任何从文件读取的方法,因为它实现了与真实文件相同的read()方法。

但我相信有一个使用isinstance()的时间和地点,否则它可能不会在那里:)

大多数 Pythonic检查对象类型的方法是……不检查它。

由于Python鼓励鸭子打字,你应该try...except以你想要的方式使用对象的方法。所以如果你的函数正在寻找一个可写的文件对象,不要检查它是否是file的子类,只要尝试使用它的.write()方法!

当然,有时这些很好的抽象会崩溃,isinstance(obj, cls)就是你需要的。但要谨慎使用。

致雨果:

你可能是指list而不是array,但这指出了类型检查的整个问题——你不想知道有问题的对象是一个列表,你想知道它是某种序列还是一个单一的对象。所以试着像序列一样使用它。

假设您想将对象添加到现有序列中,或者如果它是一个对象序列,则将它们全部添加

try:my_sequence.extend(o)except TypeError:my_sequence.append(o)

其中一个技巧是,如果您正在处理字符串和/或字符串序列-这很棘手,因为字符串通常被认为是单个对象,但它也是一系列字符。更糟糕的是,因为它实际上是一系列单长度字符串。

我通常选择设计我的API,使其只接受单个值或序列——这使事情变得更容易。如果需要,当您传入单个值时,在它周围放一个[ ]并不难。

(虽然这可能会导致字符串错误,因为它们看起来像(是)序列。

在问题被问到并回答之后,类型提示已添加到Python。Python中的类型提示允许检查类型,但方式与静态类型语言非常不同。Python中的类型提示将预期的参数类型与函数相关联,作为与函数关联的运行时可访问数据,这允许用于要检查的类型。类型提示语法示例:

def foo(i: int):return i
foo(5)foo('oops')

在这种情况下,我们希望为foo('oops')触发一个错误,因为参数的注释类型是int。添加的类型提示不会导致脚本正常运行时发生错误。但是,它向描述其他程序可以查询和用于检查类型错误的预期类型的函数添加属性。

可用于查找类型错误的其他程序之一是mypy

mypy script.pyscript.py:12: error: Argument 1 to "foo" has incompatible type "str"; expected "int"

(您可能需要从包管理器安装mypy。我认为它不附带CPython,但似乎具有某种程度的“官方性”。)

这种方式的类型检查不同于静态类型编译语言中的类型检查。因为类型在Python中是动态的,所以类型检查必须在运行时完成,如果我们坚持每次都要进行,这就会带来成本——即使是在正确的程序上。显式类型检查也可能比需要的更具限制性,并导致不必要的错误(例如参数真的需要完全是list类型吗?或者任何可迭代的东西都足够了吗?)。

显式类型检查的好处是它可以比鸭子类型更早地发现错误并给出更清晰的错误消息。鸭子类型的确切要求只能用外部留档表示(希望它是彻底和准确的),不兼容类型的错误可能发生在远离它们起源的地方。

Python的类型提示旨在提供一种折衷方案,可以指定和检查类型,但在通常的代码执行期间没有额外的成本。

typing包提供了类型变量,可以在类型提示中使用这些变量来表达所需的行为,而无需特定类型。例如,它包括IterableCallable等变量,用于提示来指定对具有这些行为的任何类型的需求。

虽然类型提示是检查类型的最Pythonic的方式,但完全不检查类型并依赖鸭子类型通常更Pythonic。类型提示相对较新,何时它们是最Pythonic的解决方案还没有定论。一个相对没有争议但非常一般的比较:类型提示提供了一种可以强制执行的留档形式,允许代码更早生成且更容易理解错误,可以捕获鸭子类型无法捕获的错误,并且可以静态检查(在一种不寻常的意义上,但它仍然在运行时之外)。另一方面,鸭子类型长期以来一直是Pythonic方式,不会强加静态类型的认知开销,不那么冗长,并且会接受所有可行的类型,然后是一些。

对于更复杂的类型验证,我喜欢打字机基于python类型提示注释的验证方法:

from typeguard import check_typefrom typing import List
try:check_type('mylist', [1, 2], List[int])except TypeError as e:print(e)

您可以以非常清晰易读的方式执行非常复杂的验证。

check_type('foo', [1, 3.14], List[Union[int, float]])# vsisinstance(foo, list) and all(isinstance(a, (int, float)) for a in foo)

您可以使用类型的__name__检查变量的类型。

例如:

>>> a = [1,2,3,4]>>> b = 1>>> type(a).__name__'list'>>> type(a).__name__ == 'list'True>>> type(b).__name__ == 'list'False>>> type(b).__name__'int'

我认为最好的方法是输入好你的变量。你可以通过使用“键入”库来做到这一点。

示例:

from typing import NewTypeUserId = NewType ('UserId', int)some_id = UserId (524313`)

https://docs.python.org/3/library/typing.html

检查类型的一个简单方法是将其与您知道的类型进行比较。

>>> a  = 1>>> type(a) == type(1)True>>> b = 'abc'>>> type(b) == type('')True

接受的答案回答了问题,因为它提供了所问问题的答案。

问:检查给定对象是否属于给定类型的最佳方法是什么?检查对象是否继承自给定类型如何?

答:使用isinstance, issubclass, type检查类型。

然而,正如其他答案和评论很快指出的那样,“类型检查”的想法比python中的要多得多。自从Python 3和类型提示的添加以来,也发生了很多变化。下面,我将讨论类型检查、鸭子类型和异常处理的一些困难。对于那些认为不需要类型检查的人(通常不是,但我们在这里),我还指出如何使用类型提示。

类型检查

在python中,类型检查并不总是合适的事情。考虑以下示例:

def sum(nums):"""Expect an iterable of integers and return the sum."""result = 0for n in nums:result += nreturn result

为了检查输入是否是可迭代的整数,我们遇到了一个主要问题。检查是否每个元素都是整数的唯一方法是循环检查每个元素。但是如果我们遍历整个迭代器,那么没有任何东西留给预期的代码。在这种情况下,我们有两种选择。

  1. 循环时检查。

  2. 事先检查,但在检查时存储所有内容。

选项1的缺点是使代码复杂化,特别是当我们需要在许多地方执行类似的检查时。它迫使我们将类型检查从函数顶部移动到到处都是,我们在代码中使用可迭代。

选项2有一个明显的缺点,它破坏了迭代器的整个目的。整个重点是不存储数据,因为我们不应该需要。

有人可能会认为检查是否检查所有元素太多了,那么也许我们可以检查输入本身是否是可迭代的类型,但实际上没有任何可迭代的基类。任何实现__iter__的类型都是可迭代的。

异常处理和鸭子类型

另一种方法是完全放弃类型检查,而是专注于异常处理和鸭子类型。也就是说,将你的代码包装在一个try-但块中并捕获任何发生的错误。或者,什么都不做,让异常从你的代码中自然产生。

这里有一种方法来捕获异常。

def sum(nums):"""Try to catch exceptions?"""try:result = 0for n in nums:result += nreturn resultexcept TypeError as e:print(e)

与之前的选项相比,这当然更好。我们在运行代码时进行检查。如果任何地方有TypeError,我们就会知道。我们不必在循环输入的每个地方进行检查。当我们迭代输入时,我们不必存储输入。

此外,这种方法支持鸭子类型。我们不再检查specific types,而是检查specific behaviors并查找输入何时未能按预期运行(在这种情况下,循环遍历nums并能够添加n)。

然而,使异常处理良好的确切原因也可能是它们的垮台。

  1. Afloat不是int,但它满足行为的工作要求。

  2. 将整个代码包装成一个try-除了块也是不好的做法。

起初,这些似乎不是问题,但这里有一些原因可能会改变你的想法。

  1. 用户不能再期望我们的函数按预期返回int。这可能会破坏其他地方的代码。

  2. 由于异常可以来自各种各样的来源,因此在整个代码块上使用try-除了可能最终会捕获你不想捕获的异常。我们只想检查nums是否可迭代并且有整数元素。

  3. 理想情况下,我们希望捕获我们的代码生成器的异常,并在它们的位置引发更多信息异常。当从别人的代码中引发异常时,除了你没有写的一行并且发生了一些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 = 0for n in nums:result += nreturn result

以下是使用类型提示的一些优点。

  • 代码现在看起来不错!

  • 如果您使用类型提示,您的编辑器可能会执行静态类型分析!

  • 它们存储在函数/类中,使它们动态可用,例如#0#1

  • 它们在使用help(...)时显示为函数。

  • 无需根据描述或更糟糕的是缺乏描述来检查您的输入类型是否正确。

  • 您可以基于结构“键入”提示,例如“它有这个属性吗?”而不需要用户进行子类化。

类型提示的缺点?

  • 类型提示本身只不过是语法和特殊文本。这和类型检查不一样

换句话说,它实际上并没有回答这个问题,因为它没有提供类型检查。无论如何,如果你来这里是为了类型检查,那么你也应该是类型提示。当然,如果你得出结论,类型检查实际上不是必要的,但你想要一些打字的外观,那么类型提示适合你。

python3.10中,您可以在isinstance中使用|

>>> isinstance('1223', int | str)True
>>> isinstance('abcd', int | str)True