检查给定对象是否属于给定类型的最佳方法是什么?如何检查对象是否继承自给定类型?
假设我有一个对象o
。我如何检查它是否是一个str
?
要检查是否o
是 的实例str
或任何子类str
,请使用isinstance(这将是“规范”方式):
if isinstance(o, str):
检查类型o
是否准确str
(排除子类):
if type(o) is str:
以下方法也有效,并且在某些情况下可能有用:
if issubclass(type(o), str):
有关相关信息,请参阅Python 库参考中的内置函数。
还有一点需要注意:在这种情况下,如果您使用的是 Python 2,您可能实际上想要使用:
if isinstance(o, basestring):
因为这也会捕获 Unicode 字符串(unicode
不是 的子类str
;两者都是str
的unicode
子类basestring
)。请注意,basestring
在 Python 3 中不再存在字符串 ( ) 和二进制数据 ( )的严格分隔。str
bytes
或者,isinstance
接受一个类的元组。True
如果o
是以下任何一个子类的实例,这将返回(str, unicode)
:
if isinstance(o, (str, unicode)):
检查对象类型的最Pythonic 方法是……不检查它。
由于 Python 鼓励Duck Typing,因此您应该try...except
按照您想要的方式使用对象的方法。因此,如果您的函数正在寻找可写文件对象,请不要检查它是否是 的子类file
,只需尝试使用它的.write()
方法!
当然,有时这些漂亮的抽象会崩溃,而isinstance(obj, cls)
这正是您所需要的。但要谨慎使用。
isinstance(o, str)
True
如果o
是str
或是继承自 的类型,将返回str
。
type(o) is str
True
当且仅当o
是一个 str时才会返回。False
如果o
是继承自的类型,它将返回str
。
在提出并回答问题后,类型提示被添加到 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
包提供类型变量,可以在类型提示中使用来表达所需的行为,而无需特定类型。例如,它包括变量,例如Iterable
和Callable
提示,以指定具有这些行为的任何类型的需求。
虽然类型提示是检查类型的最 Pythonic 方式,但通常更 Pythonic 的是根本不检查类型并依赖鸭子类型。类型提示相对较新,当它们是最 Pythonic 的解决方案时,陪审团仍然没有定论。一个相对没有争议但非常笼统的比较:类型提示提供了一种可以强制执行的文档形式,允许代码生成更早且更易于理解的错误,可以捕获鸭子类型无法捕获的错误,并且可以静态检查(在不寻常的情况下)感觉,但它仍然在运行时之外)。另一方面,duck 类型长期以来一直是 Pythonic 方式,不会强加静态类型的认知开销,不那么冗长,并且会接受所有可行的类型,然后是一些。
在Python 3.10|
中,您可以使用isinstance
:
>>> isinstance('1223', int | str)
True
>>> isinstance('abcd', int | str)
True
这是一个示例,为什么鸭子打字是邪恶的,却不知道何时是危险的。
例如:这是 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 之外,它作为概念证明留下,没有进行重大编辑。我不记得最初是什么迫使我写这个。
您可以使用类型的 __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'
isinstance(o, str)
对于更复杂的类型验证,我喜欢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)
我认为使用像 Python 这样的动态语言很酷的一点是你真的不应该检查类似的东西。
我只会在您的对象上调用所需的方法并捕获一个AttributeError
. 稍后这将允许您使用其他(看似不相关的)对象调用您的方法来完成不同的任务,例如模拟一个对象进行测试。
在从 Web 上获取数据时,我经常使用urllib2.urlopen()
它来返回类似对象的文件。这可以反过来传递给几乎任何从文件读取的方法,因为它实现了与read()
真实文件相同的方法。
但我确定有使用的时间和地点isinstance()
,否则它可能不会在那里:)
接受的答案回答了问题,因为它提供了所提问题的答案。
问:检查给定对象是否属于给定类型的最佳方法是什么?如何检查对象是否继承自给定类型?
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
为了检查输入是否是整数的可迭代,我们遇到了一个主要问题。检查每个元素是否为整数的唯一方法是循环检查每个元素。但是如果我们循环遍历整个迭代器,那么预期的代码将一无所有。在这种情况下,我们有两种选择。
在我们循环时检查。
事先检查,但在我们检查时存储所有内容。
选项 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)
与之前的选项相比,这肯定更好。我们在运行代码时进行检查。如果有TypeError
任何地方,我们会知道的。我们不必在循环输入的任何地方进行检查。而且我们不必在迭代时存储输入。
此外,这种方法可以实现鸭子类型。我们不再检查 for specific types
,而是检查specific behaviors
并查找输入何时未能按预期运行(在这种情况下,循环nums
并能够添加n
)。
然而,使异常处理变得很好的确切原因也可能是它们的失败。
Afloat
不是 a 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(...)
.
无需根据描述检查您的输入类型是否正确或更糟糕的是缺少描述。
您可以根据结构“键入”提示,例如“它有这个属性吗?” 无需用户进行子类化。
类型提示的缺点?
换句话说,它实际上并没有回答问题,因为它不提供类型检查。但是,无论如何,如果您在这里进行类型检查,那么您也应该进行类型提示。当然,如果您得出的结论是类型检查实际上不是必需的,但您想要一些类似的类型,那么类型提示适合您。
给雨果:
您可能的意思是list
而不是array
,但这指向了类型检查的整个问题-您不想知道所讨论的对象是否是列表,您想知道它是某种序列还是单个对象。所以试着像序列一样使用它。
假设您要将对象添加到现有序列中,或者如果它是对象序列,则将它们全部添加
try:
my_sequence.extend(o)
except TypeError:
my_sequence.append(o)
一个技巧是,如果您正在使用字符串和/或字符串序列 - 这很棘手,因为字符串通常被认为是单个对象,但它也是一个字符序列。比这更糟糕的是,它实际上是一系列单长字符串。
我通常选择将我的 API 设计为只接受一个值或一个序列——它使事情变得更容易。[ ]
如果需要,当您传入单个值时,将它放在您的单个值周围并不难。
(虽然这可能会导致字符串错误,因为它们看起来确实像(是)序列。)
检查类型的一种简单方法是将其与您知道的类型进行比较。
>>> a = 1
>>> type(a) == type(1)
True
>>> b = 'abc'
>>> type(b) == type('')
True
我认为最好的方法是输入好你的变量。您可以通过使用“打字”库来做到这一点。
例子:
from typing import NewType
UserId = NewType ('UserId', int)
some_id = UserId (524313`)