没有“最佳”的方法, ,因为您从来不只是检查属性是否存在; 它总是某个较大程序的一部分。有几个正确的方法和一个值得注意的不正确的方法。

走错路了

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

(在3.2之前的 Python 版本中,hasattr内建实际上在异常方面表现得很奇怪——它会捕获不应该捕获的异常——但这可能是不相关的,因为这样的异常是不太可能的。hasattr技术也比 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 函数,请便... ... 这个函数听起来就像一个怪物。

这就是我们的方法

如果您只需要一个默认值,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 中甚至可能有未定义的属性感到恐惧。)

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: ..它可能隐藏 AttributeError内的 do_something()功能。

* 在 Python 3.1之前,hasattr()抑制了所有异常(不仅仅是 AttributeError)如果不合适,应该使用 getattr()

hasattr() 是 Python 式的方法,学习它,喜欢它。

其他可能的方法 是检查变量名是在 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。

这是个很老的问题,但是真的需要一个好的答案。即使是一个很短的程序,我会说使用一个自定义函数!

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