Как BeautifulSoup создает имена объектов во время выполнения? - PullRequest
0 голосов
/ 10 мая 2018

Как BeautifulSoup создает имена объектов во время выполнения? Возьмите следующий код, например

from bs4 import BeautifulSoup
html = """<html>
<head>
<sid><b>Custom Tag</b></sid>
<sid><b>Custom Tag</b></sid>
<sid><b>Custom Tag</b></sid>
</head>
</html>"""
soup = BeautifulSoup(html)
print(soup.html.head.sid)

Как создается объект с именем 'sid'. Ранее я думал, что стандартные HTML-теги уже созданы, но появление пользовательского тега в качестве имени объекта означает, что мое понимание было неверным. Мое ограниченное понимание состоит в том, что bs4 сначала создает и объект типа <class 'bs4.BeautifulSoup'>, который рекурсивно создает объекты типа <class 'bs4.element.Tag'> Мой вопрос заключается в том, как bs4 динамически называет эти <class 'bs4.element.Tag'> объекты фактическими тегами, найденными в html?

Как я могу повторить это?

1 Ответ

0 голосов
/ 10 мая 2018

Ключ __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 этозависит даже от того, идет ли такой потомок до или после текущего узла.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...