没有“最好”的方法,因为您永远不会只是检查某个属性是否存在;它总是一些更大的程序的一部分。有几种正确的方法和一种值得注意的错误方法。
错误的方式
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 中甚至可能有未定义属性感到震惊。)