【问题标题】:What's the canonical way to check for type in Python?在 Python 中检查类型的规范方法是什么?
【发布时间】:2010-09-14 05:36:06
【问题描述】:

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

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

【问题讨论】:

  • 好吧,Python 中的规范方法是根本不检查类型(除非您正在调试)。通常您只是尝试将其用作字符串(例如与其他字符串连接,打印到控制台等);如果您认为它可能会失败,请使用 try/except 或 hasattr。也就是说,公认的答案是在 Python 世界中做你通常“不应该做”的事情的规范方法。欲了解更多信息,谷歌“Python 鸭打字”或阅读这些:voidspace.org.uk/python/articles/duck_typing.shtmlstackoverflow.com/questions/610883/…
  • 我认为 Coombs 先生忽略了非 JSON 可序列化类等示例。如果通过函数(其代码无法影响其代码)放置大量数据,则可能希望在传递之前将该数据的某些部分转换为例如 。至少这就是最终出现在这个页面上的方式......
  • 要求这个的最常见原因似乎是想要区分字符串和字符串的可迭代对象。这是一个棘手的问题,因为字符串 are 是字符串的可迭代对象——单个字符串甚至是其自身的序列(上次我检查过——可能不应该依赖它)。但是有人会使用类似字符串的东西吗? Yes。那么“我应该怎么做才能区分字符串和字符串的其他可迭代对象”的答案呢?是正确的:“这取决于你想要做什么”。 :-D
  • Python 类型注解现在是一回事。看看mypy

标签: python types


【解决方案1】:

要检查ostr 的实例还是str 的任何子类,请使用isinstance(这将是“规范”方式):

if isinstance(o, str):

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

if type(o) is str:

以下方法也有效,并且在某些情况下可能有用:

if issubclass(type(o), str):

有关相关信息,请参阅 Python 库参考中的 Built-in Functions

还有一点需要注意:在这种情况下,如果您使用的是 Python 2,您可能实际上想要使用:

if isinstance(o, basestring):

因为这也会捕获 Unicode 字符串(unicode 不是 str 的子类;strunicode 都是 basestring 的子类)。请注意,basestring 在 Python 3 中不再存在,其中有字符串 (str) 和二进制数据 (bytes) 的 a strict separation

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

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

