Как разделить, но игнорировать разделители в кавычках, в Python? - PullRequest
60 голосов
/ 07 мая 2010

Мне нужно разбить строку, например, на точки с запятой.Но я не хочу разбивать точки с запятой, которые находятся внутри строки ('или "). Я не анализирую файл; просто простая строка без разрывов строки.*

Результат должен быть:

  • часть 1
  • "это;часть 2; "
  • " это; часть 3 '
  • часть 4
  • это ";часть "5

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

Ответы [ 16 ]

49 голосов
/ 07 мая 2010

Большинство ответов кажутся слишком сложными. Вам не нужны обратные ссылки. Вы не должны зависеть от того, дает ли re.findall перекрывающиеся совпадения. Принимая во внимание, что входные данные не могут быть проанализированы с помощью модуля csv, поэтому регулярное выражение - это, пожалуй, единственный путь, все, что вам нужно, это вызвать re.split с шаблоном, который соответствует полю.

Обратите внимание, что здесь намного проще сопоставить поле, чем сопоставить разделитель:

import re
data = """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
PATTERN = re.compile(r'''((?:[^;"']|"[^"]*"|'[^']*')+)''')
print PATTERN.split(data)[1::2]

и вывод:

['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5']

Как правильно заметил Жан-Люк Насиф Коэльо, это не будет правильно обрабатывать пустые группы. В зависимости от ситуации, которая может иметь или не иметь значения. Если это имеет значение, возможно, можно будет обработать его, например, заменив ';;' на ';<marker>;', где <marker> должна быть некоторой строкой (без точек с запятой), которую, как вы знаете, не будет в данных до разделения , Также вам необходимо восстановить данные после:

>>> marker = ";!$%^&;"
>>> [r.replace(marker[1:-1],'') for r in PATTERN.split("aaa;;aaa;'b;;b'".replace(';;', marker))[1::2]]
['aaa', '', 'aaa', "'b;;b'"]

Однако это клудж. Есть лучшие предложения?

28 голосов
/ 07 мая 2010
re.split(''';(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', data)

Каждый раз, когда он находит точку с запятой, просмотрщик просматривает всю оставшуюся строку, проверяя наличие четного числа одинарных кавычек и четного числа двойных кавычек.(Одиночные кавычки внутри полей в двойных кавычках или наоборот игнорируются.) Если предварительный просмотр завершается успешно, точка с запятой является разделителем.

В отличие от решение Дункана , соответствующее полямвместо разделителей, у этого нет проблем с пустыми полями.(Даже не последний: в отличие от многих других реализаций split, Python не сбрасывает автоматически завершающие пустые поля.)

16 голосов
/ 23 февраля 2016
>>> a='A,"B,C",D'
>>> a.split(',')
['A', '"B', 'C"', 'D']

It failed. Now try csv module
>>> import csv
>>> from StringIO import StringIO
>>> data = StringIO(a)
>>> data
<StringIO.StringIO instance at 0x107eaa368>
>>> reader = csv.reader(data, delimiter=',') 
>>> for row in reader: print row
... 
['A,"B,C",D']
11 голосов
/ 07 мая 2010

Вот аннотированный pyparsing подход:

from pyparsing import (printables, originalTextFor, OneOrMore, 
    quotedString, Word, delimitedList)

# unquoted words can contain anything but a semicolon
printables_less_semicolon = printables.replace(';','')

# capture content between ';'s, and preserve original text
content = originalTextFor(
    OneOrMore(quotedString | Word(printables_less_semicolon)))

# process the string
print delimitedList(content, ';').parseString(test)

давая

['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 
 'this "is ; part" 5']

Используя предоставленное pyparsing quotedString, вы также получаете поддержку экранированных кавычек.

Вам также было непонятно, как обрабатывать начальные пробелы до или после точки с запятой, и ни одно из ваших полей в образце не содержит никаких полей. Pyparsing будет анализировать "a; b; c" как:

['a', 'b', 'c']
9 голосов
/ 07 мая 2010

У вас, кажется, есть строка, разделенная точкой с запятой.Почему бы не использовать модуль csv для выполнения всей тяжелой работы?

С макушки головы это должно работать

import csv 
from StringIO import StringIO 

line = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''

data = StringIO(line) 
reader = csv.reader(data, delimiter=';') 
for row in reader: 
    print row 

Это должно дать вам что-то вроде
("part 1", "this is ; part 2;", 'this is ; part 3', "part 4", "this \"is ; part\" 5")

Редактировать:
К сожалению, это не совсем работает (даже если вы используете StringIO, как я и предполагал) из-за смешанных кавычек (оба одинарныеи двойной).На самом деле вы получите

['part 1', 'this is ; part 2;', "'this is ", " part 3'", 'part 4', 'this "is ', ' part" 5'].

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

3 голосов
/ 07 мая 2010
>>> x = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''
>>> import re
>>> re.findall(r'''(?:[^;'"]+|'(?:[^']|\\.)*'|"(?:[^']|\\.)*")+''', x)
['part 1', "this is ';' part 2", "'this is ; part 3'", 'part 4', 'this "is ; part" 5']
3 голосов
/ 07 мая 2010

Хотя это может быть сделано с помощью PCRE через lookaheads / behinds / backreferences, на самом деле это не та задача, для которой разработано регулярное выражение из-за необходимости сопоставлять сбалансированные пары кавычек.просто создайте мини-конечный автомат и проанализируйте строку следующим образом.

Edit

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

Однако, если вам интересно, как может выглядеть реализация без регулярных выражений:

x = """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""

results = [[]]
quote = None
for c in x:
  if c == "'" or c == '"':
    if c == quote:
      quote = None
    elif quote == None:
      quote = c
  elif c == ';':
    if quote == None:
      results.append([])
      continue
  results[-1].append(c)

results = [''.join(x) for x in results]

# results = ['part 1', '"this is ; part 2;"', "'this is ; part 3'",
#            'part 4', 'this "is ; part" 5']
2 голосов
/ 16 июня 2017

мы можем создать собственную функцию

def split_with_commas_outside_of_quotes(string):
    arr = []
    start, flag = 0, False
    for pos, x in enumerate(string):
        if x == '"':
            flag= not(flag)
        if flag == False and x == ',':
            arr.append(string[start:pos])
            start = pos+1
    arr.append(string[start:pos])
    return arr
1 голос
/ 07 мая 2010

, поскольку у вас нет '\ n', используйте его для замены любого ';' это не в строке кавычки

>>> new_s = ''
>>> is_open = False

>>> for c in s:
...     if c == ';' and not is_open:
...         c = '\n'
...     elif c in ('"',"'"):
...         is_open = not is_open
...     new_s += c

>>> result = new_s.split('\n')

>>> result
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ; part" 5']
1 голос
/ 07 мая 2010

Это регулярное выражение сделает это: (?:^|;)("(?:[^"]+|"")*"|[^;]*)

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