Python变量的类型检查最佳方式是什么?(成语)

348

我需要知道Python中的一个变量是字符串还是字典。以下代码有什么问题吗?

if type(x) == type(str()):
    do_something_with_a_string(x)
elif type(x) == type(dict()):
    do_somethting_with_a_dict(x)
else:
    raise ValueError
更新: 我接受了avisser的答案(如果有人解释为什么isinstancetype(x) is更好,我会改变我的想法)。
但感谢nakedfanatic提醒我,使用字典(作为case语句)往往比使用if / elif / else系列更干净。
让我详细说明一下我的用例。 如果一个变量是字符串,我需要将它放到列表中。 如果它是一个字典,我需要一个唯一值列表。 这是我想出来的:
def value_list(x):
    cases = {str: lambda t: [t],
             dict: lambda t: list(set(t.values()))}
    try:
        return cases[type(x)](x)
    except KeyError:
        return None

如果偏爱使用isinstance,您将如何编写value_list()函数?

10
在我看来,isinstance() 更好,因为它可以针对某个特定类的类型测试某个变量的类型,而无需分配/创建任何内容。我的意思是:当您执行“type(str())”时,只是为了获取其类型而创建一个 str 对象的实例。然后,刚刚创建的对象被丢弃并稍后被垃圾回收。您不需要这样做,因为您预先知道要测试的类型,因此如果您执行“isinstance(variable, type)”,则更有效率。 - Richard Gomes
13
实际上,您混淆了两个不同的主题。如果目标是避免分配一个 str,那么程序员应该简单地使用 str 而不是 type(str())。假设程序员的意图是测试一个确切的类型。isinstance 的目的是允许子类型。这可能是所期望的,也可能不是。例如,collections.OrderedDictdict 的子类,因此,如果程序员也想允许这些类型,则将代码从 type(x) == dict 更改为 isinstance(x, dict) 是正确的。但不是为了避免分配,而是为了改变意义为“接受子类”。 - ToolmakerSteve
3
分配一个空的对象并用type()进行检查不仅是因为空对象实例无用,而且甚至因为你无法检查你的对象是否为file类型而不在文件系统上创建文件(type(file())失败,因为file()需要至少一个参数)。 - dappiu
10个回答

346
如果有人将一个Unicode字符串传递给您的函数,或者是派生自字典的类,或者是实现了类似于字典的接口的类,会发生什么?下面的代码覆盖了前两种情况。如果您正在使用Python 2.6,则可能要使用collections.Mapping而不是dict,根据ABC PEP
def value_list(x):
    if isinstance(x, dict):
        return list(set(x.values()))
    elif isinstance(x, basestring):
        return [x]
    else:
        return None

2
能够看到collections.Mapping如何进入这个讨论会很好。它有什么优点?我们可以看到一些示例代码,以了解它与其他内容的比较吗?ABC PEP链接相当繁重,理论内容很多,而当目标只是测试某些内容是否为dict或string时,需要消耗大量精力。实现ABCs需要额外的努力吗?(特别是对于简单用例),增加的努力是否值得? - Mike S
1
帮助页面的直接链接:https://docs.python.org/2.7/library/collections.html#collections-abstract-base-classes - Suraj
5
collections.Mapping ABC 提供了一种简单的方法来检查对象是否像一个字典一样运作。代码中的改变将是将 isinstance(x, dict) 替换为 isinstance(x, collections.Mapping)。这提供了额外的好处,可以匹配那些虽然不是从 dict 派生而来但提供了类似接口的对象。 - Suraj
检查类似字符串的一个好方法(来自《Python入门到专业》)是尝试<value> + ''并检查是否引发TypeError。如果未引发TypeError,则它类似于字符串。唯一的缺点是,如果字符串很大,我不知道性能成本如何。也许解释器足够聪明,成本基本为零?我不知道。 - Steve Jorgensen
@SteveJorgensen 除了聪明的技巧之外,使用那种方法是否比使用isinstance有优势? - Dymas
在Python 3中,basestring被str所代替。 - luckyguy73

63

type(dict()) 表示“创建一个新的字典,然后找出它的类型是什么”。只说 dict 更快。 但如果你只想检查类型,更习惯用的方式是 isinstance(x, dict)

