source

Python에서 따옴표로 묶인 하위 문자열을 보존하는 공백으로 문자열 분할

factcode 2022. 9. 28. 00:05
반응형

Python에서 따옴표로 묶인 하위 문자열을 보존하는 공백으로 문자열 분할

다음과 같은 문자열이 있습니다.

this is "a test"

따옴표 안의 공간은 무시하고 공백으로 나누기 위해 Python으로 쓰려고 합니다.제가 원하는 결과는 다음과 같습니다.

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

추신. "제 어플리케이션에서 인용문 안에 그런 일이 없을 경우 어떻게 될까요?"라고 물을 것입니다.

싶다split, 내장 모듈로부터 취득합니다.

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

이 정도면 원하는 대로 할 수 있을 겁니다.

를 유지하고 따옴표를 붙이면 .posix=False

>>> shlex.split('this is "a test"', posix=False)
['this', 'is', '"a test"']

,그러면한번 보세요.shlex 「」, 「」의shlex.split.

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

여기에서는 regex 접근법이 복잡해 보이거나 잘못되어 보이는 것을 알 수 있습니다.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는 더 많은 기능을 제공할 수 있습니다.

사용 사례에 따라 모듈을 체크할 수도 있습니다.

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

7,000,000줄의 오징어 로그를 처리하기 위해 shlex.split을 사용하지만 처리 속도가 너무 느립니다.그래서 re로 바꿨어요.

만약 플렉스에 성능 문제가 있다면 이것을 사용해 보세요.

import re

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

이유로 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★」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어프로치는 따옴표로 둘러싸인 서브스트링 이외의 이스케이프 문자를 무시하지 않고 일부 코너 케이스에서 약간 예기치 않은 결과를 얻을 수 있습니다.

다음과 같은 사용 사례가 있습니다. 입력 문자열을 분할하여 단일 따옴표 또는 이중 따옴표 하위 문자열 내에서 따옴표를 피할 수 있는 기능이 필요합니다.따옴표로 묶지 않은 문자열 내의 따옴표는 다른 문자와 다르게 취급하지 마십시오.예상되는 출력을 사용한 테스트 케이스의 예를 다음에 나타냅니다.

input string | 예상되는 출력==============================================='def' | ['def', 'def']"def" | ['def', '\s', 'def']"hi def" ghi | ["hi def", "ghi"]"'ghi def' ghi" | ['ghi def', 'ghi']''def'' ghi' | ['def', 'ghi']'''def'' ghi'' | ['def', 'ghi']"'ghi\s def' ghi" | ["ghi\s def", "ghi"]" " " \s def " ghi " | [ " \s def " , ghi " ]'' test'' | ['', 'test']'' test'' | ['', 'test']"def" | ["def""]"def" | ["def""'def' ghi' | ['def'', 'ghi']'def'ghi' | ['def'ghi']'def' | ['def']'def' | ['def']'def' ghi' | ['def'', 'ghi']'def' ghi' | ['def' ghi']"r'AA'r"*_xyz$" | ["r"]AA', 'r'입니다.*_xyz$'"'def ghi' | ['def ghi']def ghi jkl | [def ghi jkl]'a'b c"d"e"f"g h' | ['a'b c"d"e"f"g h"]'c="ls /" type key" | ['c="ls /"", 'type', 'key']'def ghi' | 'def ghi' ]"c='ls /' type key" | ["c='ls /'", 'type', 'key']

