In Python, come posso dividere una stringa e mantenere i separatori?


226

Ecco il modo più semplice per spiegarlo. Ecco cosa sto usando:

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

Ecco cosa voglio:

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

Il motivo è che voglio dividere una stringa in token, manipolarla, quindi rimetterla insieme.


3
cosa significa \W? Ho fallito su Google.
Ooker

8
Un personaggio non verbale vedi qui per i dettagli
Russell

Per la domanda applicata a una stringa di byte non elaborata e posta su "Dividi una stringa e mantieni i delimitatori come parte dei blocchi di stringa divisa, non come elementi di elenco separati", vedi stackoverflow.com/questions/62591863/…
Lorenz

Risposte:


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

22
Questo è figo. Non sapevo che re.split lo facesse con i gruppi di acquisizione.
Laurence Gonsalves il

16
@Laurence: Beh, è ​​documentato: docs.python.org/library/re.html#re.split : "Dividi la stringa per le occorrenze del modello. Se le parentesi di cattura sono usate nel modello, allora il testo di tutti i gruppi nel modello vengono inoltre restituiti come parte dell'elenco risultante. "
Vinay Sajip,

40
È seriamente sotto documentato. Uso Python da 14 anni e l'ho appena scoperto.
smci,

19
Esiste un'opzione in modo che l'output della corrispondenza di gruppo sia collegato a tutto ciò che si trova a sinistra (o analogamente a destra) della divisione? Ad esempio, può essere facilmente modificato in modo che l'output sia ['foo', '/bar', ' spam', '\neggs']?
ely,

3
@ Mr.F Potresti essere in grado di fare qualcosa con re.sub. Volevo dividere una percentuale finale, quindi ho appena fatto un doppio personaggio e poi ho diviso, hacky, ma ho lavorato per il mio caso: re.split('% ', re.sub('% ', '%% ', '5.000% Additional Whatnot'))->['5.000%', 'Additional Whatnot']
Kyle James Walker,

29

Se stai dividendo su newline, usa splitlines(True).

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

(Non è una soluzione generale, ma aggiungendola qui nel caso in cui qualcuno venga qui senza rendersi conto che questo metodo esiste.)


12

Un'altra soluzione senza regex che funziona bene su 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

Se hai solo 1 separatore, puoi utilizzare la comprensione dell'elenco:

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

Separatore in aggiunta / in attesa:

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

Separatore come elemento proprio:

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
puoi anche aggiungere if xper assicurarti che il pezzo prodotto da splitabbia qualche contenuto, cioèresult = [x + sep for x in text.split(sep) if x]
ho allarmato l'alieno

Per me, ho rimosso troppo e ho dovuto usare questo:result = [sep+x for x in data.split(sep)] result[0] = result[0][len(sep):]
scottlittle il

9

un altro esempio, diviso in caratteri non alfanumerici e mantieni i separatori

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

produzione:

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

spiegazione

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

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

Anche se, come dicono i documenti , questo equivale alla risposta accettata, mi piace la leggibilità di questa versione, anche se \Wè un modo più compatto per esprimerla.
efsmith,

3

Puoi anche dividere una stringa con una matrice di stringhe anziché un'espressione regolare, in questo modo:

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

Una soluzione pigra e semplice

Supponiamo che il tuo modello regex sia split_pattern = r'(!|\?)'

Innanzitutto, aggiungi lo stesso carattere del nuovo separatore, come "[taglio]"

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

Quindi dividi il nuovo separatore, new_string.split('[cut]')


Questo approccio è intelligente, ma fallirà quando la stringa originale contiene già [cut]da qualche parte.
Matthijs Kooijman,

Potrebbe essere più veloce su problemi su larga scala in quanto utilizza infine string.split (), nel caso in cui re.split () abbia un costo maggiore di re.sub () con string.split () (che non conosco).
Lorenz

1

Se si desidera dividere la stringa mantenendo i separatori per regex senza acquisire il gruppo:

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)

Se si presume che regex sia racchiuso nel gruppo di acquisizione:

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)

Entrambi i modi rimuoveranno anche i gruppi vuoti che sono inutili e fastidiosi nella maggior parte dei casi.


1

Ecco una .splitsoluzione semplice che funziona senza regex.

Questa è una risposta per Python split () senza rimuovere il delimitatore , quindi non esattamente quello che pone il post originale ma l'altra domanda è stata chiusa come duplicata per questo.

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

Test casuali:

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 dovrebbe essere evitato su problemi su larga scala per motivi di velocità, ecco perché questo è un buon suggerimento.
Lorenz

0

Ho avuto un problema simile nel tentativo di dividere un percorso di file e ho faticato a trovare una risposta semplice. Questo ha funzionato per me e non ha comportato la necessità di sostituire i delimitatori nel testo diviso:

my_path = 'folder1/folder2/folder3/file1'

import re

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

ritorna:

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


Questo può essere leggermente semplificato usando: re.findall('[^/]+/?', my_path)(es. Rendendo opzionale la barra finale usando un ?anziché fornire due alternative |.
Matthijs Kooijman,

0

Ho trovato questo approccio basato sul generatore più soddisfacente:

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

Evita la necessità di capire la regex corretta, mentre in teoria dovrebbe essere abbastanza economico. Non crea nuovi oggetti stringa e delega la maggior parte del lavoro di iterazione al metodo di ricerca efficiente.

... e in Python 3.8 può essere breve come:

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. sostituire tutto seperator: (\W)conseperator + new_seperator: (\W;)

  2. diviso per il 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')
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.