Python'da bir dizedeki kaçış dizilerini işleme


112

Bazen bir dosyadan veya kullanıcıdan girdi aldığımda, içinde kaçış dizileri olan bir dize alırım. Kaçış dizilerini , Python'un dize değişmezlerindeki kaçış dizilerini işlediği şekilde işlemek istiyorum .

Örneğin şöyle myStringtanımlandığını varsayalım:

>>> myString = "spam\\neggs"
>>> print(myString)
spam\neggs

Bunu yapan bir işlev (onu arayacağım process) istiyorum :

>>> print(process(myString))
spam
eggs

İşlevin Python'daki tüm kaçış dizilerini işleyebilmesi önemlidir (yukarıdaki bağlantıda bir tabloda listelenmiştir).

Python'un bunu yapmak için bir işlevi var mı?


1
hmmm, içeren bir dizenin 'spam'+"eggs"+'''some'''+"""more"""işlenmesini tam olarak nasıl beklersiniz ?
Nas Banov

@Nas Banov Bu iyi bir test. Bu dize hiçbir kaçış dizisi içermez, bu nedenle işlendikten sonra tamamen aynı olmalıdır. myString = "'spam'+\"eggs\"+'''some'''+\"\"\"more\"\"\"", çalışıyor print(bytes(myString, "utf-8").decode("unicode_escape"))gibi görünüyor.
dln385

5
Bu soruya verilen yanıtların çoğunun ciddi sorunları var. Python'da unicode'u bozmadan kaçış dizilerini onurlandırmanın standart bir yolu yok gibi görünüyor. @Rspeer tarafından gönderilen cevap, şimdiye kadar bilinen tüm vakaları ele aldığı için Grako için benimsediğim cevap .
Apalala

Yanıtlar:


139

Yapılması gereken doğru şey, dizeyi çözmek için 'dizge-kaçış' kodunu kullanmaktır.

>>> myString = "spam\\neggs"
>>> decoded_string = bytes(myString, "utf-8").decode("unicode_escape") # python3 
>>> decoded_string = myString.decode('string_escape') # python2
>>> print(decoded_string)
spam
eggs

AST veya eval kullanmayın. Dize kodeklerini kullanmak çok daha güvenlidir.


3
eller aşağı, en iyi çözüm! btw, dokümanlara göre "string_escape" (alt çizgi ile) olmalıdır, ancak bazı nedenlerden dolayı 'string escape', 'string @ escape "kalıbındaki herhangi bir şeyi kabul eder ... temelde'string\W+escape'
Nas Banov

2
@Nas Banov Belgeler bununla ilgili küçük bir açıklama yapıyor :Notice that spelling alternatives that only differ in case or use a hyphen instead of an underscore are also valid aliases; therefore, e.g. 'utf-8' is a valid alias for the 'utf_8' codec.
dln385

30
Bu çözüm yeterince iyi değil çünkü orijinal dizede okunaklı unicode karakterlerin olduğu durumu ele almıyor. Eğer denerseniz: >>> print("juancarlo\\tañez".encode('utf-8').decode('unicode_escape'))juancarlo añez
Apalala

2
@Apalala ile anlaştı: Bu yeterince iyi değil. Python2 ve 3'te çalışan eksiksiz bir çözüm için aşağıdaki rseeper'in cevabına göz atın!
Christian Aichinger

2
Yana latin1tarafından kabul edilir unicode_escape, kodlama / kod çözme bit yinelemek, örneğins.encode('utf-8').decode('unicode_escape').encode('latin1').decode('utf8')
metatoaster

121

unicode_escape genel olarak çalışmıyor

string_escapeVeya unicode_escapeçözümün genel olarak çalışmadığı ortaya çıktı - özellikle gerçek Unicode varlığında çalışmıyor.

ASCII olmayan tüm karakterlerin kaçılacağından emin olabilirseniz (ve ilk 128 karakterin dışındaki hiçbir şeyin ASCII olmadığını unutmayın), unicode_escapesizin için doğru olanı yapacaktır. Ancak dizenizde zaten ASCII olmayan herhangi bir gerçek karakter varsa, işler ters gidecektir.

