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



этот вопрос уже есть ответ здесь:



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



Джаррет Харди при условии, что этот ответ:



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


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



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


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

586   4  

4 ответов:

нет никакого "лучшего" способа, потому что вы никогда не просто проверяете, существует ли атрибут; он всегда является частью какой-то более крупной программы. Есть несколько правильных способов и один заметный неправильный способ.

неправильно

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

The hasattr метод, конечно, тоже работает. Эта техника называется "взгляд перед прыжком", или LBYL для краткости.

# Cached attribute
if not hasattr(self, 'big_object'):
    big_object = CreateBigObject()
    self.big_object = CreateBigObject()
big_object.do_something()

(The hasattr builtin на самом деле ведет себя странно в версиях 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). Таким образом, все они ведут себя одинаково, когда это имеет значение, и выбор правильного из-за обстоятельств и стиля.

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

hasattr() путь*.

a.__dict__ уродливо, и это не работает во многих случаях. hasattr() на самом деле пытается получить атрибут и ловит AttributeError внутренне так это работает, даже если вы определяете custom __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 внутри

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(). Фу.

очень старый вопрос, но он действительно нуждается в хорошем ответе. Даже для короткой программы, я бы сказал, использовать пользовательскую функцию!

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

Comments

    Ничего не найдено.