Каков наилучший способ проверить наличие атрибута?

Каков лучший способ проверить наличие атрибута?

Джаррет Харди дал этот ответ:

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

Я вижу, что это также можно сделать следующим образом:

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

Один подход обычно используется больше, чем другие?

Ответы

Ответ 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' 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 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 ведет себя странно в версиях Python до 3.2 в отношении исключений - он будет ловить исключения, которых он не должен, но это, вероятно, не имеет значения, поскольку такие исключения маловероятны. 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 для импорта дополнительных модулей, будьте моим гостем... похоже, что функция будет полным монстром.

Способ 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). Таким образом, все они ведут себя так же, как и считают, и выбор правильного объясняется обстоятельствами и стилем.

Код EAFP try/except всегда будет неправильно протирать некоторых программистов, а код hasattr/getattr LBYL будет раздражать других программистов. Они оба правильны, и часто нет поистине убедительных оснований для выбора того или другого. (Тем не менее, другим программистам отвратительно, что вы считаете нормальным для атрибута undefined, а некоторые программисты в ужасе, что даже возможно иметь атрибут undefined в Python.)

Ответ 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().

*До того, как Python 3.1 hasattr() подавил все исключения (не только AttributeError), если это нежелательно getattr() should.

Ответ 3

hasattr() - это питонический способ сделать это. Учите его, любите.

Другой возможный способ - проверить, находится ли имя переменной в 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.

Ответ 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 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