Bir dizeyi Python'da boşluklara (alıntılanan alt dizeleri koruyarak) bölme


269

Ben böyle bir dize var:

this is "a test"

Ben tırnak içindeki boşlukları göz ardı ederken boşluk ile bölmek için Python bir şey yazmaya çalışıyorum. Aradığım sonuç:

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

PS. "Eğer tırnak içinde tırnak varsa, ne olur, benim uygulamada, bu asla olmayacak" diye soracağını biliyorum.


1
Bu soruyu sorduğun için teşekkürler. Pypar derleme modülünü sabitlemek için tam da ihtiyacım olan şey bu.
Martlark

Yanıtlar:


392

Sen istemek splityerleşik gelen, shlexmodül.

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

Bu tam olarak istediğinizi yapmalıdır.


13
Teklifleri korumak için "posix = False" kullanın. shlex.split('this is "a test"', posix=False)döner['this', 'is', '"a test"']
Boon

@MatthewG. Python 2.7.3'teki "düzeltme", bir unicode dizgenin shlex.split()iletilmesinin bir UnicodeEncodeErroristisnayı tetikleyeceği anlamına gelir .
Rockallite

57

shlexÖzellikle modüle bir göz atın shlex.split.

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

40

Burada karmaşık ve / veya yanlış görünen normal ifade yaklaşımları görüyorum. Bu beni şaşırtıyor, çünkü normal ifade sözdizimi kolayca "boşluk veya tırnaklarla çevrili" ifadesini tanımlayabilir ve çoğu normal ifade motoru (Python'lar dahil) normal ifadeye bölünebilir. Eğer normal ifadeleri kullanacaksanız, neden sadece tam olarak ne demek istediğinizi söylemiyorsunuz ?:

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()]

Açıklama:

