Функция recume ruamel.yaml: неожиданно обновляет каждый экземпляр нестроковых данных с одинаковым значением - PullRequest
0 голосов
/ 26 сентября 2019

Я открываю этот вопрос по запросу от автора ruamel.yaml на Как изменить привязанный скаляр в последовательности, не разрушая якорь в ruamel.yaml? .

В ответе https://stackoverflow.com/a/55717146/5880190, был дан следующий код для решения вопроса о том, как обновить псевдонимы значений в данных ruamel.yaml:

def update_aliased_scalar(data, obj, val):
    def recurse(d, ref, nv):
        if isinstance(d, dict):
            for i, k in [(idx, key) for idx, key in enumerate(d.keys()) if key is ref]:
                d.insert(i, nv, d.pop(k))
            for k, v in d.non_merged_items():
                if v is ref:
                    d[k] = nv
                else:
                    recurse(v, ref, nv)
        elif isinstance(d, list):
            for idx, item in enumerate(d):
                if item is ref:
                    d[idx] = nv
                else:
                    recurse(item, ref, nv)

    if hasattr(obj, 'anchor'):
        recurse(data, obj, type(obj)(val, anchor=obj.anchor.value))
    else:
        recurse(data, obj, type(obj)(val))

Этот блестящий код работал такхорошо, что я обернул его в функцию и использовал в своем проекте для обработки выполнения всех изменений данных, как показано здесь (с легким переименованием, чтобы соответствовать стилю кода, в который он был вставлен): https://github.com/wwkimball/yamlpath/blob/319585620abfab199f3e15c87e0a2dc2c900aa1d/yamlpath/processor.py#L739-L781

Это сработало очень хорошо для большинства случаев использования моего проекта.Я использую этот код с большим успехом в производстве с тех пор.Значения, с которыми я работаю, - это почти исключительно строковые данные, и случайно любые нестроковые данные оказываются псевдонимами, потому что обычно используются номера портов службы.

На основании этих успехов, а не при критическом чтении кода (Я полностью доверяю автору, который знает ruamel.yaml гораздо лучше меня), я ошибочно полагал, что этот код обновляет только целевой узел и любые ссылки на it .Поэтому я также подумал, что этот код можно использовать для обновления данных без псевдонимов.Я ошибался.Это моя ошибка.

Как оказалось, всякий раз, когда для обновления этой функции передается не строковое значение, он заменяет не только этот целевой узел, но каждый узел наодно и то же значение, даже если они не являются ссылками друг на друга .Таким образом, когда данные выглядят следующим образом:

---
key: 42
other_key: 42

Вызов функции для изменения key: 42 на key: 5280 не только вносит ожидаемое изменение, но также меняет other_key: на 5280.Это не происходит , когда изменяемое значение представляет собой строковые данные без псевдонима, независимо от того, сколько других узлов имеют такое же значение (что заставило меня поверить, что это безопасноиспользуйте эту функцию для обновления любого значения , с псевдонимом или без него).Это действительно также происходит, когда значения булевы.

Я не понял, что на самом деле делал код.Я использовал код способом, для которого он не был предназначен.

Мне нужна функция, которая принимает узел для изменения, а затем изменяет только этот узел, когда он являетсязначение без псевдонима и , если оно является значением с псевдонимом, также измените все остальные узлы с псевдонимом без , влияя на другие узлы, которые являются не связанными псевдонимами (* alias1 по сравнению с* alias2) с тем же значением или значениями без псевдонимов, которые совпадают с обновляемым псевдонимом. Когда я вызываю функцию, у меня есть только все данные, целевой узел иожидаемое новое значение для него.

Я открыт для рефакторинга своего собственного кода, если функция требует больше информации при вызове.

1 Ответ

0 голосов
/ 27 сентября 2019

Ваши проблемы возникают из-за ограничения (ошибка cq) в функции recurse, как представлено в другом ответе.

При загрузке образца YAML оба «узла» для значений 42 имеют одинаковый идентификатор.Это оптимизация Python, и она применяется к логическим значениям, подмножеству целых чисел (до 100 IIRC) и т. Д. Поскольку recurse проверяет идентичность (используя is), if v is ref совпадает два раза.

Это в основном из-за передаваемого объекта obj, и вам потребуется родительский объект и ключ / индекс для этого объекта:

import sys
import ruamel.yaml

yaml_str = """\
- key: 42
  other_key: 42
  k: &xx 196
  l: *xx
"""

def update_aliased_scalar(data, parent, key_index, val):
    def recurse(d, parent, key_index, ref, nv):
        if isinstance(d, dict):
            for i, k in [(idx, key) for idx, key in enumerate(d.keys()) if key is ref]:
                d.insert(i, nv, d.pop(k))
            for k, v in d.non_merged_items():
                if v is ref:
                    if hasattr(v, 'anchor') or (d is parent and k == key_index):
                        d[k] = nv
                else:
                    recurse(v, parent, key_index, ref, nv)
        elif isinstance(d, list):
            for idx, item in enumerate(d):
                if item is ref:
                    d[idx] = nv
                else:
                    recurse(item, parent, key_index, ref, nv)

    obj = parent[key_index]
    if hasattr(obj, 'anchor'):
        recurse(data, parent, key_index, obj, type(obj)(val, anchor=obj.anchor.value))
    else:
        recurse(data, parent, key_index, obj, type(obj)(val))

yaml = ruamel.yaml.YAML()
data = yaml.load(yaml_str)

update_aliased_scalar(data, data[0], 'key', 43)
update_aliased_scalar(data, data[0], 'k', 197)
yaml.dump(data, sys.stdout)

, что дает:

- key: 43
  other_key: 42
  k: &xx 197
  l: *xx
...