Почему PyYAML / ruamel объединяет экранированные суррогаты на Python 2? - PullRequest
0 голосов
/ 01 ноября 2019

Для проекта Python, который задает некоторые из своих тестовых случаев в файлах YAML, я хотел бы указать байтовые строки в кодировке UTF-8 в тестовых случаях YAML, включая недопустимые последовательности UTF-8, такие как непарные суррогаты или поменянные местами high / lowсуррогаты с целью тестирования их обработки проектом Python.

Я использую скаляры в двойных кавычках в тестовом регистре YAML (потому что это единственный способ указать escape-последовательности в YAML) и 16-битный юникодэкранирует (например, \ u03B5), чтобы указать суррогатные кодовые точки.

Наш проект Python использует PyYAML для загрузки YAML-файлов тестового примера, но я убедился, что приведенные ниже результаты также применимы к ruamel. Ниже приведен пример с PyYAML (как на Python 2.7 и 3.7, если не указано иное, так и на macOS с CPython):

>>> import yaml  # PyYAML

# some valid Unicode character (lower case greek epsilon, fwiw)
>>> yaml.safe_load('x: "\\u03B5"')  
{'x': u'\u03b5'}

# invalid unpaired surrogate
>>> yaml.safe_load('x: "\\uD800"')  
{'x': u'\ud800'}

# invalid swapped high/low surrogates
>>> yaml.safe_load('x: "\\uDC00\\uD800"')
{'x': u'\udc00\ud800'}

# valid surrogates, but using surrogates is illegal in UTF-8
>>> yaml.safe_load('x: "\\uD800\\uDC00"')  
{'x': '\ud800\udc00'}  # on Python 3.7
{'x': u'\U00010000'}   # on Python 2.7  <-- why??

Поведение последнего случая выше на Python 2.7 делает невозможным передачуэтот случай, как и предполагалось для нашего проекта Python, потому что он «исправляется» либо PyYAML, либо самим Python.

Где происходит это «исправление» и можно ли его отключить?

1 Ответ

0 голосов
/ 01 ноября 2019

Я не уверен, какие версии вы используете, но я не могу воспроизвести это. Только libyaml, кажется, имеет проблемы с вашим вводом:

from __future__ import print_function

import sys
print('Python:     ', sys.version_info[:2])
import yaml as pyyaml
print('PyYAML:     ', pyyaml.__version__)
import ruamel.yaml
print('ruamel.yaml:', ruamel.yaml.version_info)

yaml_str = 'x: "\\uD800\\uDC00"'
print('yaml_str', yaml_str)

print(repr(pyyaml.safe_load(yaml_str)['x']))



for typ, pure in [('rt', True), ('safe', True), ('safe', False)]:
    yaml = ruamel.yaml.YAML(typ=typ, pure=pure)
    data = yaml.load(yaml_str)
    print(repr(data['x']))

дает:

Python:      (2, 7)
PyYAML:      5.1.2
ruamel.yaml: (0, 16, 6, u'dev')
yaml_str x: "\uD800\uDC00"
u'\ud800\udc00'
u'\ud800\udc00'
u'\ud800\udc00'
Traceback (most recent call last):
  File "/tmp/ryd-of-anthon/ryd-111/tmp_0.py", line 20, in <module>
    data = yaml.load(yaml_str)
  File "/home/anthon/.venv/27/lib/python2.7/site-packages/ruamel/yaml/main.py", line 341, in load
    return constructor.get_single_data()
  File "/home/anthon/.venv/27/lib/python2.7/site-packages/ruamel/yaml/constructor.py", line 111, in get_single_data
    node = self.composer.get_single_node()
  File "_ruamel_yaml.pyx", line 706, in _ruamel_yaml.CParser.get_single_node
  File "_ruamel_yaml.pyx", line 724, in _ruamel_yaml.CParser._compose_document
  File "_ruamel_yaml.pyx", line 775, in _ruamel_yaml.CParser._compose_node
  File "_ruamel_yaml.pyx", line 889, in _ruamel_yaml.CParser._compose_mapping_node
  File "_ruamel_yaml.pyx", line 731, in _ruamel_yaml.CParser._compose_node
  File "_ruamel_yaml.pyx", line 904, in _ruamel_yaml.CParser._parse_next_event
ruamel.yaml.scanner.ScannerError: while parsing a quoted scalar
  in "<byte string>", line 1, column 4
found invalid Unicode character escape code
  in "<byte string>", line 1, column 7
...