检查属性是否存在的最佳方法是哪种?

时间:2012-03-17 09:04:46

标签: python attributes

哪种方法可以检查是否存在属性?

Jarret Hardie提供了这个答案:

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

我看到它也可以这样做:

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

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

4 个答案:

答案 0 :(得分:134)

没有“最佳”方式,因为您不仅要检查属性是否存在;它总是一些大型计划的一部分。有几种正确的方法和一种值得注意的错误方法。

错误的方式

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

输出:

'prop' in a.__dict__ = 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()

hasattr内置在3.2之前的Python版本中实际上在异常方面表现得很奇怪 - 它会捕获它不应该的异常 - 但这可能是无关紧要的,因为这样的异常是不可能的。 hasattr技术也比try/except慢,但是你并不经常调用它,而且差异不是很大。最后,hasattr不是原子的,所以它可以如果另一个线程删除了该属性,抛出AttributeError,但这是一个牵强附会的场景,无论如何你都需要非常小心线程。我不认为这三个差异中的任何一个值得担心。 )

使用hasattrtry/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中有一个未定义的属性。)

答案 1 :(得分:11)

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 sentinel。

我不喜欢try: do_something(x.attr) \n except AttributeError: ..它可能会在AttributeError函数中隐藏do_something()

* Before Python 3.1 hasattr() suppressed all exceptions(不仅AttributeError)如果不合适,应使用getattr()

答案 2 :(得分:5)

hasattr()是Pythonic的做法。学习它,喜欢它。

其他可行方式是检查变量名称是否在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()。 EWW。

答案 3 :(得分: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