모든 입력 문자열에 대해 예상되는 출력이 나오도록 문자열을 분할하는 다음 함수를 사용하게 되었습니다.

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]*)+|(?:[^\'\s]*\'(?:\\.|[^\'])*\'[^\'\s]*)+|[^\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$'"])
    test_case_fn(fn, 'abc"def ghi"', ['abc"def ghi"'])
    test_case_fn(fn, 'abc"def ghi""jkl"', ['abc"def ghi""jkl"'])
    test_case_fn(fn, 'a"b c"d"e"f"g h"', ['a"b c"d"e"f"g h"'])
    test_case_fn(fn, 'c="ls /" type key', ['c="ls /"', 'type', 'key'])
    test_case_fn(fn, "abc'def ghi'", ["abc'def ghi'"])
    test_case_fn(fn, "c='ls /' type key", ["c='ls /'", 'type', 'key'])

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]*)+|(?:[^\'\s]*\'(?:\\.|[^\'])*\'[^\'\s]*)+|[^\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)')

출력:

플렉스
[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] '테스트 -> ['', '테스트''][OK] '테스트 -> [ ] '테스트'][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] 'AA' 'r'*_xyz$' -> ['rAA', 'r'*_xyz$'][FAIL] abc 'def ghi' -> [ abcdef ghi ][FAIL] abc'def ghi'jkl' -> [abcdef ghijkl'][FAIL] a"b c"d"e"f"g h" -> ['ab cdefg h'][FAIL] c="ls /" type key -> ['c=ls /', 'type', 'key'][FAIL] abc'def ghi' -> [abcdef ghi'][FAIL] c='ls /' 입력 키 -> ['c=ls /', 'type', 'key']
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] '테스트 -> ['', '테스트''][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] 'AA' r'*_xyz$' -> ['r']AA', 'r'입니다.*_xyz$'"[FAIL] abc 'def ghi' -> ['abc'def', 'ghi'][FAIL] abc'def ghi'jkl' -> [abc'def, ghi'jkl][FAIL] a"b c"d"e"f"g h" -> ['a"b", "c"d"e"f"g", "h"][FAIL] c="ls /" type key -> ['c="ls", '/", 'type', 'key'][FAIL] abc'def ghi' -> ["abc'def", "ghi"][FAIL] c='ls /' 유형 키 -> ["c='ls", "/', 'type', 'key']
참조.
[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] '테스트 -> ['', '테스트''][OK] '테스트 -> [ ] '테스트'][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] 'AA' r'*_xyz$' -> ['r']AA', 'r'입니다.*_xyz$'"[OK] abc "def ghi" -> [ abc "def ghi" ][OK] abc'def ghi'jkl' -> [abc'def ghi'jkl][OK] a"b c"d"e"f"g h" -> ['a"b c"d"e"f"g h"][확인] c="ls /" type key -> ['c="ls /"", 'type', 'key'][OK] abc'def ghi' -> [abc'def ghi'][확인] c='ls /' 입력 키 -> ["c='ls /'', 'type', 'key']
shlex: 0.335ms/반복csv: 0.036ms/반복순서: 0.068ms/반복

퍼포먼스는 라라포 so보다 .shlex정규 컴파일함으로써 더 향상할 수 경우 은 「정규 표현」을 합니다.이 경우, 이 경우, 이 표현은 보다 뛰어난 퍼포먼스를 발휘합니다.csv★★★★★★ 。

이 질문에는 regex 태그가 붙어있기 때문에 regex 접근방식을 시도해 보기로 했습니다.먼저 따옴표 부분의 모든 공간을 \x00으로 교체한 후 공백으로 분할한 다음 \x00을 각 부분의 공백으로 다시 바꿉니다.

두 버전 모두 동일한 작업을 수행하지만 스플리터가 스플리터2보다 조금 더 읽기 쉽습니다.

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)

다양한 답변의 속도 테스트:

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

따옴표를 유지하려면 다음 함수를 사용합니다.

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

음, "응답" 버튼을 찾을 수 없습니다.어쨌든 이 답변은 Kate의 접근법에 기초하고 있지만, 는 이스케이프 따옴표를 포함한 서브스트링으로 스트링을 올바르게 분할하고 서브스트링의 시작 따옴표와 끝 따옴표도 삭제합니다.

  [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 버전에서 유니코드 문제를 해결하려면 다음을 권장합니다.

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

옵션으로 tssplit을 사용해 보십시오.

In [1]: from tssplit import tssplit
In [2]: tssplit('this is "a test"', quote='"', delimiter='')
Out[2]: ['this', 'is', 'a test']

제안:

테스트 문자열:

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

단순 문자열보다 하위 문자열에 관심이 없는 경우

>>> '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'"]

언급URL : https://stackoverflow.com/questions/79968/split-a-string-by-spaces-preserving-quoted-substrings-in-python

반응형