检查属性是不是存在的最佳方法是啥? [复制]

Posted

技术标签:

【中文标题】检查属性是不是存在的最佳方法是啥? [复制]【英文标题】:Which is the best way to check for the existence of an attribute? [duplicate]检查属性是否存在的最佳方法是什么? [复制] 【发布时间】:2012-04-02 16:04:26 【问题描述】:

检查属性是否存在的更好方法是?

Jarret Hardie 提供了这个答案:

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

我看到也可以这样实现:

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

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

【问题讨论】:

您的第二个选项是错误的,无论如何,您提供的链接中的second answer 是您问题的答案。 【参考方案1】:

没有“最好”的方法,因为您永远不会只是检查某个属性是否存在;它总是一些更大的程序的一部分。有几种正确的方法和一种值得注意的错误方法。

错误的方式

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' 在 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 版本中的异常行为很奇怪——它会捕获它不应该捕获的异常——但这可能无关紧要,因为这种异常不太可能发生。@987654331 @ 技术也比 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 中甚至可能有未定义属性感到震惊。)

【讨论】:

当您几乎总是期望x 具有属性x.attr 时,可以说try... except 更好。 (那么例外将是真正的例外。) 我个人几乎总是使用try... excepthasattr 方法在我的嘴里留下了不好的味道。 @Dietrich Epp:可能隐藏异常的代码,例如上面的try/except,肯定比hasattr()差。 @J.F.Sebastian:通常是try/except/else 块。如果你觉得它很重要,我会祝福你编辑它。 @KarlKnechtel:啊,当然。道歉。我认为 10 次中有 9 次我误解了该网站上的评论,这是由于那些棘手的代词及其先行词;他们的解释既不明显也不明确,两个人会在没有实际交流的情况下长时间交流。【参考方案2】:

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

【讨论】:

【参考方案3】:

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()。哎哟。

【讨论】:

int/isdigit 版本在一个重要方面有所不同; int 版本允许负数。我挑战你想出一个简洁易读的等价于try: int(s) except ValueError: ...,它可以正确处理负数,并拒绝带有无关前导零的数字(就像 Python 所做的那样)。【参考方案4】:

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

这是一个例子。它并不适合所有应用程序,但它适用于我的应用程序,用于解析来自无数 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

【讨论】:

以上是关于检查属性是不是存在的最佳方法是啥? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

检查变量是不是为列表的最佳方法是啥? [复制]

检查给定变量是不是为 NaN 的最佳方法是啥? [复制]

Boto:检查 CloudFormation 堆栈是不是存在的最佳方法是啥?

检查 C 中是不是存在文件的最佳方法是啥?

检查 C 中是不是存在文件的最佳方法是啥?

如果路径已知,检查 Firestore 记录是不是存在的最佳方法是啥?