파이썬에서 문자열을 나누고 구분 기호를 어떻게 유지합니까?


226

이것을 설명하는 가장 간단한 방법은 다음과 같습니다. 내가 사용하는 것은 다음과 같습니다.

re.split('\W', 'foo/bar spam\neggs')
-> ['foo', 'bar', 'spam', 'eggs']

내가 원하는 것은 다음과 같습니다.

someMethod('\W', 'foo/bar spam\neggs')
-> ['foo', '/', 'bar', ' ', 'spam', '\n', 'eggs']

그 이유는 문자열을 토큰으로 나누고 조작 한 다음 다시 합치려고하기 때문입니다.


3
무엇 \W을 의미합니까? 나는 구글에 실패했다.
Ooker

8
단어가 아닌 문자 자세한 내용은 여기를 참조
러셀

대신 문자열 분할의 원시 바이트 문자열 분할의 경우, 중복 참조 stackoverflow.com/questions/62591863/...을
로렌츠

답변:


295
>>> re.split('(\W)', 'foo/bar spam\neggs')
['foo', '/', 'bar', ' ', 'spam', '\n', 'eggs']

22
멋지다. 나는 re.split이 캡처 그룹으로 그 일을했는지 ​​몰랐습니다.
Laurence Gonsalves

16
@Laurence : 글쎄, 그것은 문서화되어 있습니다 : docs.python.org/library/re.html#re.split : "패턴의 발생으로 문자열 분리 결과 목록의 일부로 반환됩니다. "
Vinay Sajip 님이

40
문서화가 심각하지 않습니다. 나는 14 년 동안 파이썬을 사용해 왔으며 단지 이것을 발견했다.
smci

19
그룹 경기 결과가 스플릿의 왼쪽 (또는 유사하게 오른쪽)에 첨부되도록 옵션이 있습니까? 예를 들어, 이것을 쉽게 수정하여 출력이 ['foo', '/bar', ' spam', '\neggs']?
ely

3
@ Mr.F re.sub로 무언가를 할 수 있습니다. 나는 결말 퍼센트로 나누고 싶었 기 때문에 나는 단지 더블 캐릭터로 잠을 잤다가 해 키지 만 나름대로 일했다 re.split('% ', re.sub('% ', '%% ', '5.000% Additional Whatnot')).->['5.000%', 'Additional Whatnot']
Kyle James Walker

29

줄 바꿈으로 분할하는 경우을 사용하십시오 splitlines(True).

>>> 'line 1\nline 2\nline without newline'.splitlines(True)
['line 1\n', 'line 2\n', 'line without newline']

(일반적인 해결책은 아니지만 누군가 가이 방법이 존재하지 않는다고 여기에 오면 여기에 추가하십시오.)


12

파이썬 3에서 잘 작동하는 또 다른 정규식 솔루션

# Split strings and keep separator
test_strings = ['<Hello>', 'Hi', '<Hi> <Planet>', '<', '']

def split_and_keep(s, sep):
   if not s: return [''] # consistent with string.split()

   # Find replacement character that is not used in string
   # i.e. just use the highest available character plus one
   # Note: This fails if ord(max(s)) = 0x10FFFF (ValueError)
   p=chr(ord(max(s))+1) 

   return s.replace(sep, sep+p).split(p)

for s in test_strings:
   print(split_and_keep(s, '<'))


# If the unicode limit is reached it will fail explicitly
unicode_max_char = chr(1114111)
ridiculous_string = '<Hello>'+unicode_max_char+'<World>'
print(split_and_keep(ridiculous_string, '<'))

10

구분 기호가 하나만 있으면 목록 이해를 사용할 수 있습니다.

text = 'foo,bar,baz,qux'  
sep = ','

추가 / 앞에 구분 기호 :

result = [x+sep for x in text.split(sep)]
#['foo,', 'bar,', 'baz,', 'qux,']
# to get rid of trailing
result[-1] = result[-1].strip(sep)
#['foo,', 'bar,', 'baz,', 'qux']

