У меня есть такая строка:
this is "a test"
Я пытаюсь написать что-то на Python, чтобы разделить его по пробелам, игнорируя пробелы в кавычках. Результат, который я ищу:
['this','is','a test']
PS. Я знаю, вы спросите: «Что произойдет, если в кавычках есть кавычки, ну, в моем приложении этого никогда не произойдет.






Вам нужен split из встроенного модуля shlex.
>>> import shlex
>>> shlex.split('this is "a test"')
['this', 'is', 'a test']
Это должно делать именно то, что вы хотите.
@MatthewG. «Исправление» в Python 2.7.3 означает, что передача строки Unicode в shlex.split() вызовет исключение UnicodeEncodeError.
Обратите внимание на модуль shlex, особенно на shlex.split.
>>> import shlex
>>> shlex.split('This is "a test"')
['This', 'is', 'a test']
Вау, впечатляет. Вы написали сообщение одновременно с @Jerub. И через 2 минуты после вопроса!
Попробуй это:
def adamsplit(s):
result = []
inquotes = False
for substring in s.split('"'):
if not inquotes:
result.extend(substring.split())
else:
result.append(substring)
inquotes = not inquotes
return result
Некоторые тестовые строки:
'This is "a test"' -> ['This', 'is', 'a test']
'"This is \'a test\'"' -> ["This is 'a test'"]
Пожалуйста, укажите повтор строки, которая, по вашему мнению, не будет выполнена.
adamsplit("This is 'a test'") → ['This', 'is', "'a", "test'"]OP говорит только «в кавычках» и имеет только пример с двойными кавычками.
Если вас не интересуют подстроки, чем простой
>>> 'a short sized string with spaces '.split()
Спектакль:
>>> s = " ('a short sized string with spaces '*100).split() "
>>> t = timeit.Timer(stmt=s)
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
171.39 usec/pass
Или строковый модуль
>>> from string import split as stringsplit;
>>> stringsplit('a short sized string with spaces '*100)
Производительность: похоже, строковый модуль работает лучше, чем строковые методы
>>> s = "stringsplit('a short sized string with spaces '*100)"
>>> t = timeit.Timer(s, "from string import split as stringsplit")
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
154.88 usec/pass
Или вы можете использовать двигатель RE
>>> from re import split as resplit
>>> regex = '\s+'
>>> medstring = 'a short sized string with spaces '*100
>>> resplit(regex, medstring)
Спектакль
>>> s = "resplit(regex, medstring)"
>>> t = timeit.Timer(s, "from re import split as resplit; regex='\s+'; medstring='a short sized string with spaces '*100")
>>> print "%.2f usec/pass" % (1000000 * t.timeit(number=100000)/100000)
540.21 usec/pass
Для очень длинных строк не следует загружать всю строку в память, а вместо этого либо разделять строки, либо использовать итерационный цикл.
Похоже, вы упустили суть вопроса. В строке есть разделы в кавычках, которые не нужно разделять.
Поскольку этот вопрос помечен регулярным выражением, я решил попробовать подход с регулярным выражением. Сначала я заменяю все пробелы в частях кавычек на \ x00, затем разделяю пробелами, а затем заменяю \ x00 обратно на пробелы в каждой части.
Обе версии делают то же самое, но splitter немного более читаем, чем splitter2.
import re
s = 'this is "a test" some text "another test"'
def splitter(s):
def replacer(m):
return m.group(0).replace(" ", "\x00")
parts = re.sub('".+?"', replacer, s).split()
parts = [p.replace("\x00", " ") for p in parts]
return parts
def splitter2(s):
return [p.replace("\x00", " ") for p in re.sub('".+?"', lambda m: m.group(0).replace(" ", "\x00"), s).split()]
print splitter2(s)
Вместо этого вам следовало использовать re.Scanner. Это более надежно (и я фактически реализовал шлекс-подобный с помощью re.Scanner).
+1 Хм, это довольно умная идея - разбить проблему на несколько этапов, чтобы ответ не был слишком сложным. Shlex не сделал именно то, что мне нужно, даже при попытке настроить его. И решения с однопроходным регулярным выражением становились действительно странными и сложными.
Я вижу здесь подходы с регулярными выражениями, которые выглядят сложными и / или неправильными. Это меня удивляет, потому что синтаксис регулярных выражений может легко описывать «пробелы или объекты, заключенные в кавычки», а большинство механизмов регулярных выражений (включая Python) могут разделяться на регулярное выражение. Итак, если вы собираетесь использовать регулярные выражения, почему бы просто не сказать именно то, что вы имеете в виду ?:
test = 'this is "a test"' # or "this is 'a test'"
# pieces = [p for p in re.split("( |[\\"'].*[\\"'])", test) if p.strip()]
# From comments, use this:
pieces = [p for p in re.split("( |\\".*?\\"|'.*?')", test) if p.strip()]
Объяснение:
[\\"'] = double-quote or single-quote
.* = anything
( |X) = space or X
.strip() = remove space and empty-string separators
Однако shlex, вероятно, предоставляет больше возможностей.
Я думал примерно так же, но предлагал вместо t [t.strip ('"') для t в re.findall (r '[^ \ s"] + | "[^"] * "', 'this is" тест"')]
Что делает это разделение, когда в двойных кавычках есть апострофы: Он сказал: «Не делай этого!» Я думаю, он будет рассматривать <"Дон" как единое целое, не так ли?
Джонатан: в данном случае нет, я сделал две ошибки, которые в этом случае нивелируют друг друга: жадный. * Выйдет в финал ". :-) Я должен был сказать" (| \\\ ". *? \ \\ "| '. *?')". Хороший улов.
+1 Я использую это, потому что это было чертовски быстрее, чем shlex.
+1 от меня, второе регулярное выражение (комментарии) работает для моих нужд, а первое - нет. Таким образом, я редактировал второе регулярное выражение, но оставил первое легко видимым.
P.S. это отлично, мне не нужны функции shlex, просто сплит, как argv. Я бы дал +2, если бы мог.
этот код почти похож на Perl, разве вы не слышали о «сырых строках»?
Рассмотрим эти данные: string = r'simple "quot ed" "игнорировать escape-последовательность в кавычках \\" "howboutthemapostrophe \ 's?" "\" withescapedquotes \ "" "\" с несбалансированными экранированными кавычками "'Обновление Jonathan / Kate / Ninefingers разбивает термин withescapedquotes на три (дегенеративные-одиночные цитаты, withescapedquotes, другие-вырожденные). в порядке. Это можно сделать через re?
Почему тройная обратная косая черта? Разве простая обратная косая черта не сделает то же самое?
Этот обрабатывает несбалансированные кавычки и юникод, а shlex - нет :(
+1 Мне нравится этот ответ, потому что он фактически сохраняет цитаты, в отличие от Шлекса. Сплит Shlex должен делать только расщепление, он не должен удалять цитаты обо мне. Хотя, возможно, это настраивается.
На самом деле, что мне не нравится в этом, так это то, что все, что до / после кавычек, не разделяется должным образом. Если у меня есть такая строка, как "PARAMS val1 =" Thing "val2 =" Thing2 "'. Я ожидаю, что строка разделится на три части, но она разделится на 5. Прошло много времени с тех пор, как я делал регулярные выражения, поэтому я не чувствую, что сейчас пытаюсь решить эту проблему с помощью вашего решения.
При использовании регулярных выражений следует использовать необработанные строки.
Он обрабатывает оба типа кавычек и удаляет только проанализированный: [''.join(t) for t in re.findall(r"""([^\s"']+)|"([^"]*)"|'([^']*)'""", test)]
Чтобы использовать любой разделитель (не только пробелы) и исправить проблему неправильного разделения до / после кавычек, посмотрите здесь stackoverflow.com/a/56791724/1201614
Может ли кто-нибудь расширить данное «объяснение»? Мне достаточно удобно с регулярным выражением, но я не понял «объяснения». Может ли кто-нибудь предоставить здесь более подробную информацию? Похоже, это было бы моим предпочтительным решением, за исключением того, что мне это непонятно.
В зависимости от вашего варианта использования вы также можете проверить модуль csv:
import csv
lines = ['this is "a string"', 'and more "stuff"']
for row in csv.reader(lines, delimiter = " "):
print(row)
Выход:
['this', 'is', 'a string']
['and', 'more', 'stuff']
полезно, когда shlex удаляет некоторые необходимые символы
CSV используйте две двойные кавычки подряд (как рядом, "") для представления одной двойной кавычки ", поэтому превратит две двойные кавычки в одинарную кавычку 'this is "a string""' и 'this is "a string"""' будут отображаться на ['this', 'is', 'a string"']
Хм, похоже, не удается найти кнопку «Ответить» ... в любом случае, этот ответ основан на подходе Кейт, но правильно разбивает строки на подстроки, содержащие экранированные кавычки, а также удаляет начальные и конечные кавычки подстрок:
[i.strip('"').strip("'") for i in re.split(r'(\s+|(?<!\)".*?(?<!\)"|(?<!\)\'.*?(?<!\)\')', string) if i.strip()]
Это работает со строками, такими как 'This is " a \\"test\\"\\'s substring"' (безумная разметка, к сожалению, необходима, чтобы Python не удалял escape-последовательности).
Если результирующие escape-последовательности в строках в возвращаемом списке не нужны, вы можете использовать эту слегка измененную версию функции:
[i.strip('"').strip("'").decode('string_escape') for i in re.split(r'(\s+|(?<!\)".*?(?<!\)"|(?<!\)\'.*?(?<!\)\')', string) if i.strip()]
Чтобы обойти проблемы с Unicode в некоторых версиях Python 2, я предлагаю:
from shlex import split as _split
split = lambda a: [b.decode('utf-8') for b in _split(a.encode('utf-8'))]
Для python 2.7.5 это должно быть: split = lambda a: [b.decode('utf-8') for b in _split(a)], иначе вы получите: UnicodeDecodeError: 'ascii' codec can't decode byte ... in position ...: ordinal not in range(128)
Я использую shlex.split для обработки 70 000 000 строк журнала Squid, он такой медленный. Так что я перешел на re.
Пожалуйста, попробуйте это, если у вас проблемы с производительностью с shlex.
import re
def line_split(line):
return re.findall(r'[^"\s]\S*|".+?"', line)
Я предлагаю:
тестовая строка:
s = 'abc "ad" \'fg\' "kk\'rdt\'" zzz"34"zzz "" \'\''
захватить также "" и "':
import re
re.findall(r'"[^"]*"|\'[^\']*\'|[^"\'\s]+',s)
результат:
['abc', '"ad"', "'fg'", '"kk\'rdt\'"', 'zzz', '"34"', 'zzz', '""', "''"]
игнорировать пустые "" и "':
import re
re.findall(r'"[^"]+"|\'[^\']+\'|[^"\'\s]+',s)
результат:
['abc', '"ad"', "'fg'", '"kk\'rdt\'"', 'zzz', '"34"', 'zzz']
Также может быть написано как re.findall("(?:\".*?\"|'.*?'|[^\s'\"]+)", s).
Для сохранения кавычек используйте эту функцию:
def getArgs(s):
args = []
cur = ''
inQuotes = 0
for char in s.strip():
if char == ' ' and not inQuotes:
args.append(cur)
cur = ''
elif char == '"' and not inQuotes:
inQuotes = 1
cur += char
elif char == '"' and inQuotes:
inQuotes = 0
cur += char
else:
cur += char
args.append(cur)
return args
При сравнении с большей строкой ваша функция настолько медленная
Тест скорости разных ответов:
import re
import shlex
import csv
line = 'this is "a test"'
%timeit [p for p in re.split("( |\\".*?\\"|'.*?')", line) if p.strip()]
100000 loops, best of 3: 5.17 µs per loop
%timeit re.findall(r'[^"\s]\S*|".+?"', line)
100000 loops, best of 3: 2.88 µs per loop
%timeit list(csv.reader([line], delimiter = " "))
The slowest run took 9.62 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 2.4 µs per loop
%timeit shlex.split(line)
10000 loops, best of 3: 50.2 µs per loop
Основная проблема с принятым подходом shlex заключается в том, что он не игнорирует escape-символы за пределами цитируемых подстрок и дает несколько неожиданные результаты в некоторых угловых случаях.
У меня есть следующий вариант использования, когда мне нужна функция разделения, которая разделяет входные строки таким образом, чтобы сохранялись подстроки в одинарных или двойных кавычках, с возможностью экранирования кавычек внутри такой подстроки. Кавычки внутри строки без кавычек не должны обрабатываться иначе, чем любой другой символ. Некоторые примеры тестовых случаев с ожидаемым результатом:
input string | expected output =============================================== 'abc def' | ['abc', 'def'] "abc \s def" | ['abc', '\s', 'def'] '"abc def" ghi' | ['abc def', 'ghi'] "'abc def' ghi" | ['abc def', 'ghi'] '"abc \" def" ghi' | ['abc " def', 'ghi'] "'abc \' def' ghi" | ["abc ' def", 'ghi'] "'abc \s def' ghi" | ['abc \s def', 'ghi'] '"abc \s def" ghi' | ['abc \s def', 'ghi'] '"" test' | ['', 'test'] "'' test" | ['', 'test'] "abc'def" | ["abc'def"] "abc'def'" | ["abc'def'"] "abc'def' ghi" | ["abc'def'", 'ghi'] "abc'def'ghi" | ["abc'def'ghi"] 'abc"def' | ['abc"def'] 'abc"def"' | ['abc"def"'] 'abc"def" ghi' | ['abc"def"', 'ghi'] 'abc"def"ghi' | ['abc"def"ghi'] "r'AA' r'.*_xyz$'" | ["r'AA'", "r'.*_xyz$'"]
В итоге я получил следующую функцию для разделения строки таким образом, чтобы ожидаемые выходные результаты были для всех входных строк:
import re
def quoted_split(s):
def strip_quotes(s):
if s and (s[0] == '"' or s[0] == "'") and s[0] == s[-1]:
return s[1:-1]
return s
return [strip_quotes(p).replace('\"', '"').replace("\'", "'") \
for p in re.findall(r'"(?:\.|[^"])*"|\'(?:\.|[^\'])*\'|[^\s]+', s)]
Следующее тестовое приложение проверяет результаты других подходов (на данный момент shlex и csv) и реализации пользовательского разделения:
#!/bin/python2.7
import csv
import re
import shlex
from timeit import timeit
def test_case(fn, s, expected):
try:
if fn(s) == expected:
print '[ OK ] %s -> %s' % (s, fn(s))
else:
print '[FAIL] %s -> %s' % (s, fn(s))
except Exception as e:
print '[FAIL] %s -> exception: %s' % (s, e)
def test_case_no_output(fn, s, expected):
try:
fn(s)
except:
pass
def test_split(fn, test_case_fn=test_case):
test_case_fn(fn, 'abc def', ['abc', 'def'])
test_case_fn(fn, "abc \s def", ['abc', '\s', 'def'])
test_case_fn(fn, '"abc def" ghi', ['abc def', 'ghi'])
test_case_fn(fn, "'abc def' ghi", ['abc def', 'ghi'])
test_case_fn(fn, '"abc \" def" ghi', ['abc " def', 'ghi'])
test_case_fn(fn, "'abc \' def' ghi", ["abc ' def", 'ghi'])
test_case_fn(fn, "'abc \s def' ghi", ['abc \s def', 'ghi'])
test_case_fn(fn, '"abc \s def" ghi', ['abc \s def', 'ghi'])
test_case_fn(fn, '"" test', ['', 'test'])
test_case_fn(fn, "'' test", ['', 'test'])
test_case_fn(fn, "abc'def", ["abc'def"])
test_case_fn(fn, "abc'def'", ["abc'def'"])
test_case_fn(fn, "abc'def' ghi", ["abc'def'", 'ghi'])
test_case_fn(fn, "abc'def'ghi", ["abc'def'ghi"])
test_case_fn(fn, 'abc"def', ['abc"def'])
test_case_fn(fn, 'abc"def"', ['abc"def"'])
test_case_fn(fn, 'abc"def" ghi', ['abc"def"', 'ghi'])
test_case_fn(fn, 'abc"def"ghi', ['abc"def"ghi'])
test_case_fn(fn, "r'AA' r'.*_xyz$'", ["r'AA'", "r'.*_xyz$'"])
def csv_split(s):
return list(csv.reader([s], delimiter=' '))[0]
def re_split(s):
def strip_quotes(s):
if s and (s[0] == '"' or s[0] == "'") and s[0] == s[-1]:
return s[1:-1]
return s
return [strip_quotes(p).replace('\"', '"').replace("\'", "'") for p in re.findall(r'"(?:\.|[^"])*"|\'(?:\.|[^\'])*\'|[^\s]+', s)]
if __name__ == '__main__':
print 'shlex\n'
test_split(shlex.split)
print
print 'csv\n'
test_split(csv_split)
print
print 're\n'
test_split(re_split)
print
iterations = 100
setup = 'from __main__ import test_split, test_case_no_output, csv_split, re_split\nimport shlex, re'
def benchmark(method, code):
print '%s: %.3fms per iteration' % (method, (1000 * timeit(code, setup=setup, number=iterations) / iterations))
benchmark('shlex', 'test_split(shlex.split, test_case_no_output)')
benchmark('csv', 'test_split(csv_split, test_case_no_output)')
benchmark('re', 'test_split(re_split, test_case_no_output)')
Выход:
shlex [ OK ] abc def -> ['abc', 'def'] [FAIL] abc \s def -> ['abc', 's', 'def'] [ OK ] "abc def" ghi -> ['abc def', 'ghi'] [ OK ] 'abc def' ghi -> ['abc def', 'ghi'] [ OK ] "abc \" def" ghi -> ['abc " def', 'ghi'] [FAIL] 'abc \' def' ghi -> exception: No closing quotation [ OK ] 'abc \s def' ghi -> ['abc \s def', 'ghi'] [ OK ] "abc \s def" ghi -> ['abc \s def', 'ghi'] [ OK ] "" test -> ['', 'test'] [ OK ] '' test -> ['', 'test'] [FAIL] abc'def -> exception: No closing quotation [FAIL] abc'def' -> ['abcdef'] [FAIL] abc'def' ghi -> ['abcdef', 'ghi'] [FAIL] abc'def'ghi -> ['abcdefghi'] [FAIL] abc"def -> exception: No closing quotation [FAIL] abc"def" -> ['abcdef'] [FAIL] abc"def" ghi -> ['abcdef', 'ghi'] [FAIL] abc"def"ghi -> ['abcdefghi'] [FAIL] r'AA' r'.*_xyz$' -> ['rAA', 'r.*_xyz$'] csv [ OK ] abc def -> ['abc', 'def'] [ OK ] abc \s def -> ['abc', '\s', 'def'] [ OK ] "abc def" ghi -> ['abc def', 'ghi'] [FAIL] 'abc def' ghi -> ["'abc", "def'", 'ghi'] [FAIL] "abc \" def" ghi -> ['abc \', 'def"', 'ghi'] [FAIL] 'abc \' def' ghi -> ["'abc", "\'", "def'", 'ghi'] [FAIL] 'abc \s def' ghi -> ["'abc", '\s', "def'", 'ghi'] [ OK ] "abc \s def" ghi -> ['abc \s def', 'ghi'] [ OK ] "" test -> ['', 'test'] [FAIL] '' test -> ["''", 'test'] [ OK ] abc'def -> ["abc'def"] [ OK ] abc'def' -> ["abc'def'"] [ OK ] abc'def' ghi -> ["abc'def'", 'ghi'] [ OK ] abc'def'ghi -> ["abc'def'ghi"] [ OK ] abc"def -> ['abc"def'] [ OK ] abc"def" -> ['abc"def"'] [ OK ] abc"def" ghi -> ['abc"def"', 'ghi'] [ OK ] abc"def"ghi -> ['abc"def"ghi'] [ OK ] r'AA' r'.*_xyz$' -> ["r'AA'", "r'.*_xyz$'"] re [ OK ] abc def -> ['abc', 'def'] [ OK ] abc \s def -> ['abc', '\s', 'def'] [ OK ] "abc def" ghi -> ['abc def', 'ghi'] [ OK ] 'abc def' ghi -> ['abc def', 'ghi'] [ OK ] "abc \" def" ghi -> ['abc " def', 'ghi'] [ OK ] 'abc \' def' ghi -> ["abc ' def", 'ghi'] [ OK ] 'abc \s def' ghi -> ['abc \s def', 'ghi'] [ OK ] "abc \s def" ghi -> ['abc \s def', 'ghi'] [ OK ] "" test -> ['', 'test'] [ OK ] '' test -> ['', 'test'] [ OK ] abc'def -> ["abc'def"] [ OK ] abc'def' -> ["abc'def'"] [ OK ] abc'def' ghi -> ["abc'def'", 'ghi'] [ OK ] abc'def'ghi -> ["abc'def'ghi"] [ OK ] abc"def -> ['abc"def'] [ OK ] abc"def" -> ['abc"def"'] [ OK ] abc"def" ghi -> ['abc"def"', 'ghi'] [ OK ] abc"def"ghi -> ['abc"def"ghi'] [ OK ] r'AA' r'.*_xyz$' -> ["r'AA'", "r'.*_xyz$'"] shlex: 0.281ms per iteration csv: 0.030ms per iteration re: 0.049ms per iteration
Таким образом, производительность намного лучше, чем у shlex, и ее можно дополнительно улучшить, предварительно скомпилировав регулярное выражение, и в этом случае он превзойдет подход csv.
Не уверен, о чем вы говорите: `` `>>> shlex.split ('это" тест ") [' это ',' is ',' тест '] >>> shlex.split (' this is \\ "тест \\" ') [' this ',' is ',' "a ',' test" '] >>> shlex.split (' this is "\\" test \\ " "') [' this ',' is ',' a" test "']` ``
@morsik, о чем ты? Может быть, ваш вариант использования не соответствует моему? Когда вы посмотрите на тестовые примеры, вы увидите все случаи, когда shlex ведет себя не так, как ожидалось для моих вариантов использования.
Похоже, что по соображениям производительности re быстрее. Вот мое решение с использованием наименее жадного оператора, сохраняющего внешние кавычки:
re.findall("(?:\".*?\"|\S)+", s)
Результат:
['this', 'is', '"a test"']
Он оставляет вместе такие конструкции, как aaa"bla blub"bbb, поскольку эти токены не разделены пробелами. Если строка содержит экранированные символы, вы можете сопоставить это так:
>>> a = "She said \"He said, \\"My name is Mark.\\"\""
>>> a
'She said "He said, \"My name is Mark.\""'
>>> for i in re.findall("(?:\".*?[^\\]\"|\S)+", a): print(i)
...
She
said
"He said, \"My name is Mark.\""
Обратите внимание, что это также соответствует пустой строке "" посредством части \S шаблона.
Еще одним важным преимуществом этого решения является его универсальность в отношении символа-разделителя (например, , через '(?:".*?"|[^,])+'). То же самое относится к кавычкам (заключительным) символам.
Как вариант попробуйте tssplit:
In [1]: from tssplit import tssplit
In [2]: tssplit('this is "a test"', quote='"', delimiter='')
Out[2]: ['this', 'is', 'a test']
Используйте "posix = False", чтобы сохранить цитаты.
shlex.split('this is "a test"', posix=False)возвращает['this', 'is', '"a test"']