ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

我认为 Coombs 先生忽略了非 JSON 可序列化类等示例。如果通过函数(其代码无法影响其代码)放置大量数据,则可能希望在传递之前将该数据的某些部分转换为例如 。至少这就是我最终出现在这个页面上的方式......
要求这样做的最常见原因似乎是想要区分字符串和字符串的可迭代对象。这是一个棘手的问题,因为字符串 are 是字符串的可迭代对象——单个字符串甚至是其自身的序列(上次我检查过——可能不应该依赖它)。但是有人会使用类似字符串的东西吗? Yes。所以回答“我应该怎么做才能区分字符串和其他可迭代的字符串?”是正确的:“这取决于你想要做什么”。 :-D
Python 类型注释现在是一件事。看看mypy

M
Mateen Ulhaq

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

if isinstance(o, str):

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

if type(o) is str:

上述的另一种选择:

if issubclass(type(o), str):

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

在 Python 2 中检查字符串

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

if isinstance(o, basestring):

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

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

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

str.__subclasses__() 只返回 str 的直接子类,不做与 issubclass() 或 isinstance() 相同的事情。 (为此,您必须递归调用 .__subclasses__()。
这是一个很好的答案,但我认为它真的应该从一个警告开始,你通常不应该在 Python 中这样做。事实上,它似乎验证了这样的假设,即这是“在 Python 中要做的规范事情”,但事实并非如此。
实例和“确切地”有什么区别?如果 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。
s
smci

检查对象类型的最 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
L
L3viathan

如果 ostr 或属于从 str 继承的类型,则 isinstance(o, str) 将返回 True

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


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

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


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

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

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

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

这很酷……但我真的不明白这里添加了什么或者它有什么帮助。
python 的一大优点是它避免了代码中到处都是随机符号。不幸的是,随着时间的推移,这种财产正在慢慢消失。
Y
Yerramsetty Rohit

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

这是检查某物类型的糟糕方法。我可以用我想要的名称创建一个随机类,它会通过你的测试。它甚至比鸭式打字更糟糕。
D
Dmitry

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

例如:这是 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 那是两年前的事,但这是真的。我大大夸大了这个问题。 “动态空间”中的类型检查与“编译时空间”中的类型检查非常不同。在运行时,为了让计算机理解我们想要从它那里得到什么,它需要做比在良好的编译时间空间中更多的不可避免的工作。当我编写这个示例时,我主要使用 C 和 Java 进行编码,并且对动态空间几乎没有了解,所以如果没有能力通过静态分析来防止这种事情发生,这似乎很糟糕。
@clacke 基本上,在运行时严格执行类型太昂贵了,因为所有东西都必须是一个对象(为了从字符串映射到任何可能的类型),并且因为鸭子类型允许真正强大的原型技术克服了一些事情,所以没有鸭子打字太方便了通常很难用刚性接口做。此外,任何静态语言都面临着需要通过动态库、评估和字符串化或接口创建鸭子类型的问题,而这些东西本身并不会使它变得邪恶,只是非常强大。
D
DarkCygnus
isinstance(o, str)

Link to docs


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

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

P
Peter Mortensen

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

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

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

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


必须使用它的一个很好的例子是解析动态 json 对象。您不会提前知道字段是字符串还是字典。
S
Simply Beautiful Art

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

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

A:使用isinstance、issubclass、type根据类型进行检查。

然而,正如其他答案和评论很快指出的那样,“类型检查”的概念比 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 的明显缺点是它破坏了迭代器的全部目的。重点是不要存储数据,因为我们不需要。

人们可能还认为检查是否检查所有元素太多,那么也许我们可以检查输入本身是否为可迭代类型,但实际上没有任何可迭代的基类。任何实现 __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,我们就会知道。我们不必在循环输入的任何地方进行检查。而且我们不必在迭代时存储输入。

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

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

float 不是 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

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

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

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

它们存储在函数/类中,使它们可以动态使用,例如类型保护和数据类。

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

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

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

类型提示的缺点?

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

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


P
Peter Mortensen

给雨果:

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

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

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

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

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

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


U
Ultrablendz

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

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

X
Xiddoc

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

例子:

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

请参阅https://docs.python.org/3/library/typing.html


但是你没有在任何地方展示支票,是吗?如果你这样做了,你能解释一下你在哪里检查你给出的代码中的类型吗?