it-swarm-tr.com

Python'da bir dizgiyi boşluklarla - alıntılanan alt dizeleri koruyarak - bölme

Bu gibi bir dize var:

this is "a test"

Python'da tırnak içindeki boşlukları yok sayarak boşlukla ayırmak için bir şeyler yazmaya çalışıyorum. Aradığım sonuç şudur:

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

PS. Biliyorum ki, "alıntılar içinde alıntılar olursa ne olur, ne olacağını asla sormayacaksınız.

223
Adam Pierce

shlex modülünden ayrılmak istiyorsunuz.

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

Bu tam olarak istediğini yapmalı.

342
Jerub

shlex modülüne bir göz atın, özellikle shlex.split.

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

Burada düzenli ve/veya hatalı görünen regex yaklaşımlarını görüyorum. Bu beni şaşırtıyor çünkü regex sözdizimi kolayca "boşluk veya tırnaklarla çevrilmiş" ifadesini tanımlayabilir ve çoğu regex motoru (Python's dahil) bir regex üzerinde bölünebilir. Öyleyse, regex kullanacaksanız, neden 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 sağlar.

31
Kate

Kullanım durumunuza bağlı olarak, csv modülünü de kontrol etmek 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']
23
Ryan Ginstrom

Shlex.split'i 70.000.000 kalamar kütüğünün işlenmesi için kullanıyorum, çok yavaş. Bu yüzden yeniden değiştim.

Shlex ile performans sorununuz varsa lütfen bunu deneyin.

import re

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

Bu soru regex ile etiketlendiğinden, regex yaklaşımını denemeye karar verdim. Önce tırnak parçalarındaki tüm boşlukları\x00 ile değiştirip sonra boşluklara böldüm, sonra da/x00'leri her bir kısımdaki boşluklarla değiştirdim.

Her iki versiyon da aynı şeyi yapar, ancak splitter, splitter2'den biraz daha okunur.

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)
8
gooli

Tırnakları 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
3
THE_MAD_KING

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

Performans nedenleriyle re daha hızlı görünüyor. Dış teklifleri koruyan en az açgözlü işleç kullanan çözümüm:

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

Sonuç:

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

Bu belirteçler boşluklarla ayrılmadığından aaa"bla blub"bbb gibi yapıları birlikte bırakır. Dize çıkış karakterleri içeriyorsa, aşağıdaki ş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 ayrıca, boşluğun "" dizesi ile, desenin \S kısmıyla eşleştiğini de unutmayın.

2
hochl

Bazı Python 2 sürümlerinde unicode sorunlarını aşmak için şunu öneririm:

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

Kabul edilen shlex yaklaşımıyla ilgili temel sorun, alıntılanan alt dizelerin dışındaki kaçış karakterlerini yoksaymaması ve bazı köşe durumlarda biraz beklenmedik sonuçlar vermesidir.

Aşağıdaki kullanım durumlarına sahibim; burada, tek tırnaklı veya çift tırnaklı alt tabanların korunacağı şekilde girdi dizgelerini bölen, böylesi bir alt halka içindeki tırnaklardan kaçma kabiliyetine sahip olan bölünmüş bir işleve ihtiyacım var. Tırnak işareti olmayan bir dizgideki alıntılar diğer karakterlerden farklı şekilde ele alınmamalıdır. Beklenen çıktıya sahip bazı örnek test durumları:

  giriş dizesi | beklenen çıktı 
 =============================================================== == 
 'abc def' | ['abc', 'def'] 
 "abc \\ s def" | ['abc', '\\', '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"' | [ ' "Def" abc'] 
 'abc "def" ghi' | ['abc "def"', 'ghi'] 
 'abc "def" ghi' | [ ' "Def" abc ghi'] 
 "r'AA 'r'. * _ xyz $ '" | ["r'AA '", "r'. * _ xyz $ '"]

Tüm girdi dizgileri için beklenen çıktının sonuçlanacağı şekilde bir dizgeyi bölmek için aşağıdaki işlev ile bitirdim:

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 sonuçlarını (şu an shlex ve csv) 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 __== '__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ış tırnak işareti yok. [OK]' 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: Teklifi kapat 
 [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'] 
 [FAIL] 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 $ '"] 

 re 

 [Tamam] abc def -> [' abc ',' def '] 
 [OK] abc\s def -> [' abc ' , '\' ',' def '] 
 [Tamam] "abc def" ghi -> [' abc def ',' ghi '] 
 [Tamam]' abc def 'ghi -> [' abc def ',' ghi '] 
 [Tamam] "abc \" def "ghi -> [' abc" def ',' ghi '] 
 [OK]' abc\'def' ghi -> [" abc 'def ",' ghi '] 
 [Tamam]' abc\s def 'ghi -> [' abc \\ s def ',' ghi '] 
 [OK] "abc\s def" ghi -> [' abc\\ '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.281ms 
 csv: yineleme başına 0.030ms
re: yineleme başına 0.049ms

Bu nedenle performans shlex'den çok daha iyidir ve normal ifadeyi önceden derleyerek daha da geliştirilebilir, bu durumda csv yaklaşımından daha iyi performans gösterecektir.

1

Yukarıda ele alınan shlex ile olan unicode sorunları (en iyi cevap) http: //bugs.python.org/issue6988#msg146200 'e göre 2.7.2+' de çözüldü (dolaylı olarak).

(yorum yapamam çünkü ayrı cevap)

1
Tyris

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

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

Bu, 'This is " a \\\"test\\\"\\\'s substring"' gibi karakter dizileri üzerinde çalışır (Python'un kaçışları engellemesini sağlamak için ne yazık ki işaretleme gerekir).

Döndürülen listedeki dizelerde ortaya çıkan kaçmaların istenmemesi durumunda, 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
user261478

Öneririm:

test dizesi:

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

ayrıca "" ve "" yi yakalamak için:

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

sonuç:

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

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

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

sonuç:

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