[\\\"'] = double-quote or single-quote
.* = anything
( |X) = space or X
.strip() = remove space and empty-string separators

shlex muhtemelen daha fazla özellik sunar.


1
Ben de aynı şeyi düşünüyordum, ama bunun yerine re.findall (r '[^ \ s "] + |" [^ "] *"' için t için [t.strip ('"') önereceğim. a test "')]
Darius Bacon

2
+1 Bunu kullanıyorum çünkü shlex'ten çok daha hızlı bir halttı.
hanleyp

3
Neden üçlü ters eğik çizgi? basit bir ters eğik çizgi aynı şeyi yapmayacak mı?
Doppelganger

1
Aslında, bu konuda sevmediğim bir şey tırnak önce / sonra hiçbir şey düzgün bölünmemiş olmasıdır. Böyle bir dize varsa 'PARAMS val1 = "Şey" val2 = "Şey2". İpin üç parçaya bölünmesini bekliyorum, ancak 5'e ayrılıyor. Regex'i yaptığımdan bu yana bir süre geçti, bu yüzden şu anda çözümünüzü kullanarak çözmeye çalışmak istemiyorum.
leetNightshade

1
Normal ifadeler kullanırken ham dizeler kullanmalısınız.
asmeurer

29

Kullanım durumunuza bağlı olarak, csvmodülü kontrol etmek de isteyebilirsiniz :

import csv
lines = ['this is "a string"', 'and more "stuff"']
for row in csv.reader(lines, delimiter=" "):
    print(row)

Çıktı:

['this', 'is', 'a string']
['and', 'more', 'stuff']

2
yararlı, shlex bazı gerekli karakter şeritleri
scraplesh 29:03

1
CSV en üst üste kullanılması iki çift tırnak (yan-yana olduğu gibi "") bir çift tırnak temsil etmek ", böylece tek bir teklifle içine iki çift tırnak dönecek 'this is "a string""'ve 'this is "a string"""'irade her iki harita için['this', 'is', 'a string"']
Boris

15

70.000.000 satır kalamar günlüğünü işlemek için shlex.split kullanıyorum, çok yavaş. Bu yüzden yeniden geçtim.

Shlex ile ilgili performans sorununuz varsa lütfen bunu deneyin.

import re

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

8

Bu soru normal ifade ile etiketlendiğinden, normal ifade yaklaşımını denemeye karar verdim. Önce tırnak parçalarındaki tüm boşlukları \ x00 ile değiştiririm, sonra boşluklara bölerim, sonra \ x00'ü her parçadaki boşluklarla değiştiririm.

Her iki sürüm de aynı şeyi yapar, ancak splitter splitter2'den biraz daha okunabilir.

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)

Bunun yerine re.Scanner kullanmış olmalısınız. Daha güvenilir (ve aslında re.Scanner kullanarak shlex benzeri bir uygulama yaptım).
Devin Jeanpierre

+1 Hm, bu oldukça akıllı bir fikir, sorunu birden fazla aşamaya bölüyor, böylece cevap çok karmaşık değil. Shlex, tam olarak ihtiyacım olan şeyi yapmadı, hatta ince ayar yapmaya çalışırken bile. Ve tek geçişli regex çözümleri gerçekten garip ve karmaşık hale geliyordu.
leetNightshade

6

Performans nedenleriyle redaha hızlı görünüyor . İşte benim dış tırnak koruyan en az açgözlü bir operatör kullanarak benim çözüm:

re.findall("(?:\".*?\"|\S)+", s)

Sonuç:

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

Bu aaa"bla blub"bbbtokenler boşluklarla ayrılmadığından yapıları bir arada bırakır . Dize kaçan karakterler içeriyorsa şu şekilde eşleşebilirsiniz:

>>> 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.\""

Bunun da boş dizeyle eşleştiğini lütfen unutmayın ""\S , desenin bir kısmı aracılığıyla .


1
Bu çözümün bir diğer önemli avantajı, sınırlayıcı karakter (örn. ,Yoluyla '(?:".*?"|[^,])+') açısından çok yönlülüğüdür . Aynısı, tırnak işaretleri içine alan karakterler için de geçerlidir.
a_guest

4

Kabul edilen shlexyaklaşımla ilgili temel sorun, alıntı yapılan alt dizelerin dışındaki kaçış karakterlerini yok saymaması ve bazı köşe durumlarında biraz beklenmedik sonuçlar vermesidir.

Bu tür bir alt dize içinde tırnak kaçış yeteneği ile tek tırnaklı veya çift tırnaklı alt dizeleri korunur, giriş dizeleri böler bölünmüş bir işlev gereken aşağıdaki kullanım durumu var. Alıntılanmamış bir dize içindeki tırnak işaretleri diğer karakterlerden farklı olarak ele alınmamalıdır. Beklenen çıktıya sahip bazı örnek test örnekleri:

giriş dizesi | beklenen çıktı
===============================================
 'abc def' | ['abc', 'def']
 "abc \\ s def" | ['abc', '\\ s', 'def']
 '"abc def" ghi' | ['abc def', 'ghi']
 "'abc def' ghi" | ['abc def', 'ghi']
 '"abc \\" def "ghi' | ['abc" def', 'ghi']
 "'abc \\' def 'ghi" | ["abc 'def",' ghi ']
 "'abc \\ s def' ghi" | ['abc \\ s def', 'ghi']
 '"abc \\ s def" ghi' | ['abc \\ s def', 'ghi']
 '"" testi "| ['', 'Ölçek']
 "'' testi" | ['', 'Ölçek']
 "abc'def" | [ "Abc'def"]
 "abc'def '" | [ "'Abc'def"]
 "abc'def 'ghi" | ["abc'def '",' ghi ']
 "abc'def'ghi" | [ "Abc'def'ghi"]
 'abc "def' | ['abc" def']
 'abc "def"' | [ 'Abc "def"']
 'abc "def" ghi' | ['abc "def",' ghi ']
 'abc "def" ghi' | [ 'Abc "def" GSS']
 "r'AA 'r'. * _ xyz $ '" | ["r'AA '", "r'. * _ xyz $ '"]

Bir dizeyi tüm çıktı dizeleri için beklenen çıktı sonuçları olacak şekilde bölmek için aşağıdaki işlevi ile sona erdi:

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

Aşağıdaki test uygulaması diğer yaklaşımların ( shlexve csvşimdilik) ve özel bölünmüş uygulamanın sonuçlarını kontrol eder :

#!/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$'"])

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

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

Çıktı:

shlex

[Tamam] abc def -> ['abc', 'def']
[FAIL] abc \ s def -> ['abc', 's', 'def']
[Tamam] "abc def" ghi -> ['abc def', 'ghi']
[Tamam] 'abc def' ghi -> ['abc def', 'ghi']
[Tamam] "abc \" def "ghi -> ['abc" def', 'ghi']
[FAIL] 'abc \' def 'ghi -> istisna: Kapanış teklifi yok
[Tamam] 'abc \ s def' ghi -> ['abc \\ s def', 'ghi']
[Tamam] "abc \ s def" ghi -> ['abc \\ s def', 'ghi']
[Tamam] "" test -> ['', 'test']
[Tamam] '' test -> ['', 'test']
[FAIL] abc'def -> istisna: Kapanış teklifi yok
[FAIL] abc'def '-> [' abcdef ']
[FAIL] abc'def 'ghi -> [' abcdef ',' ghi ']
[FAIL] abc'def'ghi -> ['abcdefghi']
[FAIL] abc "def -> istisna: Kapanış teklifi yok
[FAIL] abc "def" -> ['abcdef']
[FAIL] abc "def" ghi -> ['abcdef', 'ghi']
[FAIL] abc "def" ghi -> ['abcdefghi']
[BAŞARISIZ] r'AA 'r'. * _ Xyz $ '-> [' rAA ',' r. * _ Xyz $ ']

cSV

[Tamam] abc def -> ['abc', 'def']
[Tamam] abc \ s def -> ['abc', '\\ s', 'def']
[Tamam] "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']
[Tamam] "abc \ s def" ghi -> ['abc \\ s def', 'ghi']
[Tamam] "" test -> ['', 'test']
[FAIL] '' test -> ["''", 'test']
[Tamam] abc'def -> ["abc'def"]
[Tamam] abc'def '-> ["abc'def'"]
[Tamam] abc'def 'ghi -> ["abc'def'", 'ghi']
[Tamam] abc'def'ghi -> ["abc'def'ghi"]
[Tamam] abc "def -> ['abc" def']
[Tamam] abc "def" -> ['abc "def"']
[Tamam] abc "def" ghi -> ['abc "def"', 'ghi']
[Tamam] abc "def" ghi -> ['abc "def" ghi']
[Tamam] r'AA 'r'. * _ Xyz $ '-> ["r'AA'", "r '. * _ Xyz $'"]

yeniden

[Tamam] abc def -> ['abc', 'def']
[Tamam] abc \ s def -> ['abc', '\\ s', 'def']
[Tamam] "abc def" ghi -> ['abc def', 'ghi']
[Tamam] 'abc def' ghi -> ['abc def', 'ghi']
[Tamam] "abc \" def "ghi -> ['abc" def', 'ghi']
[Tamam] 'abc \' def 'ghi -> ["abc' def", 'ghi']
[Tamam] 'abc \ s def' ghi -> ['abc \\ s def', 'ghi']
[Tamam] "abc \ s def" ghi -> ['abc \\ s def', 'ghi']
[Tamam] "" test -> ['', 'test']
[Tamam] '' test -> ['', 'test']
[Tamam] abc'def -> ["abc'def"]
[Tamam] abc'def '-> ["abc'def'"]
[Tamam] abc'def 'ghi -> ["abc'def'", 'ghi']
[Tamam] abc'def'ghi -> ["abc'def'ghi"]
[Tamam] abc "def -> ['abc" def']
[Tamam] abc "def" -> ['abc "def"']
[Tamam] abc "def" ghi -> ['abc "def"', 'ghi']
[Tamam] abc "def" ghi -> ['abc "def" ghi']
[Tamam] r'AA 'r'. * _ Xyz $ '-> ["r'AA'", "r '. * _ Xyz $'"]

shlex: yineleme başına 0.281 ms
csv: yineleme başına 0,030 ms
re: yineleme başına 0.049 ms

Bu nedenle performans, shlexnormal ifadeden önceden derlenerek daha iyi olur ve bu durumda csvyaklaşımdan daha iyi performans gösterir .


Neden bahsettiğinizden emin değilim: `` >>> shlex.split ('bu bir testtir' ') [' bu ',' bir 'test'] >>> shlex.split (' bu \\ "bir test \\" ') [' bu ',' ',' 'a', 'test' '] >>> shlex.split (' bu "a \\" test \\ " "') [' this ',' is ',' a" test "']` `
morsik

morsik, ne demek istiyorsun? Belki kullanım durumunuz benimkine uymuyor? Test senaryolarına baktığınızda, shlexkullanım vakalarım için beklendiği gibi davranmayan tüm vakaları göreceksiniz .
Ton van den Heuvel

3

Tırnak işaretlerini korumak için bu işlevi kullanın:

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

Daha büyük bir dize ile karşılaştırıldığında, fonksiyonunuz çok yavaş
Faran2007

3

Farklı cevapların hız testi:

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

1

Hmm, "Yanıtla" düğmesini bulamıyorum ... yine de, bu cevap Kate'in yaklaşımına dayanıyor, ancak dizeleri kaçan tırnaklar içeren alt dizelerle doğru şekilde ayırıyor ve alt dizelerin başlangıç ​​ve bitiş tırnaklarını da kaldırıyor:

  [i.strip('"').strip("'") for i in re.split(r'(\s+|(?<!\\)".*?(?<!\\)"|(?<!\\)\'.*?(?<!\\)\')', string) if i.strip()]

Bu gibi dizelerde çalışır 'This is " a \\\"test\\\"\\\'s substring"'(Python'un kaçışları kaldırmasını önlemek için çılgın işaretleme maalesef gereklidir).

Döndürülen listedeki dizelerde ortaya çıkan çıkışlar istenmezse, işlevin bu biraz değiştirilmiş sürümünü kullanabilirsiniz:

[i.strip('"').strip("'").decode('string_escape') for i in re.split(r'(\s+|(?<!\\)".*?(?<!\\)"|(?<!\\)\'.*?(?<!\\)\')', string) if i.strip()]

1

Bazı Python 2 sürümlerinde unicode sorunlarını çözmek için şunları öneririm:

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

Python 2.7.5 için bu şöyle olmalıdır: split = lambda a: [b.decode('utf-8') for b in _split(a)]aksi takdirde şunları elde edersiniz:UnicodeDecodeError: 'ascii' codec can't decode byte ... in position ...: ordinal not in range(128)
Peter Varo

1

Bir seçenek olarak tssplit'i deneyin:

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

0

Öneririm:

test dizesi:

s = 'abc "ad" \'fg\' "kk\'rdt\'" zzz"34"zzz "" \'\''

"" ve "işaretlerini de yakalamak için:

import re
re.findall(r'"[^"]*"|\'[^\']*\'|[^"\'\s]+',s)

sonuç:

['abc', '"ad"', "'fg'", '"kk\'rdt\'"', 'zzz', '"34"', 'zzz', '""', "''"]

boş "" ve "işaretlerini yoksaymak için:

import re
re.findall(r'"[^"]+"|\'[^\']+\'|[^"\'\s]+',s)

sonuç:

['abc', '"ad"', "'fg'", '"kk\'rdt\'"', 'zzz', '"34"', 'zzz']

re.findall("(?:\".*?\"|'.*?'|[^\s'\"]+)", s)Ayrıca yazılabilir .
hochl

-3

Alt dizeleri basit bir şeyden daha fazla umursamıyorsanız

>>> 'a short sized string with spaces '.split()

Verim:

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

Veya dize modülü

>>> from string import split as stringsplit; 
>>> stringsplit('a short sized string with spaces '*100)

Performans: String modülü, string yöntemlerinden daha iyi performans gösteriyor gibi görünüyor

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

Veya RE motorunu kullanabilirsiniz

>>> from re import split as resplit
>>> regex = '\s+'
>>> medstring = 'a short sized string with spaces '*100
>>> resplit(regex, medstring)

Verim

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

Çok uzun dizeler için dizenin tamamını belleğe yüklememeli ve satırları ayırmamalı ya da yinelemeli bir döngü kullanmamalısınız


11
Sorunun bütün noktasını kaçırmış görünüyorsunuz. Dizede bölünmemesi gereken alıntılanmış bölümler vardır.
rjmunro

-3

Bunu dene:

  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

Bazı test dizeleri:

'This is "a test"' -> ['This', 'is', 'a test']
'"This is \'a test\'"' -> ["This is 'a test'"]

Lütfen başarısız olacağını düşündüğünüz bir dizenin repr değerini sağlayın.
pjz

Düşün ? adamsplit("This is 'a test'")['This', 'is', "'a", "test'"]
Matthew Schinckel

OP yalnızca "tırnak içinde" diyor ve sadece çift tırnak içeren bir örneği var.
pjz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.