PyYAML интерпретирует строку как метку времени
Похоже, что PyYAML интерпретирует строку 10: 01 как длительность в секундах:
import yaml
>>> yaml.load("time: 10:01")
{'time': 601}
Официальная документация не отражает этого: PyYAML documentation
Есть предложения, как читать 10: 01 в виде строки?
3 ответа:
Заключите это в кавычки:
>>> import yaml >>> yaml.load('time: "10:01"') {'time': '10:01'}
Это говорит YAML, что это литеральная строка, и препятствует попыткам рассматривать ее как числовое значение.
Поскольку вы используете синтаксический анализатор для YAML 1.1, вы должны ожидать, что будет реализовано то, что указано в спецификации (пример 2.19):
sexagesimal: 3:25:45
Sexagesimals далее объясняются Здесь :
Использование ": "позволяет выражать целые числа в базе 60, что удобно для значений времени и угла.
Не каждая деталь, которая реализована в PyYAML, находится в документации, на которую вы ссылаетесь, вы должны видеть это только как вступление.
Вы не единственный, кто нашел эту интерпретацию запутанной, и в YAML 1.2 sexagesimals были исключены из спецификации . Хотя эта спецификация была выпущена около восьми лет назад, изменения никогда не были реализованы в PyYAML.
Самый простой способ решить эту проблему-перейти на ruamel.yaml (отказ от ответственности: я являюсь автором этого пакета), вы получите поведение YAML 1.2 (если вы явно не укажете, что хотите используйте YAML 1.1), который интерпретирует10:01
как строку:from ruamel import yaml import warnings warnings.simplefilter('ignore', yaml.error.UnsafeLoaderWarning) data = yaml.load("time: 10:01") print(data)
Что дает:
{'time': '10:01'}
Предупреждения.фильтр необходим только потому, что вы используете
.load()
вместо.safe_load()
. Первый является небезопасным и может привести к стертому диску или еще хуже, когда используется на неконтролируемом входе YAML. Редко есть причина не использовать.safe_load()
.
Если вы хотите обезьянничать в библиотеке pyyaml, чтобы у нее не было такого поведения(поскольку нет аккуратного способа сделать это), для решателя по вашему выбору, код ниже работает. Проблема в том, что регулярное выражение, используемое для
int
, включает в себя некоторый код для сопоставления меток времени , хотя, похоже, нет спецификации для этого поведения, это просто считалось "хорошей практикой" для строк, таких как30:00
или40:11:11:11:11
, которые должны рассматриваться как целые числа.import yaml import re def partition_list(somelist, predicate): truelist = [] falselist = [] for item in somelist: if predicate(item): truelist.append(item) else: falselist.append(item) return truelist, falselist @classmethod def init_implicit_resolvers(cls): """ creates own copy of yaml_implicit_resolvers from superclass code taken from add_implicit_resolvers; this should be refactored elsewhere """ if not 'yaml_implicit_resolvers' in cls.__dict__: implicit_resolvers = {} for key in cls.yaml_implicit_resolvers: implicit_resolvers[key] = cls.yaml_implicit_resolvers[key][:] cls.yaml_implicit_resolvers = implicit_resolvers @classmethod def remove_implicit_resolver(cls, tag, verbose=False): cls.init_implicit_resolvers() removed = {} for key in cls.yaml_implicit_resolvers: v = cls.yaml_implicit_resolvers[key] vremoved, v2 = partition_list(v, lambda x: x[0] == tag) if vremoved: cls.yaml_implicit_resolvers[key] = v2 removed[key] = vremoved return removed @classmethod def _monkeypatch_fix_int_no_timestamp(cls): bad = '|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+' for key in cls.yaml_implicit_resolvers: v = cls.yaml_implicit_resolvers[key] vcopy = v[:] n = 0 for k in xrange(len(v)): if v[k][0] == 'tag:yaml.org,2002:int' and bad in v[k][1].pattern: n += 1 p = v[k][1] p2 = re.compile(p.pattern.replace(bad,''), p.flags) vcopy[k] = (v[k][0], p2) if n > 0: cls.yaml_implicit_resolvers[key] = vcopy yaml.resolver.Resolver.init_implicit_resolvers = init_implicit_resolvers yaml.resolver.Resolver.remove_implicit_resolver = remove_implicit_resolver yaml.resolver.Resolver._monkeypatch_fix_int_no_timestamp = _monkeypatch_fix_int_no_timestamp
Тогда, если вы это сделаете это:
class MyResolver(yaml.resolver.Resolver): pass t1 = MyResolver.remove_implicit_resolver('tag:yaml.org,2002:timestamp') MyResolver._monkeypatch_fix_int_no_timestamp() class MyLoader(yaml.SafeLoader, MyResolver): pass text = ''' a: 3 b: 30:00 c: 30z d: 40:11:11:11 ''' print yaml.safe_load(text) print yaml.load(text, Loader=MyLoader)
Затем он печатает
{'a': 3, 'c': '30z', 'b': 1800, 'd': 8680271} {'a': 3, 'c': '30z', 'b': '30:00', 'd': '40:11:11:11'}
Показывает, что поведение yaml по умолчанию было оставлено без изменений, но ваш частный класс загрузчика обрабатывает эти строки разумно.