it-swarm-eu.dev

Rozdělte řetězec mezerami - zachováním citovaných podřetězců - v Pythonu

Mám řetězec, který je takto:

this is "a test"

Snažím se psát něco v Pythonu, abych ho rozdělil do prostoru a zároveň ignoroval mezery v uvozovkách. Výsledek, který hledám, je:

['this','is','a test']

PS. Vím, že se budete ptát "co se stane, když jsou citace v uvozovkách, dobře, v mé žádosti, že se to nikdy nestane.".

223
Adam Pierce

Chcete rozdělit modul shlex /.

>>> import shlex
>>> shlex.split('this is "a test"')
['this', 'is', 'a test']

To by mělo dělat přesně to, co chcete.

342
Jerub

Podívejte se na modul shlex, zejména shlex.split.

>>> import shlex
>>> shlex.split('This is "a test"')
['This', 'is', 'a test']
54
Allen

Vidím zde regex přístupy, které vypadají složitě a/nebo špatně. To mě překvapuje, protože syntaxe regexu může snadno popsat "mezery nebo věci obklopené citáty" a většina regex motorů (včetně Pythonu) se může rozdělit na regex. Pokud tedy chcete použít regexes, proč neřeknout přesně, co máte na mysli ?:

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()]

Vysvětlení:

[\\\"'] = double-quote or single-quote
.* = anything
( |X) = space or X
.strip() = remove space and empty-string separators

shlex pravděpodobně poskytuje více funkcí.

31
Kate

V závislosti na vašem případu použití se můžete také podívat na modul csv:

import csv
lines = ['this is "a string"', 'and more "stuff"']
for row in csv.reader(lines, delimiter=" "):
    print row

Výstup: 

['this', 'is', 'a string']
['and', 'more', 'stuff']
23
Ryan Ginstrom

Používám shlex.split zpracovat 70.000.000 řádků chobotnice log, je to tak pomalé. Tak jsem přešel na re.

Prosím, zkuste to, pokud máte problém s výkonem shlexu.

import re

def line_split(line):
    return re.findall(r'[^"\s]\S*|".+?"', line)
12
Daniel Dai

Vzhledem k tomu, že tato otázka je označena regex, rozhodla jsem se vyzkoušet regex přístup. Nejprve nahradím všechny mezery v částech uvozovek pomocí x00, pak rozdělím mezerami, pak nahradí x00 na mezery v každé části.

Obě verze dělají totéž, ale rozdělovač je trochu čitelnější než 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)
8
gooli

Chcete-li uchovat uvozovky, použijte tuto funkci:

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
3
THE_MAD_KING

Rychlostní test různých odpovědí:

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
2
har777

Zdá se, že z důvodů výkonu je re rychlejší. Zde je mé řešení s použitím nejméně chamtivého operátora, který zachovává vnější citace:

re.findall("(?:\".*?\"|\S)+", s)

Výsledek:

['this', 'is', '"a test"']

Ponechává konstrukty jako aaa"bla blub"bbb dohromady, protože tyto tokeny nejsou odděleny mezerami. Pokud řetězec obsahuje uniklé znaky, můžete se shodnout takto:

>>> 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.\""

Vezměte prosím na vědomí, že toto také odpovídá prázdnému řetězci "" pomocí \S části vzoru.

2
hochl

Chcete-li obejít problémy s unicode v některých verzích Pythonu 2, doporučuji:

from shlex import split as _split
split = lambda a: [b.decode('utf-8') for b in _split(a.encode('utf-8'))]
2
moschlar

Hlavním problémem s akceptovaným přístupem shlex je, že neignoruje únikové znaky mimo citované podřetězce a v některých rohových případech poskytuje mírně neočekávané výsledky.

