Python – функция генератора сбрасывается между вызовами?

Я анализирую словарь языка, представленный в файле XML, с помощью функции iterparse ElementTree. Я фильтрую его с помощью функции генератора, и какое-то странное непонимание порядка выполнения дает мне повторяющуюся запись. Вот некоторый код установки (на самом деле это происходит внутри функции, но остальные детали не имеют значения):

import xml.etree.cElementTree as ET
dictionary = iter(ET.iterparse("../dictionaries/language_name.xml", 
                  events=("start", "end"))) 
#We can discard the original iterable, I think

Фильтрация

Затем у меня есть функция, которая получает итератор и фильтрует его (игнорируйте глобальную переменную, это просто для отладки проблемы):

def get_entries(iterparsed):
    global yielded
    root = next(iterparsed)[1] #iterpase gives (event, element)
    yield root

    for event, elem in iterparsed:
        if event == "end" and elem.tag == "entry":
            yielded += 1
            print("Num yielded:", yielded)
            print("Yielding", ET.tostring(elem, encoding="utf-8"))
            yield elem

Обработка

Затем я использую его так (опять же, временный глобальный для отладки):

root = next(get_entries(dictionary))
for elem in get_entries(dictionary):
    global received
    received += 1
    print("Num received:", received)
    print("I got", ET.tostring(elem, encoding="utf-8"))
    raw_input("Continue? ") 
    #I only yield the first item once, but receive it twice? :(
    process_entry(elem) #Defined elsewhere, adds a <sgmtd> node to each entry
    root.clear() #Clears the processed children of root node

Выход

Если я прогоню все подряд, yielded = 9050 пока received = 9051. И проблемный вывод:

Num received: 1
I got <entry><form>aː</form><ortho>a:</ortho><pos>dcadv</pos><sense><def><en>over here</en><es>acá</es></def></sense></entry>

Continue? 
Num yielded: 1
Yielding <entry><form>aː</form><ortho>a:</ortho><sgmtd /><pos>dcadv</pos><sense><def><en>over here</en><es>acá</es></def></sense></entry>

Num received: 2
I got <entry><form>aː</form><ortho>a:</ortho><sgmtd /><pos>dcadv</pos><sense><def><en>over here</en><es>acá</es></def></sense></entry>

Continue?
Num yielded: 2
Yielding <entry><form>aːčáx</form><ortho>a:cháj</ortho><pos>n</pos><sense><def><en>axe</en><es>hacha</es></def></sense></entry>

Num received: 3
I got <entry><form>aːčáx</form><ortho>a:cháj</ortho><pos>n</pos><sense><def><en>axe</en><es>hacha</es></def></sense></entry>

Continue?

Вопрос

Теперь я проверил, и elem не определен до начала цикла. И нет, в начале файла нет двух одинаковых элементов. После этого первого бита "Я получил" все, кажется, работает так, как я и ожидал - вещи сначала возвращаются, а затем принимаются (например, a:cháj axe сначала передается, а затем принимается).

Еще более странно, что этот первый элемент обрабатывается до того, как будет возвращен — без очистки в конце цикла for. В первый раз, когда он "получен", он не имеет узла ‹sgmtd›. Когда он «выдается» в первый раз, он уже имеет узел ‹sgmtd›, указывающий, что он был обработан. Затем он снова принимается, и (несмотря на строку if not elem.find("sgmtd"): elem.insert(2, segmented_form)) добавляется и записывается в файл второй узел ‹sgmtd›. Итак, мой выходной файл заканчивается:

<?xml version="1.0" encoding="UTF-8"?>
<lexicon>
<entry><form>aː</form><ortho>a:</ortho><sgmtd /><pos>dcadv</pos><sense><def><en>over here</en><es>acá</es></def></sense></entry>
<entry><form>aː</form><ortho>a:</ortho><sgmtd /><sgmtd /><pos>dcadv</pos><sense><def><en>over here</en><es>acá</es></def></sense></entry>

Так что же я здесь неправильно понимаю? Как получается, что элемент «получается» из функции-генератора без какого-либо кода до выполнения оператора yield?

Оказывается, изменение строки if not elem.find("sgmtd") на if elem.find("sgmtd") is None останавливает обработку повторяющегося элемента. Я предполагаю, что объекты Element не преобразуются неявно в True, как я ожидал. Но мне все равно хотелось бы знать, почему он появился!


person Matt Darling    schedule 16.08.2013    source источник
comment
Первый элемент в get_entries - это yield root, который ничего не печатает перед ним, и вы его не считаете, так что ваш вывод просто вводит в заблуждение. В начале тоже нет одинаковых элементов, просто посмотрите на длину текста.   -  person Jochen Ritzel    schedule 16.08.2013


Ответы (2)


Оба @Chad Miller и @Jochen Ritzel указал, что я не учитывал корневой элемент, который уступал. Это было преднамеренно — я думал, что моя функция-генератор никогда не сбрасывается, как не сбрасываются объекты-генераторы. Поэтому, когда я начал цикл с for elem in get_entries(dictionary), я решил, что корневой элемент уже будет использован.

Однако, если я добавлю оператор печати до получения корневого элемента, он будет напечатан дважды. Дублирование данных, которое я видел, было вызвано вызовом elem.insert(2, segmented_form) в корне, где segmented_form включает использование elem.find (таким образом, поиск его дочерних элементов) и захват первого элемента дерева.

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

person Matt Darling    schedule 17.08.2013

Похоже, ваш счетчик в вашем фильтре неверен.

Ваш счетчик появляется во внутреннем цикле и увеличивается только тогда, когда этот внутренний цикл что-то yields. Но нет приращения для запуска вашего генератора, где написано yield root.

person Free Monica Cellio    schedule 16.08.2013