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

23 浏览
0 Comments

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

这个问题已经有了答案:

如何检查一个对象是否有属性?

哪种方法更好地检查属性的存在?

Jarret Hardie 提供了这个答案:

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

我发现也可以使用这种方式:

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

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

admin 更改状态以发布 2023年5月20日
0
0 Comments

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()

0
0 Comments

没有"最好的"方法,因为你从来不会只是检查一个属性是否存在;它总是某个更大程序的一部分。有几个正确的方法和一个明显的错误方法。

错误的方法

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()

(在Python 3.2之前的版本中,hasattr内置方法实际上在处理异常时表现得很奇怪——它会捕捉不应该捕捉的异常——但这可能不重要,因为这样的异常是不太可能发生的。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 中可能存在未定义属性感到恐惧。)

0