检查属性是否存在的最佳方法是什么?

88

哪种方式更好来检查属性是否存在?

Jarret Hardie提供了以下答案:

if hasattr(a, 'property'):
    a.property

我发现这样也可以做:

if 'property' in a.__dict__:
    a.property

有一种方法通常比其他方法更常用吗?


你的第二个选项是错误的,无论如何,你提供的链接中的第二个答案回答了你的问题。 - Rik Poggi
4个回答

176

没有“最佳”方法,因为您永远不只是检查属性是否存在;它始终是某个更大程序的一部分。有几种正确的方法和一种明显错误的方法。

错误的方法

if 'property' in a.__dict__:
    a.property

以下是一个演示,显示了这种技术失败的情况:

class A(object):
    @property
    def prop(self):
        return 3

a = A()
print "'prop' in a.__dict__ =", 'prop' in a.__dict__
print "hasattr(a, 'prop') =", hasattr(a, 'prop')
print "a.prop =", a.prop

输出:

a.__dict__ 中的 'prop' = False
hasattr(a, 'prop') = True
a.prop = 3

大部分情况下,您不需要操作 __dict__。它是一个用于执行特殊操作的特殊属性,而检查属性是否存在则相对平凡。

EAFP 方法

Python 中常见的习语是“宁愿请求原谅,不要征得许可”,简称 EAFP。您会看到很多 Python 代码都使用这种习语,不仅用于检查属性是否存在。

# Cached attribute
try:
    big_object = self.big_object
    # or getattr(self, 'big_object')
except AttributeError:
    # Creating the Big Object takes five days
    # and three hundred pounds of over-ripe melons.
    big_object = CreateBigObject()
    self.big_object = big_object
big_object.do_something()

请注意,这与打开可能不存在的文件的习语完全相同。
try:
    f = open('some_file', 'r')
except IOError as ex:
    if ex.errno != errno.ENOENT:
        raise
    # it doesn't exist
else:
    # it does and it's open

另外,针对将字符串转换为整数的问题。

try:
    i = int(s)
except ValueError:
    print "Not an integer! Please try again."
    sys.exit(1)

即使导入可选模块...

try:
    import readline
except ImportError:
    pass

LBYL的方式

当然,可以使用hasattr方法。这种技术被称为“先看再跳”,或简称为LBYL。

# Cached attribute
if not hasattr(self, 'big_object'):
    big_object = CreateBigObject()
    self.big_object = CreateBigObject()
big_object.do_something()

(Python3.2之前版本中,hasattr 奇怪地处理异常 - 它会捕获不应该捕获的异常。但这可能是无关紧要的,因为此类异常不太可能发生。使用 try/except比使用 hasattr 技术慢,但您不需要频繁调用它,而且差异并不大。最后, hasattr 不是原子的,因此如果另一个线程删除属性,它可能会抛出 AttributeError ,但这是一个牵强附会的情况,您无论如何都需要非常小心地处理线程。我认为这三个差异都不值得担忧。)
使用 hasattr try / except 简单得多,只要您需要知道属性是否存在。我的主要问题是LBYL技术看起来“奇怪”,因为作为Python程序员,我更习惯于阅读EAFP技术。如果你重写上面的示例以使用LBYL风格,你将得到笨拙的、完全不正确或过于难以编写的代码。)
# Seems rather fragile...
if re.match('^(:?0|-?[1-9][0-9]*)$', s):
    i = int(s)
else:
    print "Not an integer! Please try again."
    sys.exit(1)

而且 LBYL 方法有时是完全不正确的:

if os.path.isfile('some_file'):
    # At this point, some other program could
    # delete some_file...
    f = open('some_file', 'r')

如果你想为导入可选模块编写一个LBYL函数,那就尽管去做吧...听起来这个函数会非常复杂。

使用getattr方法

如果你只需要一个默认值,getattrtry/except的简化版本。

x = getattr(self, 'x', default_value)

如果默认值构造成本较高,那么你最终会得到类似以下的结果:
x = getattr(self, 'attr', None)
if x is None:
    x = CreateDefaultValue()
    self.attr = x

如果None是一个可能的值,

sentinel = object()

x = getattr(self, 'attr', sentinel)
if x is sentinel:
    x = CreateDefaultValue()
    self.attr = x

结论

