Python: преобразование сложной спискообразной строки в список (без SyntaxError для смешивания байтов и небайтовых литералов) - PullRequest
0 голосов
/ 09 июня 2018

У меня есть эта строка: u'''[ "A", """B""",'C' , " D"]'''

Я хотел бы получить список вроде: ["A","""B""",'C'," D"] * отредактированный как "" B "" действительно недопустим

Некоторое использование """ "или" бежать "или" или оба внутри.

Я пробовал различные решения из: Преобразовать строковое представление списка в список

s= u'''[ "A", """B""",'C' , " D"]'''

1)

import numpy as np
List = np.array(s).tolist()
print(List)
print(type(List))

Результат:

[ "A", """B""",'C' , " D"]
<class 'str'>

Так что не список, тогда:

2)

import ast
List = ast.literal_eval(s)
print(List)
print(type(List))

Результат

SyntaxError: cannot mix bytes and nonbytes literals

Любая другая идея, как это сделать? (Я использую Python 3.6)

Я думал о регулярных выражениях или простых заменах строк, но когда я вспоминаю все случаи, когда мы избегаем замены символов вроде ",'в элементах списка это очень быстро усложняется

1 Ответ

0 голосов
/ 11 июня 2018

Использование ast.literal_eval(s) прекрасно работает, если нет неэкранированных кавычек.Мы можем исправить это с помощью регулярных выражений.К сожалению, это не так просто.Тем не менее, я получил серебряную пулю для вас.

Сначала давайте определим все цитируемые подстроки, которые имеют внутренние кавычки одного типа:

(?<=[\[,])\s*(['\"])(?:(\1)|.)*?\1(?=\s*[,\]])

Пояснение

  • (?<=[\[,]) передний якорь с использованием lookbehind: начало списка или предыдущий элемент списка: [ или ,
  • \s* ноль или более пробелов
  • (['\"]) открывающая кавычка в группе захвата
  • (?:(\1)|.)*? ленивое совпадение с любым символом, если та же кавычка, что и в $1, вставьте его в $2
  • \1 закрывающая кавычка, используя обратнуюссылка
  • (?=\s*[,\]]) задний якорь с использованием указателя: , или ]

Демо

Теперь начинается настоящее веселье.Далее, ищите подстроки, которые имеют внутренние кавычки одного и того же типа для неэкранированных кавычек.

Для этого я использую подход Skip_what_to_avoid с некоторой продвинутой техникой регулярных выражений, такой как развернутый цикл .

^"[^"\\]*(?:\\.[^\"\\]*)*"$|^'[^'\\]*(?:\\.[^'\\]*)*'$|^\s*["'][^"'\\]*|["']\s*$|\\.|(["'])[^"'\\\n]*

Ключевой идеей здесь является полное игнорирование общих совпадений, возвращаемых механизмом регулярных выражений: $0 - это мусорное ведро.Вместо этого нам нужно только проверить группу захвата $1, которая, когда установлена, содержит то, что мы ищем.По сути, все эти незафиксированные чередования соответствуют обычным, правильно экранированным подстрокам, заключенным в кавычки.Нам нужны только неэкранированные.

Demo2

Я решил собрать исходную строку с необходимыми \, добавленными для правильного экранирования техне спасенные цитаты.Конечно, есть и другие способы.

Наконец, собираем вместе весь пример кода ( онлайн-демонстрация ):

import re
import ast

test = ("[ \"A\", \"\"B\"\",'C' , \" D\"]\n"
    "[ \"A\", \"'B'\",'C' , \" D\"]\n"
    "[ \"A\", ''B'','C' , \" D\"]\n"
    "[ \"A\", '\"B\"','C' , \" D\"]\n"
    "[ \"A\", '8 o'clock','C' , \" D\"]\n"
    "[ \"A\", \"Ol' 8 o'clock\",'C' , \" D\"]\n"
    "[\"Some Text\"]\n"
    "[\"Some more Text\"]\n"
    "[\"Even more text about \\\"this text\\\"\"]\n"
    "[\"Ol' 8 o'clock\"]\n"
    "['8 o'clock']\n"
    "[ '8 o'clock']\n"
    "['Ol' 8 o'clock']\n"
    "[\"\"B\"]\n"
    "[\"\\\"B\"]\n"
    "[\"\\\\\"B\"]\n"
    "[\"\\\\\\\"B\"]\n"
    "[\"\\\\\\\\\"B\"]")
result = u''
last_index = 0
regex1 = r"(?<=[\[,])\s*(['\"])(?:(\1)|.)*?\1(?=\s*[,\]])" #nested quotes of the same type
regex2 = r'''^"[^"\\]*(?:\\.[^\"\\]*)*"$|^'[^'\\]*(?:\\.[^'\\]*)*'$|^\s*["'][^"'\\]*|["']\s*$|\\.|(["'])[^"'\\\n]*''' # unescaped quotes in $1
matches = re.finditer(regex1, test, re.MULTILINE)
for match in matches:
    if match.groups()[1] is not None: #nested quotes of the same type present
        print(match.group())
        inner = re.finditer(regex2, match.group())
        for m in inner:            
            if m.groups()[0] is not None: # unescaped quotes in $1 present
                result += test[last_index:match.start() + m.start()] + '\\' + m.group()
                last_index = match.start()+m.end()
result += test[last_index:len(test)]
print(result)

for test_str in result.split("\n"):
    List = ast.literal_eval(test_str)
    print(List)
    print(type(List))

У нас есть тестовая строка, которая содержит несколько списков:строковый литерал со всеми видами особых случаев: разные и одинаковые типы кавычек, экранированные кавычки, экранированные экранирующие последовательности и т. д. Тестовая строка очищается с использованием описанного выше решения split, а затем индивидуально преобразуется в список с использованием literal_eval.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...