Какой лучший способ проверить наличие атрибута? [дубликат]
этот вопрос уже есть ответ здесь:
какой лучший способ проверить наличие атрибута?
Джаррет Харди при условии, что этот ответ:
if hasattr(a, 'property'):
a.property
Я вижу, что это также можно сделать образом:
if 'property' in a.__dict__:
a.property
один подход обычно используется больше других?
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
hasattrbuiltin на самом деле ведет себя странно в версиях 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/getattrLBYL код будет раздражать других программистов. Они оба правы, и часто нет действительно убедительной причины выбрать один или другой. (Тем не менее другие программисты испытывают отвращение, что вы считаете нормальным, чтобы атрибут был неопределенным, и некоторые программисты в ужасе от того, что в 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