getattrhasattr内部实际上只使用了 try/except 技术(但是使用 C 语言编写)。因此,在关键点它们的行为方式都相同,选择正确的函数取决于情况和风格。

try/except 的 EAFP 代码可能会让某些程序员感到不舒服,而 hasattr/getattr 的 LBYL 代码可能会惹恼其他程序员。它们两者都是正确的,通常没有一个真正令人信服的理由去选择其中之一。(还有一些程序员对于属性未定义视为正常感到反感,有些程序员甚至认为在 Python 中出现未定义属性是可怕的。)


4
可以说,在你几乎总是期望 x 有属性 x.attr 的情况下,try... except 更加适用。(这样异常情况才是真正的异常。) - Li-aung Yip
3
我个人几乎总是使用try...excepthasattr方法让我感觉不爽。 - Dietrich Epp
1
@KarlKnechtel:啊,当然。抱歉。我认为在这个网站上,我误解评论的十次中有九次都是由于那些棘手的代词及其先行词;它们的解释既不明显也不明确,两个人会长时间交换言辞而实际上并没有沟通。 - Dietrich Epp
如果在try语句中调用其他函数,那么有可能会捕获到错误的AttributeError异常。 - mlt
tryhasattr在时间上有很大的不同,因此取决于最常见的情况:属性是否存在。请参见此处的分析:https://dev59.com/uXNA5IYBdhLWcg3wk--A - 0 _
显示剩余10条评论

12

hasattr()是一种方法*

a.__dict__很丑,而且在许多情况下都不起作用。实际上,hasattr()尝试获取属性并在内部捕获AttributeError,因此即使您定义了自定义的__getattr__()方法,它也可以正常工作。

为避免两次请求属性,可以使用getattr()的第三个参数:

not_exist = object()

# ...
attr = getattr(obj, 'attr', not_exist)
if attr is not_exist:
   do_something_else()
else:
   do_something(attr)

如果在您的情况下使用默认值比 not_exist 哨兵更合适,那么您可以只使用默认值。

我不喜欢 try: do_something(x.attr) \n except AttributeError: .. 这种写法,因为它可能会隐藏在 do_something() 函数内部的 AttributeError 异常。

*在 Python 3.1 之前,hasattr() 抑制了所有异常(不仅仅是 AttributeError),如果不希望这样的情况发生,则应该使用 getattr()


9

hasattr()是Python中常用的方法,学会并熟练使用吧。

另一种可能的方式是检查变量名是否在locals()globals()中:

if varName in locals() or in globals():
    do_something()
else:
    do_something_else()

我个人不喜欢通过捕获异常来检查某些东西。这看起来和感觉很丑陋。这与用相同的方式检查字符串是否只包含数字相同:

s = "84984x"
try:
    int(s)
    do_something(s)
except ValueError:
    do_something_else(s)

不要使用s.isdigit()这种方式,太过生硬。


“int”/“isdigit”版本在一个重要的方面有所不同;“int”版本允许负数。我向你发起挑战,尝试提出一种简洁易读的替代方案,使其能够正确处理负数,并拒绝具有多余前导零的数字(就像Python一样)。 - Dietrich Epp

0

这是一个非常老的问题,但它确实需要一个好的答案。即使是一个简短的程序,我也建议使用自定义函数!

这里有一个例子。它并不完美适用于所有应用程序,但对于我的应用程序,用于解析来自无数API和使用Django的响应是很好的。它很容易根据每个人自己的要求进行修复。

from django.core.exceptions import ObjectDoesNotExist
from functools import reduce

class MultipleObjectsReturned(Exception):
    pass

def get_attr(obj, attr, default, asString=False, silent=True):
    """
    Gets any attribute of obj.
    Recursively get attributes by separating attribute names with the .-character.        
    Calls the last attribute if it's a function.

    Usage: get_attr(obj, 'x.y.z', None)
    """
    try:
        attr = reduce(getattr, attr.split("."), obj)
        if hasattr(attr, '__call__'):
            attr = attr()
        if attr is None:
            return default
        if isinstance(attr, list):
            if len(attr) > 1:
                logger.debug("Found multiple attributes: " + str(attr))
                raise MultipleObjectsReturned("Expected a single attribute")
            else:
                return str(attr[0]) if asString else attr[0]
        else:
            return str(attr) if asString else attr
    except AttributeError:
        if not silent:
            raise
        return default
    except ObjectDoesNotExist:
        if not silent:
            raise
        return default

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