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

Her*_*rge 1171 python types

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

假设我有一个对象o.我如何检查它是否是一个str

Fre*_*son 1405

要检查是否o是实例str或任何子类str,请使用isinstance(这将是"规范"方式):

if isinstance(o, str):
Run Code Online (Sandbox Code Playgroud)

要检查类型o是否完全str(排除子类):

if type(o) is str:
Run Code Online (Sandbox Code Playgroud)

以下也适用,在某些情况下可能有用:

if issubclass(type(o), str):
Run Code Online (Sandbox Code Playgroud)

有关相关信息,请参阅Python Library Reference中的内置函数.

还有一点需要注意:在这种情况下,如果你使用的是python 2,你可能真的想要使用:

if isinstance(o, basestring):
Run Code Online (Sandbox Code Playgroud)

因为这也将赶上Unicode字符串(unicode不是的子类str,这两个strunicode是的子类basestring).请注意,basestringpython 3中不再存在,其中严格分离 strings(str)和二进制数据(bytes).

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

if isinstance(o, (str, unicode)):
Run Code Online (Sandbox Code Playgroud)

  • str .__ subslasses __()只返回str的直接子类,并且与issubclass()或isinstance()不做同样的事情.(要做到这一点,你必须递归调用.__子类__(). (29认同)
  • 这是一个很好的答案,但我认为它应该首先警告你通常不应该在Python中这样做.事实上,它似乎验证了这个假设,即这是"在Python中做的规范事情",但事实并非如此. (13认同)
  • 这些是python2的答案.例如,python3中没有基本字符串. (3认同)
  • 实例与"确切"之间有什么区别?如果`type(a)是Object`那么`isinstance(a,Object)`也不是.但是,如果`type(a)是SubClassOfObject`,那么`type(a)是Object == False`,但是`isinstance(a,Object)== True`.对? (3认同)
  • @JonCoombs根据[PEP 622](https://www.python.org/dev/peps/pep-0622/#rationale-and-goals)的基本原理,`isinstance()`是第二个被调用最多的内置函数,在 `len()` 之后。我认为我们必须接受“isinstance”事实上是规范的 Python。 (3认同)
  • @mavavilj - `a is b` 意味着 a 和 b 是完全相同的东西,即对内存中同一个实体的引用。所以 `a` 和 `b` 必须是完全相同的类,而不是子类,就像 `isinstance()` 一样。参见例如 /sf/answers/9311711/ (2认同)

Dan*_*ski 184

检查对象类型的 Pythonic方法是......不检查它.

由于Python鼓励Duck Typing,你应该只想try...except按照你想要的方式使用对象的方法.因此,如果您的函数正在寻找可写文件对象,请不要检查它是否是它的子类file,只是尝试使用它的.write()方法!

当然,有时候这些漂亮的抽象会破坏,isinstance(obj, cls)这就是你所需要的.但要谨慎使用.

  • 鸭子打字是关于_using_一个图书馆.类型检查是关于_writing_一个库.问题领域不一样. (129认同)
  • 恕我直言,最恐怖的方式是应对任何给出的论点.在我的代码中,我经常无法知道是否收到了一个对象或一个对象数组,并且我在内部使用类型检查将单个对象转换为单元素列表. (70认同)
  • @RickyA,我不同意.Duck打字是关于使用具有众所周知的语义的接口与对象进行交互.这可以应用于库代码或使用此类库的代码. (15认同)
  • 而不是只是尝试使用其write方法,有时你想要这样做而不会导致异常.在这种情况下,您可以执行...`if hasattr(ob,"write")和callable(ob.write):`或者保存一些dict访问...`func = getattr(ob,"write",None)` `if callable(func):...` (13认同)
  • @ nyuszika7h,在Python3`hasattr`中只有一个属性错误 - 请参阅:https://docs.python.org/3.4/library/functions.html#hasattr (6认同)
  • "检查物体类型的最Pythonic方法是......不检查它." 当我试图调试我的代码时,这根本没有帮助... (4认同)
  • @Addem:如果它们可能是可添加的情况很常见,那就试试吧!`try:a + b除TypeError:something_appropriate`.如果你正在对数字进行类型检查,你很可能会错过一些数字,因为有很多方法可以存储不是基数类的子类. (3认同)
  • 当结果对于您的用例来说“错误”但不会引发异常时, try ... except 模式不会有任何好处。例如,给定一个混合类型列表,您可能只想在将结果传递给 csv 编写器之前对该列表中的字典调用 json.dumps,因为最终会在输出中引用引用。 (3认同)
  • 鸭子输入不是一种抽象形式(指​​的是答案)。因为它避免了实际的输入(键入是一种抽象),并且处理了具体的实现-例如“是否存在此方法”或更合适?“如果我运行`powerplant.blow(),我的核电计划会爆炸还是打开风扇? “”(谁知道:)) (2认同)
  • 如果我传递一个具有相同方法但返回不同内容的对象怎么办?它将通过“try”,但会出现意外的行为。 (2认同)
  • 除了提到“try ... except”之外,您还应该说“ except”不应该是裸露的“ except”,它**应该始终指定如果对象的类型可能发生的特定命名异常是错误的**,例如“除了 TypeError、KeyError、UserDefinedYachtError:”。它不应该隐式捕获[其他异常](https://docs.python.org/3/library/exceptions.html#concrete-exceptions):MemoryError、OSError等,尤其是危险的异常。**裸体除非被认为是不好的** (2认同)

Her*_*rge 49

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

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

  • 当然,如果对象不是'str'的实例,而是类似于字符串的实例,则会失败.像unicode,mmap,UserString或任何其他用户定义的类型.Python中常用的方法不是做类型检查. (6认同)
  • 你不必道歉,可以回答你自己的问题.这是答案,而不是业力. (6认同)
  • 这非常有帮助.因为`isinstance`和`type(var)== type('')`之间的区别并不清楚. (2认同)

Pra*_*tic 24

在询问问题并回答之后,将类型提示添加到Python中.Python中的类型提示允许检查类型,但与静态类型语言的方式完全不同.在Python类型提示的预期类型的参数与功能与功能相关联的运行获得的数据关联起来,这使得为被检查的类型.类型提示语法示例:

def foo(i: int):
    return i

foo(5)
foo('oops')
Run Code Online (Sandbox Code Playgroud)

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

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

mypy script.py
script.py:12: error: Argument 1 to "foo" has incompatible type "str"; expected "int"
Run Code Online (Sandbox Code Playgroud)

(您可能需要mypy从您的软件包管理器安装.我不认为它与CPython一起提供,但似乎有一定程度的"官方".)

以这种方式检查类型与静态类型编译语言中的类型检查不同.因为类型在Python中是动态的,所以类型检查必须在运行时完成,这会产生成本 - 即使是在正确的程序上 - 如果我们坚持认为它是偶然发生的.显式类型检查也可能比需要更严格,并导致不必要的错误(例如,参数真的需要是完全list类型还是任何可迭代的东西?).

显式类型检查的优点是它可以更早地捕获错误并提供比duck typing更清晰的错误消息.鸭子类型的确切要求只能用外部文档表示(希望它是彻底和准确的),不兼容类型的错误可能远离它们的起源.

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

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

虽然类型提示是检查类型的最Pythonic方式,但通常更不用Pythonic来检查类型并依赖于duck typing.类型提示相对较新,当他们是最恐怖的解决方案时,陪审团仍在继续.一个相对无争议但非常一般的比较:类型提示提供了一种可以强制执行的文档形式,允许代码生成更早且更容易理解的错误,可以捕获鸭子不能输入的错误,并且可以静态检查(在不寻常的情况下)感觉但它仍然在运行时之外).另一方面,鸭子打字长期以来一直是Pythonic方式,不会强加静态打字的认知开销,不那么冗长,并且会接受所有可行的类型然后一些.

  • -1:[mypy](http://mypy-lang.org/) 特别称自己为“静态类型检查器”,所以我不确定你从哪里得到“必须在运行时进行类型检查”。 (4认同)
  • 也不是。它[使用](https://github.com/python/mypy/blob/master/mypy/fastparse.py)typed_ast,它本身[只是ast的克隆](https://github.com/python/ typed_ast)具有其他功能。[ast](https://docs.python.org/3/library/ast.html)不导入模块;它将它们解析为抽象语法树。 (3认同)

Dmi*_*try 19

这里有一个例子,为什么鸭子打字是邪恶的,而不知道它何时是危险的.例如:这是Python代码(可能省略了正确的缩进),请注意,通过处理isinstance和issubclassof函数可以避免这种情况,以确保当你真的需要一个鸭子时,你不会得到炸弹.

class Bomb:
    def __init__(self):
        ""

    def talk(self):
        self.explode()

    def explode(self):
        print "BOOM!, The bomb explodes."

class Duck:
    def __init__(self):
        ""
    def talk(self):
        print "I am a duck, I will not blow up if you ask me to talk."    

class Kid:
    kids_duck = None

    def __init__(self):
        print "Kid comes around a corner and asks you for money so he could buy a duck."

    def takeDuck(self, duck):
        self.kids_duck = duck
        print "The kid accepts the duck, and happily skips along"

    def doYourThing(self):
        print "The kid tries to get the duck to talk"
        self.kids_duck.talk()

myKid = Kid()
myBomb = Bomb()
myKid.takeDuck(myBomb)
myKid.doYourThing()
Run Code Online (Sandbox Code Playgroud)

  • 即使进行类型检查,您也可以创建一个`类EvilDuck(Duck)`并覆盖talk().或者更有可能的是,"中国癌症(鸭子)",具有令人讨厌的副作用,直到几年后才出现.你最好只是监督你的孩子(并彻底测试她的玩具:) (34认同)
  • 炸弹不说话.不要添加荒谬的方法,这不会发生. (30认同)
  • @Dmitry,这是Duck Typing的常见批评:https://en.wikipedia.org/wiki/Duck_typing#Criticism ...你基本上是说语言没有强制执行语义的任何接口都是邪恶.我相信这更像是Java的方法.Python的鸭子打字的全部意义在于它只适用于关于特定接口意味着什么的普遍支持的约定.例如,您可以通过覆盖`__file__`属性(通常用于标识类似文件的对象)来表示其他内容,从而使许多Python代码变得麻烦. (6认同)
  • 这一切都归结为一个古老的笑话“医生,当我这样做时会很痛。” ……“那就别那样了。” 对于习惯于“如果编译,它运行”的人来说并不满意,但这就是为什么对测试的痴迷源于动态语言世界的原因。 (2认同)

小智 14

您可以使用类型的 __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'
Run Code Online (Sandbox Code Playgroud)

  • 这是一种检查某物类型的糟糕方法。我可以使用我想要的名称创建一个随机类,它将通过您的测试。这比鸭子打字还要糟糕。 (4认同)

Ale*_*kov 13

isinstance(o, str)
Run Code Online (Sandbox Code Playgroud)

链接到文档

  • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 (3认同)

小智 7

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

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

在从Web上获取数据时,我已经使用了很多东西,urllib2.urlopen()它返回了 object 这样的文件.这可以反过来传递给几乎任何从文件读取的方法,因为它实现了与read()真实文件相同的方法.

但我确定有时间和地点使用isinstance(),否则它可能不会在那里:)

  • 必须使用它的一个很好的例子是,如果您正在解析动态 json 对象。您事先不知道字段是字符串还是字典。 (2认同)

Gra*_*rus 7

对于更复杂的类型验证,我喜欢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)
Run Code Online (Sandbox Code Playgroud)

您可以以非常干净和可读的方式执行非常复杂的验证。

check_type('foo', [1, 3.14], List[Union[int, float]])
# vs
isinstance(foo, list) and all(isinstance(a, (int, float)) for a in foo) 
Run Code Online (Sandbox Code Playgroud)


Sim*_*Art 7

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

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

A:用于isinstance, issubclass, type根据类型进行检查。

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

类型检查

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

def sum(nums):
    """Expect an iterable of integers and return the sum."""
    result = 0
    for n in nums:
        result += n
    return result
Run Code Online (Sandbox Code Playgroud)

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

  1. 当我们循环时检查。

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

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

选项 2 有一个明显的缺点,它破坏了迭代器的全部用途。重点是不要存储数据,因为我们不需要这样做。

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

异常处理和鸭子类型

另一种方法是完全放弃类型检查,而专注于异常处理和鸭子类型。也就是说,将代码包装在 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)
Run Code Online (Sandbox Code Playgroud)

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

此外,这种方法可以实现鸭子类型。我们不再检查specific types,而是转而检查specific behaviors并查找输入何时无法按预期运行(在本例中,循环遍历nums并能够添加n)。

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

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

  2. 用 try-except 块包装整个代码也是不好的做法。

乍一看,这些可能看起来不是什么问题,但以下一些原因可能会改变您的想法。

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

  2. 由于异常可能来自多种来源,因此在整个代码块上使用 try- except 可能最终会捕获您不希望捕获的异常。我们只想检查是否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
Run Code Online (Sandbox Code Playgroud)

你大概可以看出这是怎么回事。我们越是尝试“正确”检查,我们的代码看起来就越糟糕。与原始代码相比,这根本不可读。

我们可能会说这也许有点极端。但另一方面,这只是一个非常简单的例子。实际上,您的代码可能比这复杂得多。

类型提示

我们已经看到了当我们尝试修改我们的小示例以“启用类型检查”时会发生什么。类型提示不是专注于尝试强制使用特定类型,而是提供了一种使用户清楚类型的方法。

from typing import Iterable

def sum(nums: Iterable[int]) -> int:
    result = 0
    for n in nums:
        result += n
    return result
Run Code Online (Sandbox Code Playgroud)

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

  • 现在代码看起来确实不错!

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

  • 它们存储在函数/类中,使它们可以动态使用,例如typeguarddataclasses

  • 当使用 时,它们会出现在函数中help(...)

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

  • 您可以根据结构“输入”提示,例如“它有这个属性吗?” 无需用户进行子类化。

键入提示的缺点是什么?

  • 类型提示只不过是语法和特殊文本本身。它与类型检查不同

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


小智 5

前往雨果:

您可能list不是意思而是array,但这指向类型检查的整个问题-您不想知道所讨论的对象是否是列表,您想知道它是某种序列还是单个对象。因此,请尝试像序列一样使用它。

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

try:
   my_sequence.extend(o)
except TypeError:
  my_sequence.append(o)
Run Code Online (Sandbox Code Playgroud)

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

我通常选择设计API,使其只接受一个值或一个序列-这样会使事情变得更容易。[ ]如果需要,在传递单个值时并不难。

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


Sup*_*ova 5

Python 3.10 中,您可以使用|in isinstance

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

>>> isinstance('abcd', int | str) 
True
Run Code Online (Sandbox Code Playgroud)

  • 当然,该示例实际上应该使用整数来演示功能吗? (4认同)