result = [sep+x for x in text.split(sep)]
#[',foo', ',bar', ',baz', ',qux']
# to get rid of trailing
result[0] = result[0].strip(sep)
#['foo', ',bar', ',baz', ',qux']

자체 요소 인 구분 기호 :

result = [u for x in text.split(sep) for u in (x, sep)]
#['foo', ',', 'bar', ',', 'baz', ',', 'qux', ',']
results = result[:-1]   # to get rid of trailing

1
당신은 또한에 추가 할 수 있습니다 if x에 의해 생성 된 덩어리가 수 있도록 split일부 콘텐츠, 즉이있다result = [x + sep for x in text.split(sep) if x]
내가 외국인 놀라지

나를 위해, 스트립을 너무 많이 제거하고 나는 이것을 사용해야했다 :result = [sep+x for x in data.split(sep)] result[0] = result[0][len(sep):]
scottlittle

9

다른 예를 들어, 영숫자가 아닌 숫자로 나누고 구분 기호를 유지하십시오.

import re
a = "foo,bar@candy*ice%cream"
re.split('([^a-zA-Z0-9])',a)

산출:

['foo', ',', 'bar', '@', 'candy', '*', 'ice', '%', 'cream']

설명

re.split('([^a-zA-Z0-9])',a)

() <- keep the separators
[] <- match everything in between
^a-zA-Z0-9 <-except alphabets, upper/lower and numbers.

문서 에서 알 수 있듯이 이것은 받아 들일 수있는 대답과 동일하지만이 버전의 가독성은 좋아하지만 \W표현하기가 더 간결한 방법입니다.
ephsmith

3

다음과 같이 정규식 대신 문자열 배열로 문자열을 분할 할 수도 있습니다.

def tokenizeString(aString, separators):
    #separators is an array of strings that are being used to split the the string.
    #sort separators in order of descending length
    separators.sort(key=len)
    listToReturn = []
    i = 0
    while i < len(aString):
        theSeparator = ""
        for current in separators:
            if current == aString[i:i+len(current)]:
                theSeparator = current
        if theSeparator != "":
            listToReturn += [theSeparator]
            i = i + len(theSeparator)
        else:
            if listToReturn == []:
                listToReturn = [""]
            if(listToReturn[-1] in separators):
                listToReturn += [""]
            listToReturn[-1] += aString[i]
            i += 1
    return listToReturn


print(tokenizeString(aString = "\"\"\"hi\"\"\" hello + world += (1*2+3/5) '''hi'''", separators = ["'''", '+=', '+', "/", "*", "\\'", '\\"', "-=", "-", " ", '"""', "(", ")"]))

3
# This keeps all separators  in result 
##########################################################################
import re
st="%%(c+dd+e+f-1523)%%7"
sh=re.compile('[\+\-//\*\<\>\%\(\)]')

def splitStringFull(sh, st):
   ls=sh.split(st)
   lo=[]
   start=0
   for l in ls:
     if not l : continue
     k=st.find(l)
     llen=len(l)
     if k> start:
       tmp= st[start:k]
       lo.append(tmp)
       lo.append(l)
       start = k + llen
     else:
       lo.append(l)
       start =llen
   return lo
  #############################

li= splitStringFull(sh , st)
['%%(', 'c', '+', 'dd', '+', 'e', '+', 'f', '-', '1523', ')%%', '7']

3

하나의 게으른 간단한 솔루션

정규식 패턴이 있다고 가정하십시오. split_pattern = r'(!|\?)'

먼저 '[cut]'과 같이 새 구분 기호와 동일한 문자를 추가합니다

new_string = re.sub(split_pattern, '\\1[cut]', your_string)

그런 다음 새 구분 기호를 분할하고 new_string.split('[cut]')


이 접근법은 영리하지만 원래 문자열에 이미 [cut]어딘가에 있으면 실패합니다 .
Matthijs Kooijman

re.split ()이 string.split ()을 사용하는 re.sub ()보다 비용이 많이 드는 경우, 최종적으로 string.split ()을 사용하기 때문에 대규모 문제에서 더 빠를 수 있습니다.
Lorenz

