Trong Python, làm cách nào để tách chuỗi và giữ dấu phân cách?


226

Đây là cách đơn giản nhất để giải thích điều này. Đây là những gì tôi đang sử dụng:

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

Đây là những gì tôi muốn:

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

Lý do là tôi muốn tách một chuỗi thành các mã thông báo, thao tác với nó, sau đó đặt nó lại với nhau.


3
viết \Wtắt của từ gì Tôi đã thất bại trên google nó.
Ooker

8
Một nhân vật không lời xem ở đây để biết chi tiết
Russell

Để phân tách chuỗi byte thô thay vì phân tách chuỗi, hãy xem stackoverflow.com/questions/62591863/ mẹo
Lorenz

Câu trả lời:


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

22
Thật tuyệt. Tôi không biết re.split đã làm điều đó với các nhóm chụp.
Laurence Gonsalves

16
@Laurence: Vâng, đó là tài liệu: docs.python.org/library/re.html#re.split :. "Split chuỗi bởi các lần xuất hiện của mẫu Nếu chụp ngoặc được sử dụng trong mô hình, sau đó nội dung của tất cả các nhóm trong mẫu cũng được trả lại như một phần của danh sách kết quả. "
Vinay Sajip

40
Nó thiếu nghiêm túc. Tôi đã sử dụng Python được 14 năm và chỉ mới phát hiện ra điều này.
smci

19
Có một tùy chọn để đầu ra của trận đấu nhóm được gắn vào bất cứ thứ gì ở bên trái (hoặc tương tự bên phải) của phần tách không? Ví dụ, điều này có thể dễ dàng sửa đổi để đầu ra là ['foo', '/bar', ' spam', '\neggs']?
ely

3
@ Mr.F Bạn có thể làm gì đó với re.sub. Tôi muốn phân chia theo phần trăm kết thúc vì vậy tôi chỉ chìm trong một nhân vật kép và sau đó chia tách, hacky nhưng làm việc cho trường hợp của tôi: re.split('% ', re.sub('% ', '%% ', '5.000% Additional Whatnot'))->['5.000%', 'Additional Whatnot']
Kyle James Walker

29

Nếu bạn đang chia tách trên dòng mới, sử dụng splitlines(True).

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

(Không phải là một giải pháp chung, nhưng thêm vào đây trong trường hợp ai đó đến đây không nhận ra phương pháp này tồn tại.)


12

Một giải pháp không có regex khác hoạt động tốt trên Python 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

Nếu bạn chỉ có 1 dấu phân cách, bạn có thể sử dụng mức hiểu danh sách:

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

Bổ sung / chuẩn bị phân tách:

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

Dấu tách là phần tử riêng của nó:

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
bạn cũng có thể thêm vào if xđể đảm bảo rằng đoạn được tạo bởi splitcó một số nội dung, tức làresult = [x + sep for x in text.split(sep) if x]
tôi đã báo động người ngoài hành tinh

Đối với tôi, dải bị loại bỏ quá nhiều và tôi đã phải sử dụng điều này:result = [sep+x for x in data.split(sep)] result[0] = result[0][len(sep):]
scottlittle

9

một ví dụ khác, phân tách trên không phải là số và giữ dấu phân cách

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

đầu ra:

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

giải trình

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

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

Mặc dù, như các tài liệu nói, điều này tương đương với câu trả lời được chấp nhận, tôi thích khả năng đọc của phiên bản này - mặc dù đó \Wlà một cách nhỏ gọn hơn để diễn đạt nó.
ephsmith

3

Bạn cũng có thể chia một chuỗi bằng một chuỗi các chuỗi thay vì một biểu thức thông thường, như thế này:

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

Một giải pháp đơn giản và lười biếng

Giả sử mô hình regex của bạn là split_pattern = r'(!|\?)'

Đầu tiên, bạn thêm một số ký tự giống như dấu phân cách mới, như '[cut]'

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

Sau đó, bạn tách dấu phân cách mới, new_string.split('[cut]')


Cách tiếp cận này là thông minh, nhưng sẽ thất bại khi chuỗi gốc đã có [cut]ở đâu đó.
Matthijs Kooijman

Nó có thể nhanh hơn trên các vấn đề quy mô lớn vì cuối cùng nó sử dụng chuỗi.split (), trong trường hợp re.split () có giá cao hơn re.sub () với chuỗi.split () (mà tôi không biết).
Lorenz

1

Nếu ai đó muốn tách chuỗi trong khi giữ dấu phân cách bằng regex mà không bắt nhóm:

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)

Nếu một người giả định rằng regex được gói vào nhóm bắt giữ:

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)

Cả hai cách cũng sẽ loại bỏ các nhóm trống vô dụng và gây phiền nhiễu trong hầu hết các trường hợp.


1

Đây là một .splitgiải pháp đơn giản hoạt động mà không cần regex.

Đây là một câu trả lời cho Python split () mà không xóa dấu phân cách , vì vậy không chính xác những gì bài đăng gốc yêu cầu nhưng câu hỏi khác đã được đóng lại như một bản sao cho bài này.

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

Các xét nghiệm ngẫu nhiên:

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

regex nên tránh trên các vấn đề quy mô lớn vì lý do tốc độ, đó là lý do tại sao đây là một gợi ý tốt.
Lorenz

0

Tôi gặp vấn đề tương tự khi cố gắng tách một đường dẫn tệp và đấu tranh để tìm một câu trả lời đơn giản. Điều này làm việc cho tôi và không liên quan đến việc phải thay thế các dấu phân cách trở lại văn bản phân tách:

my_path = 'folder1/folder2/folder3/file1'

import re

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

trả về:

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


Điều này có thể được đơn giản hóa một chút bằng cách sử dụng: re.findall('[^/]+/?', my_path)(ví dụ: tạo dấu gạch chéo tùy chọn bằng cách sử dụng ?thay vì cung cấp hai lựa chọn thay thế |.
Matthijs Kooijman

0

Tôi tìm thấy cách tiếp cận dựa trên máy phát điện này thỏa mãn hơn:

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

Nó tránh sự cần thiết phải tìm ra regex chính xác, trong khi về lý thuyết nên khá rẻ. Nó không tạo ra các đối tượng chuỗi mới và, hầu hết các công việc lặp lại cho phương thức tìm hiệu quả.

... và trong Python 3.8, nó có thể ngắn như:

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. thay thế tất cả seperator: (\W)bằngseperator + new_seperator: (\W;)

  2. chia theo 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')
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.