Mám následující případ použití, kde potřebuji rozdělení funkce, která rozděluje vstupní řetězce tak, že jsou zachovány buď jednokrokové nebo dvojité kóty, s možností uniknout uvozovkám v takovém podřetězci. Citáty v rámci nekótovaného řetězce by neměly být zpracovávány odlišně od jiných znaků. Některé příklady testovacích případů s očekávaným výstupem:

  vstupní řetězec | očekávaný výstup 
 ====================== ==== 
 '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\t 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 $ '"]

Skončil jsem s následující funkcí rozdělit řetězec tak, že očekávaný výstup výsledky pro všechny vstupní řetězce:

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)]

Následující testovací aplikace kontroluje výsledky jiných přístupů (nyní shlex a csv) a implementace vlastního rozdělení:

#!/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 __== '__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)')

Výstup:

 
 [OK] abc def -> ['abc', 'def'] 
 [FAIL] abc 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\t : Žádná uzavírací citace 
 [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 -> výjimka: Žádná závěrečná citace 
 [FAIL] abc'def' -> ['abcdef'] 
 [FAIL ] abc'def 'ghi -> [' abcdef ',' ghi '] 
 [FAIL] abc'def'ghi -> [' abcdefghi '] 
 [FAIL] abc "def -> výjimka: Bez závěrečné citace 
 [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 ',' de f '] 
 [OK] abc je def -> [' abc ',' s ',' def '] 
 [OK] "abc def" ghi -> [' abc def ',' ghi '] 
 [FAIL]' abc def 'ghi -> ["' abc", "def '",' ghi '] 
 [FAIL] "abc"\t "ghi -> ['abc', 'def' ',' ghi '] 
 [FAIL]' abc 'def' ghi -> [" 'abc ","\t def '', 'ghi'] 
 [FAIL] '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 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 na iteraci 
 csv: 0,030ms na iteraci 
 re: 0,049ms na iteraci

Takže výkon je mnohem lepší než shlex a může být dále vylepšen předkompilováním regulárního výrazu, v takovém případě překoná přístup csv.

1

Problémy s unicode se shlexem diskutovaným výše (top answer) se zdají být vyřešeny (nepřímo) v 2.7.2+ podle http://bugs.python.org/issue6988#msg146200

(samostatná odpověď, protože nemohu komentovat)

1
Tyris

Hmm, nemůžete najít tlačítko "Odpovědět" ... přesto je tato odpověď založena na přístupu Kate, ale správně rozděluje řetězce s podřetězci obsahujícími uniklé uvozovky a také odstraňuje úvodní a koncové uvozovky podřetězců:

  [i.strip('"').strip("'") for i in re.split(r'(\s+|(?<!\\)".*?(?<!\\)"|(?<!\\)\'.*?(?<!\\)\')', string) if i.strip()]

To funguje na řetězcích jako 'This is " a \\\"test\\\"\\\'s substring"' (šílené značení je bohužel nutné, aby Python nemohl odstranit úniky).

Pokud výsledné úniky v řetězcích v vráceném seznamu nejsou požadovány, můžete použít tuto mírně změněnou verzi funkce:

[i.strip('"').strip("'").decode('string_escape') for i in re.split(r'(\s+|(?<!\\)".*?(?<!\\)"|(?<!\\)\'.*?(?<!\\)\')', string) if i.strip()]
1
user261478

Navrhuji:

testovací řetězec:

s = 'abc "ad" \'fg\' "kk\'rdt\'" zzz"34"zzz "" \'\''

zachytit také "" a "":

import re
re.findall(r'"[^"]*"|\'[^\']*\'|[^"\'\s]+',s)

výsledek:

['abc', '"ad"', "'fg'", '"kk\'rdt\'"', 'zzz', '"34"', 'zzz', '""', "''"]

ignorovat prázdné "" a "":

import re
re.findall(r'"[^"]+"|\'[^\']+\'|[^"\'\s]+',s)

výsledek:

['abc', '"ad"', "'fg'", '"kk\'rdt\'"', 'zzz', '"34"', 'zzz']
0
hussic