1

그룹을 캡처하지 않고 정규 표현식으로 구분 기호를 유지하면서 문자열을 분할하려면 다음을 수행하십시오.

def finditer_with_separators(regex, s):
    matches = []
    prev_end = 0
    for match in regex.finditer(s):
        match_start = match.start()
        if (prev_end != 0 or match_start > 0) and match_start != prev_end:
            matches.append(s[prev_end:match.start()])
        matches.append(match.group())
        prev_end = match.end()
    if prev_end < len(s):
        matches.append(s[prev_end:])
    return matches

regex = re.compile(r"[\(\)]")
matches = finditer_with_separators(regex, s)

정규식이 캡처 그룹으로 묶여 있다고 가정하는 경우 :

def split_with_separators(regex, s):
    matches = list(filter(None, regex.split(s)))
    return matches

regex = re.compile(r"([\(\)])")
matches = split_with_separators(regex, s)

두 가지 방법 모두 대부분의 경우 쓸모없고 성가신 빈 그룹을 제거합니다.


1

다음은 .split정규식없이 작동 하는 간단한 솔루션입니다.

이것은 delimiter를 제거하지 않고 Python split ()에 대한 답변 이므로 원래 게시물이 요구하는 내용이 아니라 다른 질문 이이 질문에 대한 중복으로 닫혔습니다.

def splitkeep(s, delimiter):
    split = s.split(delimiter)
    return [substr + delimiter for substr in split[:-1]] + [split[-1]]

무작위 테스트 :

import random

CHARS = [".", "a", "b", "c"]
assert splitkeep("", "X") == [""]  # 0 length test
for delimiter in ('.', '..'):
    for idx in range(100000):
        length = random.randint(1, 50)
        s = "".join(random.choice(CHARS) for _ in range(length))
        assert "".join(splitkeep(s, delimiter)) == s

속도 문제로 인해 대규모 문제에서 정규 표현식을 피해야하므로 이것이 좋은 힌트입니다.
Lorenz

0

파일 경로를 분할하려고하는 비슷한 문제가 있었고 간단한 대답을 찾기 위해 고심했습니다. 이것은 저에게 효과적이며 분리 문자를 다시 분리 텍스트로 대체하지 않아도됩니다.

my_path = 'folder1/folder2/folder3/file1'

import re

re.findall('[^/]+/|[^/]+', my_path)

보고:

['folder1/', 'folder2/', 'folder3/', 'file1']


이 약간 사용하여 단순화 할 수있다 : re.findall('[^/]+/?', my_path)예를 들어 (A를 사용하여 선택적 후행 슬래시를 만들기 ?보다는 두 개의 대안을 제공합니다 |.
Matthijs Kooijman을

0

이 생성기 기반 접근 방식이 더 만족 스러웠습니다.

def split_keep(string, sep):
    """Usage:
    >>> list(split_keep("a.b.c.d", "."))
    ['a.', 'b.', 'c.', 'd']
    """
    start = 0
    while True:
        end = string.find(sep, start) + 1
        if end == 0:
            break
        yield string[start:end]
        start = end
    yield string[start:]

이론 상으로는 상당히 저렴한 반면 올바른 정규 표현식을 알아낼 필요가 없습니다. 새로운 문자열 객체를 생성하지 않으며 대부분의 반복 작업을 효율적인 find 메소드에 위임합니다.

... 그리고 Python 3.8에서는 다음과 같이 짧을 수 있습니다.

def split_keep(string, sep):
    start = 0
    while (end := string.find(sep, start) + 1) > 0:
        yield string[start:end]
        start = end
    yield string[start:]

0
  1. 모두 seperator: (\W)로 바꾸다seperator + new_seperator: (\W;)

  2. 에 의해 분할 new_seperator: (;)

def split_and_keep(seperator, s):
  return re.split(';', re.sub(seperator, lambda match: match.group() + ';', s))

print('\W', 'foo/bar spam\neggs')
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.