PyYAML интерпретирует строку как метку времени


Похоже, что PyYAML интерпретирует строку 10: 01 как длительность в секундах:

import yaml
>>> yaml.load("time: 10:01")
{'time': 601}

Официальная документация не отражает этого: PyYAML documentation

Есть предложения, как читать 10: 01 в виде строки?

3 2

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 по умолчанию было оставлено без изменений, но ваш частный класс загрузчика обрабатывает эти строки разумно.