回答:
必要なのsplit
は、組み込みshlex
モジュールからです。
>>> import shlex
>>> shlex.split('this is "a test"')
['this', 'is', 'a test']
これはまさにあなたが望むことをするはずです。
shlex.split('this is "a test"', posix=False)
リターン['this', 'is', '"a test"']
shlex.split()
とUnicodeEncodeError
例外がトリガーされることを意味します。
ここでは、複雑かつ/または間違っている正規表現のアプローチが見られます。regex構文は「空白または引用符で囲まれたもの」を簡単に記述でき、ほとんどのregexエンジン(Pythonを含む)はregexで分割できるため、これは私を驚かせます。したがって、正規表現を使用する場合は、どういう意味か正確に言ってみませんか?:
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はより多くの機能を提供します。
ユースケースによっては、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']
""
一つの二重引用符を表す)"
、そう単一引用符の中に2つの二重引用符を向けるだろう'this is "a string""'
と'this is "a string"""'
するであろうマップの両方['this', 'is', 'a string"']
この質問は正規表現でタグ付けされているので、私は正規表現アプローチを試すことにしました。最初に、引用符部分のすべてのスペースを\ 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
高速であるようです。これは、外側の引用符を保持する最小の貪欲演算子を使用した私の解決策です。
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
。
,
経由'(?:".*?"|[^,])+'
)。同じことが引用(囲み)文字にも当てはまります。
受け入れられたshlex
アプローチの主な問題は、引用された部分文字列の外側のエスケープ文字を無視せず、いくつかのまれなケースでわずかに予期しない結果をもたらすことです。
次の使用例があります。単一引用符または二重引用符で囲まれた部分文字列が保持されるように入力文字列を分割する分割関数が必要です。このような部分文字列内で引用符をエスケープする機能があります。引用符で囲まれていない文字列内の引用符は、他の文字と異なる扱いをしないでください。予想される出力を含むテストケースの例:
入力文字列| 期待される出力 =============================================== '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'] '""テスト' | [''、 'テスト'] "''テスト" | [''、 'テスト'] "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->例外:終了の引用なし [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->例外:終了の引用なし [FAIL] abc'def '-> [' abcdef '] [FAIL] abc'def 'ghi-> [' abcdef '、' ghi '] [FAIL] abc'def'ghi-> ['abcdefghi'] [FAIL] abc "def->例外:終了の引用なし [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] ''テスト-> ["''"、 'テスト'] [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 $'"] 再 [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 csv:反復あたり0.030ms 再:0.049ms /反復
したがって、パフォーマンスはよりもはるかに優れておりshlex
、正規表現をプリコンパイルすることでさらに向上させることができますcsv
。この場合、このアプローチの方が優れています。
shlex
が私のユースケースで期待どおりに動作しないすべてのケースがわかります。
引用符を保持するには、次の関数を使用します:
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
うーん、「返信」ボタンが見つからないようです...とにかく、この回答はケイトのアプローチに基づいていますが、エスケープされた引用符を含む部分文字列で文字列を正しく分割し、部分文字列の開始引用符と終了引用符も削除します。
[i.strip('"').strip("'") for i in re.split(r'(\s+|(?<!\\)".*?(?<!\\)"|(?<!\\)\'.*?(?<!\\)\')', string) if i.strip()]
これ'This is " a \\\"test\\\"\\\'s substring"'
は(Pythonがエスケープを削除しないようにするために、非常識なマークアップが残念ながら必要な)文字列に対して機能します。
返されたリストの文字列で結果として得られるエスケープが不要な場合は、関数を少し変更した次のバージョンを使用できます。
[i.strip('"').strip("'").decode('string_escape') for i in re.split(r'(\s+|(?<!\\)".*?(?<!\\)"|(?<!\\)\'.*?(?<!\\)\')', string) if i.strip()]
一部のPython 2バージョンでのUnicodeの問題を回避するには、次のことをお勧めします。
from shlex import split as _split
split = lambda a: [b.decode('utf-8') for b in _split(a.encode('utf-8'))]
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)
私は提案します:
テスト文字列:
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)
もできます。
単純な文字列よりも部分文字列を気にしない場合
>>> '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
非常に長い文字列の場合、文字列全体をメモリに読み込まず、行を分割するか、反復ループを使用する必要があります
これを試して:
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'"]