unicode_escapetemelde baytları Unicode metne dönüştürmek için tasarlanmıştır. Ancak birçok yerde - örneğin, Python kaynak kodu - kaynak veriler zaten Unicode metindir.

Bunun doğru çalışmasının tek yolu, metni önce bayt olarak kodlamanızdır. UTF-8 tüm metinler için mantıklı bir kodlamadır, bu yüzden işe yaramalı, değil mi?

Aşağıdaki örnekler Python 3'tedir, böylece dize değişmezleri daha temizdir, ancak aynı sorun hem Python 2 hem de 3'te biraz farklı tezahürlerde mevcuttur.

>>> s = 'naïve \\t test'
>>> print(s.encode('utf-8').decode('unicode_escape'))
naïve   test

Eh, bu yanlış.

Metni metne dönüştüren codec bileşenlerini kullanmanın önerilen yeni yolu, codecs.decodedoğrudan aramaktır. Bu yardımcı olur mu?

>>> import codecs
>>> print(codecs.decode(s, 'unicode_escape'))
naïve   test

Bir şey değil. (Ayrıca, yukarıdaki Python 2'de bir UnicodeError'dır.)

unicode_escapeCodec, adına rağmen, tüm ASCII olmayan bayt Latin-1 (ISO-8859-1) kodlamada olduğunu varsaymak çıkıyor. Yani bunu şu şekilde yapmanız gerekir:

>>> print(s.encode('latin-1').decode('unicode_escape'))
naïve    test

Ama bu korkunç. Bu sizi 256 Latin-1 karakterle sınırlar, sanki Unicode hiç icat edilmemiş gibi!

>>> print('Ernő \\t Rubik'.encode('latin-1').decode('unicode_escape'))
UnicodeEncodeError: 'latin-1' codec can't encode character '\u0151'
in position 3: ordinal not in range(256)

Sorunu çözmek için bir normal ifade eklemek

(Şaşırtıcı bir şekilde, şu anda iki sorunumuz yok.)

Yapmamız gereken şey, unicode_escapekod çözücüyü yalnızca ASCII metni olduğundan emin olduğumuz şeylere uygulamaktır . Özellikle, bunu yalnızca ASCII metni olması garanti edilen geçerli Python kaçış dizilerine uyguladığımızdan emin olabiliriz.

Plan şu ki, normal bir ifade kullanarak kaçış dizileri bulacağız ve re.subonları kaçınılmamış değerleriyle değiştirmek için argüman olarak bir işlev kullanacağız .

import re
import codecs

ESCAPE_SEQUENCE_RE = re.compile(r'''
    ( \\U........      # 8-digit hex escapes
    | \\u....          # 4-digit hex escapes
    | \\x..            # 2-digit hex escapes
    | \\[0-7]{1,3}     # Octal escapes
    | \\N\{[^}]+\}     # Unicode characters by name
    | \\[\\'"abfnrtv]  # Single-character escapes
    )''', re.UNICODE | re.VERBOSE)

def decode_escapes(s):
    def decode_match(match):
        return codecs.decode(match.group(0), 'unicode-escape')

    return ESCAPE_SEQUENCE_RE.sub(decode_match, s)

Ve bununla:

>>> print(decode_escapes('Ernő \\t Rubik'))
Ernő     Rubik

2
bunun gibi daha kapsamlı cevap türlerine ihtiyacımız var. Teşekkürler.
v.oddou

Bu hiç çalışıyor mu os.sep? Bunu yapmaya çalışıyorum: patt = '^' + self.prefix + os.sep ; name = sub(decode_escapes(patt), '', name)ve işe yaramıyor. Noktalı virgül, yeni bir satırın yerinde.
Pureferret

@Pureferret Ne sorduğunuzdan pek emin değilim, ancak muhtemelen bunu ters eğik çizginin farklı bir anlamı olan Windows dosya yolları gibi dizelerde çalıştırmamalısınız. (Bu sizin os.sepmi?) Windows dizin adlarınızda ters eğik çizgi kaçış dizileri varsa, durum hemen hemen kurtarılamaz.
rspeer

Kaçış dizisinde kaçış yok, ancak 'sahte kaçış dizesi' hatası alıyorum
Pureferret

Bu bana, başka bir normal ifadeyi ters eğik çizgiyle
bitirdiğinizi söylüyor

33