请注意,isinstance 还包括子类(感谢 Dustin):

class D(dict):
    pass

d = D()
print("type(d) is dict", type(d) is dict)  # -> False
print("isinstance (d, dict)", isinstance(d, dict))  # -> True

4
“isinstance(x, dict)”比“type(x) is dict”更好吗?为什么?是的,“isinstance(x, dict)”比“type(x) is dict”更好。因为“isinstance(x, dict)”可以检查一个对象是否是字典类型或其子类,而“type(x) is dict”只能检查一个对象是否为字典类型,无法检查其子类。 - Daryl Spitzer
27
非常抱歉,您提供的链接无法访问。请提供正确的内容或链接,我将尽力帮助您进行翻译。 - Dustin
@Dustin 链接失效... - Lenka Čížková

46

Python中的内置类型有内置名称:

>>> s = "hallo"
>>> type(s) is str
True
>>> s = {}
>>> type(s) is dict
True

顺便提一下,注意is运算符。然而,类型检查(如果你想这么称呼它)通常通过在try-except语句中包装一个特定于类型的测试来完成,因为重要的不是变量的类型,而是你是否可以对其执行某些特定操作。


5
众所周知,首选的方法是使用isinstance内置函数。 - David Locke
2
isinstance 可以应用于任何类/类型,包括您自定义的类,而内置类型名称的数量有限。 - Albert Visser

23

使用isinstance比type更好,因为当您将对象实例与其超类进行比较时,它也会评估为True,这基本上意味着您永远不必为将其与字典或字符串子类一起使用而特殊处理旧代码。

例如:

 >>> class a_dict(dict):
 ...     pass
 ... 
 >>> type(a_dict()) == type(dict())
 False
 >>> isinstance(a_dict(), dict)
 True
 >>> 

当然,可能存在一些情况,你不希望得到这种行为,但是这些情况应该比你希望得到这种行为的情况要少得多。


8
我认为我会采用鸭子类型的方法 - “如果它走起来像鸭子,叫起来像鸭子,那就是只鸭子”。这样你就不必担心字符串是Unicode还是ASCII了。
以下是我的做法:
In [53]: s='somestring'

In [54]: u=u'someunicodestring'

In [55]: d={}

In [56]: for each in s,u,d:
    if hasattr(each, 'keys'):
        print list(set(each.values()))
    elif hasattr(each, 'lower'):
        print [each]
    else:
        print "error"
   ....:         
   ....:         
['somestring']
[u'someunicodestring']
[]

这里的专家们可以就ducktyping的这种用法发表评论,我一直在使用它,但最近才介绍了确切的概念,对此感到非常兴奋。因此,我想知道是否这样做有些过头了。


6
如果我们担心这种情况,那么很可能会产生假阳性结果。例如,我的“钢琴”课程也有“键”。 - nakedfanatic
1
根据数据集而定,如果我知道只有字典和字符串(Unicode或ASCII),那么它应该可以无缝运行。是的,在更广义的意义上,你说它可能会导致误报是正确的。 - JV.
在我看来,这个例子假设strs和unicodes的行为是相同的。但事实并非如此。如果你将print [each]改为print [s + each],你会看到它们的行为不同的例子... - GreenAsJade
在这个上下文中,“鸭子类型”的一个有用的演示:http://canonical.org/~kragen/isinstance/ - Carl
1
Answer++。鸭子类型是唯一的途径,否则解释性语言的强大将完全丧失。 - Marco Sulla
这种方法比isinstance更受欢迎,因为您不知道某个人将如何与您的代码交互(尤其是对于库而言)。例如,如果您需要模拟对象进行测试,则isinstance会导致问题。我希望这能得到更多的赞! - Aaron D

7

我认为最好实际操作一下

if isinstance(x, str):
    do_something_with_a_string(x)
elif isinstance(x, dict):
    do_somethting_with_a_dict(x)
else:
    raise ValueError

有两种不同的形式,根据您的代码,其中一种可能会被认为比另一种更好。其中一种是“先行后效”。

try:
  one, two = tupleOrValue
except TypeError:
  one = tupleOrValue
  two = None

