如何检查对象是否为给定类型,或是否从给定类型继承?

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


初学者通常错误地期望字符串已经是“数字”——要么期望Python 3.x输入转换类型,要么期望像“1”这样的字符串同时也是整数。对于这些问题,这是错误的规范。请仔细阅读问题,然后使用How do I check if a string representative a number(float or int)?,如何将输入读取为数字?和/或询问用户输入,直到他们给出适当的有效响应。


isinstance(o, str)

链接到文档


如果o是str或是从str继承的类型,isinstance(o,str)将返回True。

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


使用isinstance检查o是str的实例还是str的任何子类:

if isinstance(o, str):

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

if type(o) is str:

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


检查Python 2中的字符串

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

if isinstance(o, basestring):

因为这也将捕获Unicode字符串。unicode不是str的子类;str和unicode都是basestring的子类。在Python3中,basestring不再存在,因为字符串(str)和二进制数据(字节)有严格的分隔。

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

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

我认为使用Python这样的动态语言很酷的一点是你真的不需要检查这样的东西。

我只需要在对象上调用所需的方法并捕获AttributeError。稍后,这将允许您使用其他(看似无关的)对象调用方法,以完成不同的任务,例如模拟对象进行测试。

在使用urllib2.urlopen()从web获取数据时,我经常使用这种方法,它返回一个类似文件的对象。这又可以传递给几乎任何从文件读取的方法,因为它实现了与实际文件相同的read()方法。

但我确信使用isinstance()是有时间和地点的,否则它可能不会出现:)


检查对象类型的最Python方法是……不检查它。

既然Python鼓励Duck打字,那么您应该尝试。。。除了按照您想要的方式使用对象的方法。因此,如果您的函数正在寻找一个可写的文件对象,不要检查它是否是文件的子类,只需尝试使用它的.write()方法!

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


致雨果:

您可能是指列表而不是数组,但这表明了类型检查的全部问题——您不想知道所讨论的对象是否是列表,您想知道它是某种序列还是单个对象。所以试着像一个序列一样使用它。

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

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('ops')触发一个错误,因为参数的注释类型是int。当脚本正常运行时,添加的类型提示不会导致错误发生。但是,它向函数中添加了描述其他程序可以查询并用于检查类型错误的预期类型的属性。

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

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

(您可能需要从软件包管理器中安装mypy。我认为它不随CPython一起提供,但似乎具有某种程度的“官方性”。)

这种方式的类型检查不同于静态类型编译语言中的类型检查。因为Python中的类型是动态的,所以必须在运行时进行类型检查,如果我们坚持在每一个机会都进行类型检查的话,这会增加成本——甚至对正确的程序也是如此。显式类型检查也可能比所需的更具限制性,并导致不必要的错误(例如,参数真的需要完全是列表类型吗,或者任何东西都可以迭代吗?)。

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

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

类型化包提供了类型变量,可以在类型提示中使用这些变量来表达所需的行为,而不需要特定的类型。例如,它包含Iterable和Callable等变量,用于提示,以指定对具有这些行为的任何类型的需要。

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


对于更复杂的类型验证,我喜欢typeguard基于python类型提示注释进行验证的方法:

from typeguard import check_type
from 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]])
# vs
isinstance(foo, list) and all(isinstance(a, (int, float)) for a in foo) 

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

Ex:

>>> 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 NewType
UserId = 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

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

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

A: 使用isinstance、issubclass、type基于类型进行检查。

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

类型检查

在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,我们都会知道。我们不必在循环输入的任何地方进行检查。我们不必在迭代时存储输入。

此外,这种方法支持duck类型。我们不再检查特定的类型,而是检查特定的行为,并查找输入何时无法按预期运行(在本例中,循环通过nums并能够添加n)。

然而,使异常处理变得好的确切原因也可能是它们的失败。

浮点数不是整数,但它满足工作的行为要求。用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

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

代码现在看起来不错!如果您使用类型提示,则可以由编辑器执行静态类型分析!它们存储在函数/类中,使它们可以动态使用,例如类型保护和数据类。当使用help(…)时,它们会显示函数。无需根据描述或更糟糕的缺乏来检查输入类型是否正确。您可以根据结构“键入”提示,例如“它有这个属性吗?”而不需要用户进行子类化。

打字暗示的缺点是什么?

类型提示本身就是语法和特殊文本。这与类型检查不同。

换句话说,它实际上没有回答这个问题,因为它不提供类型检查。然而,无论如何,如果您在这里进行类型检查,那么您也应该进行类型提示。当然,如果您已经得出结论,类型检查实际上是不必要的,但您需要某种类型的外观,那么类型提示适合您。


在Python 3.10中,可以在isinstance中使用|:

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

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

在Python中,可以使用内置的isinstance()函数来检查对象是否为给定类型,或者是否从给定类型继承。

要检查对象o是否为str类型,可以使用以下代码:

if isinstance(o, str):
# o is of type str

您还可以使用type()函数检查对象类型。

if type(o) == str:
# o is of type str

还可以使用issubclass()函数检查对象是否是特定类的子类。

if issubclass(type(o),str):
# o is sub class of str