【讨论】:

  • str.__subclasses__() 只返回 str 的直接子类,不做与 issubclass() 或 isinstance() 相同的事情。 (为此,您必须递归调用 .__subclasses__()。
  • 这是一个很好的答案,但我认为它真的应该从一个警告开始,你通常不应该在 Python 中这样做。事实上,它似乎验证了这样的假设,即这是“在 Python 中要做的规范事情”,但事实并非如此。
  • instance 和“exactly”有什么区别?如果type(a) is Object 那么isinstance(a, Object) 不也是如此。然而,如果type(a) is SubClassOfObject,那么type(a) is Object == False,但isinstance(a, Object) == True。对吗?
  • @mavavilj - a is b 表示 a 和 b 是完全相同的东西,即引用内存中的同一实体。所以ab 必须是完全相同的类,而不是子类,就像isinstance() 一样。参见例如stackoverflow.com/a/133024/1072212
  • @JonCoombs 根据PEP 622 的基本原理,isinstance() 是仅次于len() 的第二大内置函数。我认为我们必须接受 isinstance 是事实上的规范 Python。
【解决方案2】:

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

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

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

【讨论】:

  • 恕我直言,最 Pythonic 的方式是处理给出的任何参数。在我的代码中,我经常不知道我收到的是一个对象还是一个对象数组,我在内部使用类型检查将单个对象转换为单元素列表。
  • 而不是仅仅尝试使用它的 write 方法,有时你想这样做而不引起异常。在这种情况下,您可以这样做... if hasattr(ob, "write") and callable(ob.write): 或保存一些 dict 访问权限... func = getattr(ob, "write", None) if callable(func): ...
  • 鸭子打字是关于使用一个库。类型检查是关于编写一个库。不是同一个问题域。
  • @RickyA,我不同意。鸭子类型是关于使用具有众所周知语义的接口与对象交互。这可以应用于库代码或使用此类库的代码。
  • @nyuszika7h,在 Python3 中 hasattr 仅抑制 AttributeError - 请参阅:docs.python.org/3.4/library/functions.html#hasattr
【解决方案3】:

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

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

【讨论】:

  • 当然,如果对象不是 'str' 的实例,而是类似字符串的实例,这将失败。像 unicode、mmap、UserString 或任何其他用户定义的类型。 Python 中的常用方法是不进行类型检查。
  • 这很有帮助。因为isinstancetype(var) == type('')的区别还不清楚。
【解决方案4】:

问题被问及回答后,type hints were added to Python。 Python 中的类型提示允许检查类型,但方式与静态类型语言非常不同。 Python 中的类型提示将预期的参数类型与函数关联为与函数关联的运行时可访问数据,这允许检查类型。类型提示语法示例:

def foo(i: int):
    return i

foo(5)
foo('oops')

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

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

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

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

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

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

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

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

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

【讨论】:

  • -1: mypy 专门称自己为“静态类型检查器”,所以我不确定您从哪里得到“必须在运行时进行类型检查”。
  • @Kevin 回想起来,这是一个不必要的题外话,但为了更深入地了解它,Python 的类型提示被转换为运行时数据,mypy 是一个 Python 模块,它使用 importlib 来访问它数据。这是否是“静态类型检查”是一个哲学问题,但它与大多数人的预期不同,因为涉及到正常的语言解释器和导入机制。
  • 这也不是真的。它uses typed_ast,它本身is just a clone of ast 具有额外的功能。 ast 不导入模块;它将它们解析为抽象语法树。
【解决方案5】:

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

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

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

【讨论】:

  • 这很酷……但我真的不明白这里添加了什么或它有什么帮助。
【解决方案6】:

这是一个例子,为什么鸭子打字是邪恶的,却不知道什么时候是危险的。

例如:这是 Python 代码(可能省略了适当的缩进),注意这种情况可以通过处理 isinstance 和 issubclassof 函数来避免,以确保当你真的需要鸭子时,你不会得到炸弹.

class Bomb:
    def talk(self):
        self.explode()

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

class Duck:
    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 take_duck(self, duck):
        self.kids_duck = duck
        print("The kid accepts the duck, and happily skips along.")

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

my_kid = Kid()
my_kid.take_duck(Bomb())
my_kid.do_your_thing()

注意:这个例子陈旧、幼稚,危险被大大夸大了。除了更新到 Python 3 之外,它只是作为概念验证留下来,没有进行重大修改。我不记得最初是什么迫使我写这个的。

【讨论】:

  • 炸弹不会说话。不要添加无意义的方法,这不会发生。
  • @Dmitry,这是对 Duck Typing 的常见批评:en.wikipedia.org/wiki/Duck_typing#Criticism ...您基本上是在说,语言未强制执行语义的任何接口都是邪恶的。我相信这更像是Java的方法。 Python 的鸭子类型的全部意义在于,它仅在存在关于特定接口含义的普遍支持的约定时才有效。例如,您可以通过覆盖 __file__ 属性(通常用于识别类似文件的对象)来表达其他含义,从而使大量 Python 代码变得笨拙。
  • 这一切都归结为一句老笑话“医生,我这样做会很痛。” ......“那就不要那样做。”。对于习惯于“如果它编译,它就会运行”的人来说,这并不令人满意,但这就是为什么对测试的痴迷源于动态语言世界。
  • @clacke 那是 2 年前的事,但这是真的。我大大夸大了这个问题。 “动态空间”中的类型检查与“编译时空间”中的类型检查非常不同。在运行时,为了让计算机理解我们想要从它那里得到什么,它需要做比在良好的编译时间空间中更多的不可避免的工作。当我编写这个示例时,我主要使用 C 和 Java 进行编码,并且对动态空间几乎没有了解,因此如果没有能力通过静态分析来防止这种情况发生,这似乎很糟糕。
  • @clacke 基本上,在运行时严格执行类型太昂贵了,因为所有东西都必须是一个对象(为了从字符串映射到任何可能的类型),并且因为鸭子类型允许而没有鸭子打字太方便了真正强大的原型技术,克服了通常很难用刚性接口做的事情。此外,任何静态语言都需要通过动态库、求值和字符串化或接口来创建鸭子类型,而这些东西本身并不会使它变得邪恶,只是非常强大。
【解决方案7】:

您可以使用类型的 __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'

【讨论】:

  • 谢谢,这是我在向用户显示反馈时想要的密码。我花了太长时间才找到这个......
【解决方案8】:
isinstance(o, str)

Link to docs

【讨论】:

  • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。
【解决方案9】:

对于更复杂的类型验证,我喜欢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) 

【讨论】:

    【解决方案10】:

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

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

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

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

    【讨论】:

    • 当你必须使用它的一个很好的例子是,如果你正在解析一个动态的 json 对象。您无法提前知道字段是字符串还是字典。
    【解决方案11】:

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

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

    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. 事先检查,但在检查时存储所有内容。

    选项 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)。

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

    1. float 不是int,但它满足行为要求。

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

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

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

    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
    

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

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

    输入提示

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

    from typing import Iterable
    
    def sum(nums: Iterable[int]) -> int:
        result = 0
        for n in nums:
            result += n
        return result
    

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

    • 现在的代码实际上看起来不错!

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

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

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

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

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

    类型提示的缺点?

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

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

    【讨论】:

      【解决方案12】:

      致雨果:

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

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

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

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

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

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

      【讨论】:

        【解决方案13】:

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

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

        【讨论】:

          【解决方案14】:

          我认为最好的方法是输入好变量。您可以使用“打字”库来做到这一点。

          例子:

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

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

          【讨论】:

            猜你喜欢
            • 2017-01-22
            • 1970-01-01
            • 1970-01-01
            • 2014-10-03
            • 1970-01-01
            • 2014-03-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多