另一种方法是来自Guido的函数重载形式,它让你的代码更加开放。 http://www.artima.com/weblogs/viewpost.jsp?thread=155514

3
您可能想要查看typecheck。 http://pypi.python.org/pypi/typecheck Python类型检查模块
该软件包为Python函数、方法和生成器提供了强大的运行时类型检查功能。无需自定义预处理器或更改语言,typecheck软件包允许程序员和质量保证工程师对其代码的输入和输出进行精确断言。

3

这应该可以正常工作 - 所以,你的代码没有任何问题。然而,也可以使用字典来完成:

{type(str()): do_something_with_a_string,
 type(dict()): do_something_with_a_dict}.get(type(x), errorhandler)()

您觉得更加简洁和符合Python风格,是吗?


编辑...听从Avisser的建议,代码也可以这样工作,并且看起来更美观:

{str: do_something_with_a_string,
 dict: do_something_with_a_dict}.get(type(x), errorhandler)()

2
不,这并不更符合 Python 的风格,因为你应该使用 isinstance 内置函数。 - David Locke
嘿,我知道“P”这个词会引起争议。然而,我仍然坚持我的答案,因为它是避免if-elif-else结构的另一种选择。 - nakedfanatic
我同意。请查看我编辑后的问题。 - Daryl Spitzer

1

我一直在使用不同的方法:

from inspect import getmro
if (type([]) in getmro(obj.__class__)):
    # This is a list, or a subclass of...
elif (type{}) in getmro(obj.__class__)):
    # This one is a dict, or ...

我不记得为什么我用这个而不是isinstance,虽然...


-2

*叹气*

不,Python中的类型检查参数并不是必要的。它从来都不是必要的。

如果你的代码接受字符串或字典对象,那么你的设计就有问题了。

这是因为如果你在自己的程序中还不知道一个对象的类型,那么你已经做错了什么。

类型检查会影响代码重用和性能。拥有一个函数,根据传递的对象类型执行不同的操作,容易出现错误,并且具有更难理解和维护的行为。

你有以下更明智的选择:

1)创建一个名为unique_values的函数,将字典转换为唯一值列表:

def unique_values(some_dict):
    return list(set(some_dict.values()))

让你的函数假设传递的参数始终是一个列表。这样,如果你需要将一个字符串传递给函数,你只需要这样做:

myfunction([some_string])

如果你需要传递一个字典,你可以这样做:
myfunction(unique_values(some_dict))

这是您最佳的选择,它干净、易于理解和维护。任何人阅读代码都能立即了解发生了什么,并且您不需要进行类型检查。

2)编写两个函数,一个接受字符串列表,另一个接受字典。您可以使其中一个在内部调用另一个,以最方便的方式(myfunction_dict 可以创建一个字符串列表并调用 myfunction_list)。

无论如何,不要进行类型检查。这完全没有必要,只会带来弊端。通过重构您的代码,以不需要进行类型检查的方式,您只会获得短期和长期的好处。


26
在编写像RPC公开的函数等内容时,类型检查很有用——它可以帮助您检查是否确实像您期望的那样获得了int和string,并作为彻底检查外部不可信输入的一部分。 - Brandon Rhodes
11
有时候我们无法完全控制给某个函数传递的参数类型,特别是当它与其他脚本进行交互时。 - Adam S
45
不,Python中对参数进行类型检查并不是必需的。从根本上说这是错误的,甚至有点痛苦。话说完了。 - Jürgen A. Erhard
11
我应该足够明智不去打扰,但请解答一个谜语:既然这从来不是必要的(你的确切话语),为什么Python还有“isinstance”?只是为了好玩吗?对我来说,今天到此结束。 - Jürgen A. Erhard
55
当您构建树时,可能需要同时拥有字符串或字典这两种类型的某个东西,这是经典案例。在任何时候,左子树可能是终端(即字符串),也可能是进一步的子树(即字典)。递归遍历这样的树的任何函数都需要一个参数,该参数可以是字符串或字典,并且需要测试它是哪种类型以便进行智能处理。我想看看您如何处理此情况而不进行类型检查,@nosklo - interstar
显示剩余18条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接