检查属性是不是存在的最佳方法是啥? [复制]
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
属性,但这是一个牵强的场景,无论如何你都需要非常小心线程。我认为这三个差异中的任何一个都不值得担心。)
使用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 方式
如果您只需要一个默认值,getattr
是try/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
结论
在内部,getattr
和 hasattr
内置函数只使用 try/except
技术(用 C 编写的除外)。因此,它们的行为方式都相同,而选择正确的方式取决于环境和风格。
try/except
EAFP 代码总是会惹恼一些程序员,hasattr/getattr
LBYL 代码会惹恼其他程序员。它们都是正确的,而且通常没有真正令人信服的理由来选择其中一个。 (但其他程序员对您认为未定义属性是正常的感到厌恶,而有些程序员则对在 Python 中甚至可能有未定义属性感到震惊。)
【讨论】:
当您几乎总是期望x
具有属性x.attr
时,可以说try... except
更好。 (那么例外将是真正的例外。)
我个人几乎总是使用try... except
,hasattr
方法在我的嘴里留下了不好的味道。
@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
【讨论】:
以上是关于检查属性是不是存在的最佳方法是啥? [复制]的主要内容,如果未能解决你的问题,请参考以下文章