Почему утверждение в пустом списке / dict завершается с ошибкой?

#python #elementtree

#python #elementtree

Вопрос:

 assert {}
  

сбой с AssertionError помощью .

Проблема, приведшая к этому вопросу, заключалась в следующем:

 import xml.etree.ElementTree as ET

xml_element = ET.Element("tag")

assert xml_element
  

также завершается с AssertionError ошибкой .

assert оценивает expression , я понимаю.

Также;

 if []:
    print("foo")
  

ничего не печатается, потому что возвращается пустой список False , но,

 if [1]:
    print("foo")
  

будет печатать foo.

Мой вопрос в том, как я мог проверить внутренности этого поведения?

Комментарии:

1. Почему бы ему не завершиться с ошибкой? Как вы видели с if , пустые объекты имеют значение false-y. И вы спрашиваете о списках и dicts или почему Element("tag") false-y ?

Ответ №1:

Когда вещи обычно преобразуются в true / false?

Вопрос, который вы действительно задаете: «что происходит, когда вы преобразуете список или словарь в bool?». Это потому if x: , что и assert x ведут себя так же, как если бы вы написали if bool(x): или assert bool(x) . (Кстати, помещать этот явный вызов bool() в эти места — плохой тон, именно потому, что он избыточен.)

Ответ заключается в том, что списки и словари (а также строки и другие контейнеры) True преобразуются в bool , если они не пустые. И наоборот, они False преобразуются в bool if они пусты.

Терминология, часто используемая для этого, заключается в том, что непустые контейнеры являются «правдивыми», а пустые контейнеры — «ложными».

Это имеет интересное следствие, которое bool("False") вычисляется True как — потому "False" что это непустая строка!

Помимо контейнеров, есть и другие обычные случаи: числа являются истинными, если они ненулевые (т. Е. 27 И 0.5 Являются истинными, а 0 и 0.0 являются ложными), а значение None является ложным.

Как узнать для других классов

как я мог проверить внутренности этого поведения?

Как было сказано выше, все, что достаточно похоже на коллекцию, истинно, когда оно непустое; обычный тест на то, «похоже ли оно на контейнер», заключается в том, можете ли вы len(x) успешно вызвать его, и в этом случае оно истинно, когда len(x) оно не равно нулю.

Даже если класс не является контейнером (или числом), все же возможно, что он имеет какое-то значимое преобразование в bool() . На самом деле вы даже можете определить это в своих собственных классах, определив __bool__() метод. Единственный способ определить это, не глядя на исходный код, — это посмотреть документацию для используемой библиотеки.

Если такое преобразование не определено библиотекой, bool(x) оно всегда будет возвращаться True (это значение по умолчанию для классов, в которых не __bool__() __len__() определены методы or). См. Тестирование истинностных значений в официальных документах Python для получения дополнительной информации.

В частности, ElementTree

xml.etree.ElementTree в частности, к сожалению, все немного запутано. A немного похож на контейнер элементов: e[0] возвращает первый дочерний элемент, for child in e возвращает все дочерние элементы и len(e) возвращает количество дочерних элементов (все это задокументировано в официальных документах). Поэтому имеет смысл, что bool(e) это верно именно тогда, когда есть хотя бы один дочерний элемент, который соответствует тому, как работает весь остальной Python. Однако, как показывает ответ Tomerikoo в исходном коде, хотя в настоящее время это работает, оно устарело и может не работать в будущем. В документах также упоминается об этом, но оно чрезвычайно скрыто:

Внимание: элементы без подэлементов будут тестироваться как False . Это поведение изменится в будущих версиях. Вместо этого используйте specific len(elem) или elem is None test .

Запутанная логика, стоящая за этим, объясняется в приведенном ниже фрагменте кода. Фоном является maybe_child = e.find("child") поиск подэлемента e с именем "child" или None , если такой элемент не найден. Но None является ложным, поэтому возникает соблазн проверить, был ли этот поиск успешным путем записи if maybe_child: , но это будет фактически считаться, как False если бы дочерний элемент был найден, но сам не имел дочерних элементов. Вот фрагмент из документации:

 element = root.find('foo')

if not element:  # careful!
    print("element not found, or element has no subelements")

if element is None:
    print("element not found")
  

Ответ №2:

Из ElementTree исходного кода (класса Element ):

 class Element:
[...]
    def __init__(self, tag, attrib={}, **extra):
        if not isinstance(attrib, dict):
            raise TypeError("attrib must be dict, not %s" % (
                attrib.__class__.__name__,))
        self.tag = tag
        self.attrib = {**attrib, **extra}
        self._children = []
[...]
    def __bool__(self):
        warnings.warn(
            "The behavior of this method will change in future versions.  "
            "Use specific 'len(elem)' or 'elem is not None' test instead.",
            FutureWarning, stacklevel=2
            )
        return len(self._children) != 0 # emulate old behaviour, for now
  

Поскольку вы только что инициализировали новый элемент, _children это пустой список (как видно из __init__ ), поэтому, если вы понимаете, почему assert [] сбой, вы также должны понимать, почему assert xml_element сбой.