Ключ __getattr__
, специальный метод, позволяющий классам динамически создавать атрибуты по требованию.
Сильное упрощение: 1 при записи spam.eggs
, Python делает что-то вроде этого псевдокода:
for obj in [spam] + type(spam).__mro__:
try:
return obj.__dict__['eggs']
except KeyError:
pass
return spam.__getattr__('eggs')
По умолчанию object.__getattr__
просто вызывает AttributeError
, но вы можете переопределить его, чтобы сделать все, что вы хотите.
Вот код из Tag.__getattr__
:
def __getattr__(self, tag):
#print "Getattr %s.%s" % (self.__class__, tag)
if len(tag) > 3 and tag.endswith('Tag'):
# BS3: soup.aTag -> "soup.find("a")
tag_name = tag[:-3]
warnings.warn(
'.%sTag is deprecated, use .find("%s") instead.' % (
tag_name, tag_name))
return self.find(tag_name)
# We special case contents to avoid recursion.
elif not tag.startswith("__") and not tag=="contents":
return self.find(tag)
raise AttributeError(
"'%s' object has no attribute '%s'" % (self.__class__, tag))
Существует множество специальных кодов для обработки таких вещей, как унаследованный доступ в стиле BS3, но в остальном он очень прост:tag.spam
просто возвращает tag.find('spam')
.
Если вы планируете сделать это самостоятельно, имейте в виду, что Python также предлагает "полудинамические" атрибуты, которые вы можете создавать по имени (используя setattr
в какой-то момент после построения, а не каждый раз, когда они ищут. Для многих проектов это лучшее решение (более эффективное, атрибуты отображаются в dir
и других рефлексивных вызовах и т. Д.Возможно, это будет плохая идея для BS4изменяемые деревья, 2 , но если он не допускает мутации, возможно, имеет смысл сделать что-то вроде этого псевдокода:
for node in walk(tree):
for ancestor in node.ancestor_chain():
if not hasattr(ancestor, node.name):
setattr(ancestor, node.name, node)
1.В частности, это поведение реализовано в object.__getattribute__
, поэтому, если вы хотите полностью пропустить функциональность по умолчанию, вы даже можете переопределить этот метод.Кроме того, я проигнорировал дескрипторы __slots__
, , поиск специальных методов , как работают встроенные / C-расширения и т. Д.
2.Представьте, что вы переименовали этот узел sid
в pid
.Он не может просто пройтись по цепочке предков и удалить sid
и добавить атрибуты pid
к каждому из них - может быть другой потомок sid
, который теперь должен получить атрибут sid
, а для pid
этозависит даже от того, идет ли такой потомок до или после текущего узла.