Development Tip

파이썬에서 따옴표로 묶인 문자열의 구분 기호를 분할하지만 무시하는 방법은 무엇입니까?

yourdevel 2020. 11. 30. 20:06
반응형

파이썬에서 따옴표로 묶인 문자열의 구분 기호를 분할하지만 무시하는 방법은 무엇입니까?


세미콜론으로 이와 같은 문자열을 분할해야합니다. 하지만 문자열 ( '또는 ") 안에있는 세미콜론으로 분할하고 싶지 않습니다. 파일을 구문 분석하는 것이 아니라 줄 바꿈이없는 단순한 문자열입니다.

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

결과는 다음과 같아야합니다.

  • 1 부
  • "이것은; 파트 2;"
  • '이것은 ; 파트 3 '
  • 4 부
  • 이것은 "이다; 부분"5

나는 이것이 정규식으로 할 수 있다고 생각하지만 그렇지 않다면; 나는 다른 접근 방식에 열려 있습니다.


대부분의 답변은 엄청나게 복잡해 보입니다. 당신은 하지 않습니다 다시 참조를해야합니다. 당신은 하지 않습니다 여부 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']

Jean-Luc Nacif Coelho가 올바르게 지적했듯이 이것은 빈 그룹을 올바르게 처리하지 않습니다. 상황에 따라 중요하거나 중요하지 않을 수 있습니다. 그건 문제가없는 경우는 교환, 예를 들면, 그것을 처리하는 것이 가능하다 ';;'';<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'"]

그러나 이것은 kludge입니다. 더 좋은 제안이 있습니까?


re.split(''';(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', data)

세미콜론을 찾을 때마다 미리보기는 나머지 문자열 전체를 스캔하여 짝수 개의 작은 따옴표와 짝수의 큰 따옴표가 있는지 확인합니다. (큰 따옴표가있는 필드 안의 작은 따옴표 또는 그 반대의 경우 무시됩니다.) 미리보기가 성공하면 세미콜론이 구분 기호입니다.

구분 기호가 아닌 필드와 일치하는 Duncan의 솔루션 과 달리이 솔루션 은 빈 필드에 문제가 없습니다. (마지막 것조차도 아닙니다. 다른 많은 split구현 과 달리 Python은 후행 빈 필드를 자동으로 삭제하지 않습니다.)


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

다음은 주석이 달린 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']

세미콜론으로 구분 된 문자열이있는 것 같습니다. 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'].

적절한 위치에 작은 따옴표 또는 큰 따옴표 만 포함하도록 데이터를 변경할 수 있다면 제대로 작동 할 것입니다. 그러나 그런 종류의 질문은 약간 부정적입니다.


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

lookaheads / behinds / backreferences를 통해 PCRE로 수행 할 수 있지만 실제로는 균형 잡힌 따옴표 쌍을 일치시켜야하기 때문에 정규식이 설계된 작업은 아닙니다.

대신 미니 상태 머신을 만들고 이와 같은 문자열을 구문 분석하는 것이 가장 좋습니다.

편집하다

결과적 re.findall으로 겹치지 않는 일치를 보장 하는 Python의 편리한 추가 기능으로 인해 다른 경우보다 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']

우리는 자체 기능을 만들 수 있습니다.

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

이 정규식은 다음을 수행합니다. (?:^|;)("(?:[^"]+|"")*"|[^;]*)


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

깨끗한 정규식 솔루션이 있다고 확신하지만 (지금까지 @noiflection의 대답을 좋아합니다), 여기에 빠르고 더러운 비 정규식 대답이 있습니다.

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

inQuotes = False
current = ""
results = []
currentQuote = ""
for c in s:
    if not inQuotes and c == ";":
        results.append(current)
        current = ""
    elif not inQuotes and (c == '"' or c == "'"):
        currentQuote = c
        inQuotes = True
    elif inQuotes and c == currentQuote:
        currentQuote = ""
        inQuotes = False
    else:
        current += c

results.append(current)

print results
# ['part 1', 'this is ; part 2;', 'this is ; part 3', 'part 4', 'this is ; part 5']

(나는 이런 종류의 것을 합친 적이 없습니다. 제 형태를 자유롭게 비평 해보세요!)


내 접근 방식은 세미콜론의 인용되지 않은 모든 항목을 텍스트에 나타나지 않는 다른 문자로 바꾼 다음 해당 문자로 분할하는 것입니다. 다음 코드는 re.sub 함수를 함수 인수와 함께 사용하여 srch작은 따옴표 나 큰 따옴표 또는 괄호, 대괄호 또는 중괄호로 묶이지 않은 모든 문자열 을 검색하고 문자열로 바꿉니다 repl.

def srchrepl(srch, repl, string):
    """
    Replace non-bracketed/quoted occurrences of srch with repl in string.
    """
    resrchrepl = re.compile(r"""(?P<lbrkt>[([{])|(?P<quote>['"])|(?P<sep>["""
                          + srch + """])|(?P<rbrkt>[)\]}])""")
    return resrchrepl.sub(_subfact(repl), string)


def _subfact(repl):
    """
    Replacement function factory for regex sub method in srchrepl.
    """
    level = 0
    qtflags = 0
    def subf(mo):
        nonlocal level, qtflags
        sepfound = mo.group('sep')
        if  sepfound:
            if level == 0 and qtflags == 0:
                return repl
            else:
                return mo.group(0)
        elif mo.group('lbrkt'):
            if qtflags == 0:
                level += 1
            return mo.group(0)
        elif mo.group('quote') == "'":
            qtflags ^= 1            # toggle bit 1
            return "'"
        elif mo.group('quote') == '"':
            qtflags ^= 2            # toggle bit 2
            return '"'
        elif mo.group('rbrkt'):
            if qtflags == 0:
                level -= 1
            return mo.group(0)
    return subf

대괄호 문자에 신경 쓰지 않는다면이 코드를 많이 단순화 할 수 있습니다.
파이프 또는 수직 막대를 대체 문자로 사용하고 싶다면 다음을 수행합니다.

mylist = srchrepl(';', '|', mytext).split('|')

BTW, 이것은 nonlocalPython 3.1에서 사용 하며 필요한 경우 전역으로 변경하십시오.


일반화 된 솔루션 :

import re
regex = '''(?:(?:[^{0}"']|"[^"]*(?:"|$)|'[^']*(?:'|$))+|(?={0}{0})|(?={0}$)|(?=^{0}))'''

delimiter = ';'
data2 = ''';field 1;"field 2";;'field;4';;;field';'7;'''
field = re.compile(regex.format(delimiter))
print(field.findall(data2))

출력 :

['', 'field 1', '"field 2"', '', "'field;4'", '', '', "field';'7", '']

이 솔루션 :

  • 모든 빈 그룹을 캡처합니다 (시작 및 끝 포함).
  • works for most popular delimiters including space, tab, and comma
  • treats quotes inside quotes of the other type as non-special characters
  • if an unmatched unquoted quote is encountered, treats the remainders of the line as quoted

Although the topic is old and previous answers are working well, I propose my own implementation of the split function in python.

This works fine if you don't need to process large number of strings and is easily customizable.

Here's my function:

# l is string to parse; 
# splitchar is the separator
# ignore char is the char between which you don't want to split

def splitstring(l, splitchar, ignorechar): 
    result = []
    string = ""
    ignore = False
    for c in l:
        if c == ignorechar:
            ignore = True if ignore == False else False
        elif c == splitchar and not ignore:
            result.append(string)
            string = ""
        else:
            string += c
    return result

So you can run:

line= """part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5"""
splitted_data = splitstring(line, ';', '"')

result:

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

The advantage is that this function works with empty fields and with any number of separators in the string.

Hope this helps!


Instead of splitting on a separator pattern, just capture whatever you need:

>>> import re
>>> data = '''part 1;"this is ; part 2;";'this is ; part 3';part 4;this "is ; part" 5'''
>>> re.findall(r';([\'"][^\'"]+[\'"]|[^;]+)', ';' + data)
['part 1', '"this is ; part 2;"', "'this is ; part 3'", 'part 4', 'this "is ', ' part" 5']

This seemed to me an semi-elegant solution.

New Solution:

import re
reg = re.compile('(\'|").*?\\1')
pp = re.compile('.*?;')
def splitter(string):
    #add a last semicolon
    string += ';'
    replaces = []
    s = string
    i = 1
    #replace the content of each quote for a code
    for quote in reg.finditer(string):
        out = string[quote.start():quote.end()]
        s = s.replace(out, '**' + str(i) + '**')
        replaces.append(out)
        i+=1
    #split the string without quotes
    res = pp.findall(s)

    #add the quotes again
    #TODO this part could be faster.
    #(lineal instead of quadratic)
    i = 1
    for replace in replaces:
        for x in range(len(res)):
            res[x] = res[x].replace('**' + str(i) + '**', replace)
        i+=1
    return res

Old solution:

I choose to match if there was an opening quote and wait it to close, and the match an ending semicolon. each "part" you want to match needs to end in semicolon. so this match things like this :

  • 'foobar;.sska';
  • "akjshd;asjkdhkj..,";
  • asdkjhakjhajsd.jhdf;

Code:

mm = re.compile('''((?P<quote>'|")?.*?(?(quote)\\2|);)''')
res = mm.findall('''part 1;"this is ; part 2;";'this is ; part 3';part 4''')

you may have to do some postprocessing to res, but it contains what you want.

참고URL : https://stackoverflow.com/questions/2785755/how-to-split-but-ignore-separators-in-quoted-strings-in-python

반응형