Python 3 için gerçekten doğru ve uygun cevap:

>>> import codecs
>>> myString = "spam\\neggs"
>>> print(codecs.escape_decode(bytes(myString, "utf-8"))[0].decode("utf-8"))
spam
eggs
>>> myString = "naïve \\t test"
>>> print(codecs.escape_decode(bytes(myString, "utf-8"))[0].decode("utf-8"))
naïve    test

İlgili ayrıntılar codecs.escape_decode:

  • codecs.escape_decode bayttan bayta kod çözücüdür
  • codecs.escape_decodeascii kaçış dizilerinin kodunu çözer, örneğin: b"\\n"-> b"\n", b"\\xce"-> b"\xce".
  • codecs.escape_decode bayt nesnesinin kodlamasıyla ilgilenmez veya bilmeye ihtiyaç duymaz, ancak kaçan baytların kodlaması nesnenin geri kalanının kodlamasıyla eşleşmelidir.

Arka fon:


Gerçek cevap bu (: Çok kötü belgelenmiş bir işleve dayanması çok kötü.
jwd

5
Bu, sahip olduğunuz kaçış dizilerinin \xUTF-8 baytlarının çıkışları olduğu durumlar için cevaptır . Ancak baytları bayta dönüştürdüğü için, çıkışlar gibi ASCII Unicode olmayan karakterlerin çıkışlarının kodunu çözmez ve \uçözemez.
rspeer

Bilginize, bu işlev teknik olarak halka açık değildir. bkz. bugs.python.org/issue30588
Hack5

8

ast.literal_evalFonksiyon yakın geliyor ama dize düzgün ilk teklif edilecek bekleyecektir.

Elbette, Python'un ters eğik çizgi kaçışlarını yorumlaması, dizenin nasıl alıntı yapıldığına bağlıdır ( ""vs r""vs u"", üçlü tırnak işaretleri, vb.), Bu nedenle kullanıcı girişini uygun tırnaklarla sarmak ve 'e geçmek isteyebilirsiniz literal_eval. Bunu tırnak içine almak aynı zamanda literal_evalbir sayı, tuple, sözlük vb. Döndürmeyi de engelleyecektir .

Kullanıcı dizeyi sarmayı düşündüğünüz türde tırnaksız alıntılar yazarsa işler yine de karmaşıklaşabilir.


Anlıyorum. Bu dediğiniz gibi potansiyel olarak tehlikeli gibi görünüyor myString = "\"\ndoBadStuff()\n\"", print(ast.literal_eval('"' + myString + '"'))çalışma koduna denemek görünüyor. Bundan nasıl ast.literal_evalfarklı / daha güvenli eval?
dln385

5
@ dln385: literal_evalasla kodu çalıştırmaz. Dokümantasyondan, "Bu, güvenilir olmayan kaynaklardan gelen Python ifadelerini içeren dizeleri, değerleri tek başına ayrıştırmaya gerek kalmadan güvenli bir şekilde değerlendirmek için kullanılabilir."
Greg Hewgill

2

Bu, bunu yapmanın kötü bir yolu, ancak bir dizge bağımsız değişkeninde geçirilen kaçan sekizli sayıları yorumlamaya çalışırken benim için çalıştı.

input_string = eval('b"' + sys.argv[1] + '"')

Eval ve ast.literal_eval arasında bir fark olduğunu belirtmekte fayda var (çok daha güvensiz olarak değerlendirilir). Bkz . Python eval () ve ast.literal_eval () kullanmak?


0

Aşağıdaki kodun çalışması için \ n'nin dizede görüntülenmesi gerekiyor.

import string

our_str = 'The String is \\n, \\n and \\n!'
new_str = string.replace(our_str, '/\\n', '/\n', 1)
print(new_str)

1
Bu, yazıldığı gibi çalışmaz (eğik çizgiler replacehiçbir şey yapmaz), çılgınca modası geçmiş API'leri kullanır ( stringbu tür modül işlevleri Python 2.0'dan itibaren kaldırılmıştır, stryöntemlerle değiştirilmiştir ve Python 3'te tamamen ortadan kalkmıştır) ve yalnızca tek bir satırsonu yerine özel bir durumu ele alır, genel kaçış işlemini değil.
ShadowRanger
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.