ประมวลผลลำดับการหลีกเลี่ยงในสตริงใน Python


112

บางครั้งเมื่อฉันได้รับอินพุตจากไฟล์หรือผู้ใช้ฉันจะได้รับสตริงที่มีลำดับการหลีกเลี่ยงอยู่ในนั้น ฉันต้องการที่จะดำเนินการลำดับหนีไปในทางเดียวกันว่างูหลามกระบวนการลำดับหนีในสายอักขระตัวอักษร

ตัวอย่างเช่นสมมติว่าmyStringถูกกำหนดให้เป็น:

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

ฉันต้องการฟังก์ชัน (ฉันจะเรียกมันprocessว่า) ที่ทำสิ่งนี้:

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

สิ่งสำคัญคือฟังก์ชันสามารถประมวลผลลำดับการหลีกเลี่ยงทั้งหมดใน Python (แสดงอยู่ในตารางในลิงก์ด้านบน)

Python มีฟังก์ชันในการทำสิ่งนี้หรือไม่?


1
อืมคุณคาดหวังให้สตริงที่มี'spam'+"eggs"+'''some'''+"""more"""การประมวลผลเป็นอย่างไร
Nas Banov

@Nas Banov นั่นเป็นการทดสอบที่ดี สตริงนั้นไม่มีลำดับการหลีกเลี่ยงดังนั้นจึงควรเหมือนกันทุกประการหลังการประมวลผล myString = "'spam'+\"eggs\"+'''some'''+\"\"\"more\"\"\"", print(bytes(myString, "utf-8").decode("unicode_escape"))ดูเหมือนว่าจะทำงาน
dln385

5
คำตอบส่วนใหญ่สำหรับคำถามนี้มีปัญหาร้ายแรง ดูเหมือนจะไม่มีวิธีมาตรฐานในการใช้ลำดับการหลีกเลี่ยงใน Python โดยไม่ทำลาย Unicode คำตอบที่โพสต์โดย @rspeer คือคำตอบที่ฉันนำมาใช้กับGrakoเนื่องจากตอนนี้จัดการกับกรณีที่ทราบทั้งหมด
Apalala

คำตอบ:


139

สิ่งที่ต้องทำคือใช้รหัส 'string-escape' เพื่อถอดรหัสสตริง

>>> 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 หรือ eval การใช้ตัวแปลงสัญญาณสตริงนั้นปลอดภัยกว่ามาก


3
ลงมือทำทางออกที่ดีที่สุด ! btw ตามเอกสารควรเป็น "string_escape" (มีขีดล่าง) แต่ด้วยเหตุผลบางประการยอมรับอะไรก็ได้ในรูปแบบ "string escape", "string @ escape" และ whatnot ... โดยพื้นฐานแล้ว'string\W+escape'
Nas Banov

2
@Nas Banov เอกสารประกอบกล่าวถึงเรื่องนี้เล็กน้อย :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
โซลูชันนี้ไม่ดีพอเนื่องจากไม่สามารถจัดการกับกรณีที่มีอักขระ unicode ที่ถูกต้องในสตริงดั้งเดิม ถ้าคุณลอง: >>> print("juancarlo\\tañez".encode('utf-8').decode('unicode_escape')) คุณจะได้รับ: juancarlo añez
Apalala

2
เห็นด้วยกับ @Apalala: นี่ยังไม่ดีพอ ตรวจสอบคำตอบของ rseeper ด้านล่างสำหรับโซลูชันที่สมบูรณ์ที่ใช้งานได้ใน Python2 และ 3
Christian Aichinger

2
เนื่องจากlatin1มีการสันนิษฐานโดยunicode_escapeทำซ้ำบิตเข้ารหัส / ถอดรหัสเช่นs.encode('utf-8').decode('unicode_escape').encode('latin1').decode('utf8')
metatoaster

121

unicode_escape ไม่ทำงานโดยทั่วไป

ปรากฎว่าโซลูชันstring_escapeหรือunicode_escapeวิธีการแก้ปัญหาไม่ทำงานโดยทั่วไป - โดยเฉพาะอย่างยิ่งไม่สามารถใช้งานได้เมื่อมี Unicode จริง

หากคุณสามารถมั่นใจได้ว่าทุกตัวละครที่ไม่ใช่ ASCII จะหนี (และจำอะไรเกิน 128 ตัวอักษรแรกคือไม่ใช่ ASCII) unicode_escapeจะทำสิ่งที่ถูกต้องสำหรับคุณ แต่ถ้ามีอักขระที่ไม่ใช่ ASCII ตามตัวอักษรอยู่แล้วในสตริงของคุณสิ่งต่างๆจะผิดพลาด

unicode_escapeได้รับการออกแบบมาโดยพื้นฐานเพื่อแปลงไบต์เป็นข้อความ Unicode แต่ในหลาย ๆ ที่ - ตัวอย่างเช่นซอร์สโค้ด Python ข้อมูลต้นทางเป็นข้อความ Unicode อยู่แล้ว

วิธีเดียวที่จะทำงานได้อย่างถูกต้องคือถ้าคุณเข้ารหัสข้อความเป็นไบต์ก่อน UTF-8 เป็นการเข้ารหัสที่เหมาะสมสำหรับข้อความทั้งหมดดังนั้นจึงควรใช้งานได้ใช่ไหม

ตัวอย่างต่อไปนี้อยู่ใน Python 3 เพื่อให้ตัวอักษรสตริงสะอาดขึ้น แต่ปัญหาเดียวกันนี้มีอยู่โดยมีอาการต่างกันเล็กน้อยใน Python 2 และ 3

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

ดีที่ผิด

วิธีใหม่ที่แนะนำในการใช้ตัวแปลงสัญญาณที่ถอดรหัสข้อความเป็นข้อความคือการโทรcodecs.decodeโดยตรง ที่ช่วย?

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

ไม่ใช่เลย. (นอกจากนี้ข้างต้นเป็น UnicodeError บน Python 2)

unicode_escapeตัวแปลงสัญญาณแม้จะมีชื่อของมันจะออกมาคิดว่าทุกไบต์ไม่ใช่ ASCII อยู่ใน Latin-1 (ISO-8859-1) การเข้ารหัส ดังนั้นคุณจะต้องทำเช่นนี้:

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

แต่นั่นแย่มาก สิ่งนี้ จำกัด ให้คุณมีอักขระละติน -1 256 ตัวราวกับว่า Unicode ไม่เคยถูกประดิษฐ์ขึ้นมาเลย!

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

การเพิ่มนิพจน์ทั่วไปเพื่อแก้ปัญหา

(น่าแปลกที่ตอนนี้เราไม่มีปัญหาสองข้อ)

สิ่งที่เราต้องทำคือใช้unicode_escapeตัวถอดรหัสกับสิ่งที่เรามั่นใจว่าเป็นข้อความ ASCII เท่านั้น โดยเฉพาะอย่างยิ่งเราสามารถตรวจสอบให้แน่ใจว่าได้นำไปใช้กับลำดับการหลีกเลี่ยง Python ที่ถูกต้องเท่านั้นซึ่งรับประกันว่าเป็นข้อความ ASCII

แผนคือเราจะค้นหาลำดับการหลบหนีโดยใช้นิพจน์ทั่วไปและใช้ฟังก์ชันเป็นอาร์กิวเมนต์re.subเพื่อแทนที่ด้วยค่าที่ไม่ใช้ Escape

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)

และด้วยสิ่งนั้น:

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

2
เราต้องการคำตอบที่ครอบคลุมมากกว่านี้ ขอบคุณ.
v.oddou

ใช้งานos.sepได้หรือไม่ ฉันกำลังพยายามทำสิ่งนี้patt = '^' + self.prefix + os.sep ; name = sub(decode_escapes(patt), '', name)แต่มันไม่ได้ผล อัฒภาคจะแทนที่บรรทัดใหม่
Pureferret

@Pureferret ฉันไม่แน่ใจจริงๆว่าคุณกำลังถามอะไร แต่คุณอาจไม่ควรเรียกใช้สิ่งนี้กับสตริงที่แบ็กสแลชมีความหมายแตกต่างกันเช่นพา ธ ไฟล์ Windows (นั่นคือสิ่งที่คุณos.sepเป็นหรือไม่) หากคุณมีลำดับการหลีกเลี่ยงแบ็กสแลชในชื่อไดเรกทอรี Windows ของคุณสถานการณ์จะไม่สามารถกู้คืนได้ค่อนข้างมาก
rspeer

ลำดับการหลบหนีไม่มีการหลบหนีในพวกเขา แต่ฉันได้รับข้อผิดพลาด 'สตริงการหลบหนีปลอม'
Pureferret

นั่นบอกฉันว่าคุณจบนิพจน์ทั่วไปอื่น ๆ ด้วยแบ็กสแลช: stackoverflow.com/questions/4427174/…
rspeer

33

คำตอบที่ถูกต้องและสะดวกสำหรับ python 3:

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

รายละเอียดเกี่ยวกับcodecs.escape_decode:

  • codecs.escape_decode เป็นตัวถอดรหัสไบต์ต่อไบต์
  • codecs.escape_decodeถอดรหัสลำดับหนี ASCII เช่น: b"\\n"-> b"\n", ->b"\\xce"b"\xce"
  • codecs.escape_decode ไม่สนใจหรือจำเป็นต้องรู้เกี่ยวกับการเข้ารหัสไบต์ของอ็อบเจ็กต์ แต่การเข้ารหัสของไบต์ที่หลีกเลี่ยงควรตรงกับการเข้ารหัสของออบเจ็กต์ที่เหลือ

พื้นหลัง:

  • @rspeerถูกต้อง: unicode_escapeเป็นวิธีแก้ปัญหาที่ไม่ถูกต้องสำหรับ python3 เนื่องจากunicode_escapeถอดรหัสไบต์ที่หลีกเลี่ยงจากนั้นถอดรหัสไบต์เป็นสตริง Unicode แต่ไม่ได้รับข้อมูลเกี่ยวกับตัวแปลงสัญญาณที่จะใช้สำหรับการดำเนินการที่สอง
  • @Jerubถูกต้อง: หลีกเลี่ยง AST หรือ eval
  • ฉันค้นพบครั้งแรกcodecs.escape_decodeจากคำตอบนี้ว่า "ฉันจะถอดรหัส ('string-escape') ใน Python3 ได้อย่างไร? . ตามที่คำตอบระบุว่าฟังก์ชันนั้นไม่ได้รับการบันทึกไว้สำหรับ python 3 ในขณะนี้

นี่คือคำตอบที่แท้จริง (: เสียดายที่ต้องใช้ฟังก์ชันที่มีเอกสารไม่ดี
jwd

5
นี่คือคำตอบสำหรับสถานการณ์ที่ลำดับการหลีกเลี่ยงที่คุณมีคือ Escape \xUTF-8 ไบต์ แต่เนื่องจากมันถอดรหัสไบต์เป็นไบต์จึงไม่ - และไม่สามารถถอดรหัสการหลบหนีของอักขระ Unicode ที่ไม่ใช่ ASCII เช่น\uEscape
rspeer

ในทางเทคนิคแล้วฟังก์ชันนี้จะไม่เผยแพร่ต่อสาธารณะ ดูbugs.python.org/issue30588
Hack5

8

ast.literal_evalฟังก์ชั่นเข้ามาใกล้ แต่มันจะสตริงคาดหวังที่จะยกมาอย่างถูกต้องครั้งแรก

แน่นอนการตีความหลามของหนีทับขวาขึ้นอยู่กับวิธีสตริงที่ยกมา ( ""VS r""VS u"", คำพูดสาม ฯลฯ ) literal_evalดังนั้นคุณอาจต้องการที่จะตัดการป้อนข้อมูลของผู้ใช้ในคำพูดที่เหมาะสมและส่งผ่านไปยัง การห่อด้วยเครื่องหมายคำพูดจะป้องกันไม่ให้literal_evalส่งคืนตัวเลขทูเพิลพจนานุกรม ฯลฯ

สิ่งต่างๆยังคงยุ่งยากหากผู้ใช้พิมพ์เครื่องหมายคำพูดที่ไม่ได้ระบุไว้ในประเภทที่คุณต้องการจะพันรอบสตริง


ฉันเห็น. นี้น่าจะเป็นอันตรายในขณะที่คุณพูดว่า: myString = "\"\ndoBadStuff()\n\"", print(ast.literal_eval('"' + myString + '"'))ดูเหมือนว่าจะพยายามที่จะเรียกใช้รหัส เป็นวิธีการast.literal_evalที่แตกต่างกันใด ๆ / ปลอดภัยกว่าeval?
dln385

5
@ dln385: literal_evalอย่ารันโค้ด จากเอกสารประกอบ "สามารถใช้สำหรับการประเมินสตริงที่มีนิพจน์ Python จากแหล่งที่มาที่ไม่น่าเชื่อถือได้อย่างปลอดภัยโดยไม่จำเป็นต้องแยกวิเคราะห์ค่าด้วยตนเอง"
Greg Hewgill

2

นี่เป็นวิธีที่ไม่ดีในการทำเช่นนี้ แต่ได้ผลสำหรับฉันเมื่อพยายามตีความเลขฐานแปดที่ผ่านการยกเว้นในอาร์กิวเมนต์สตริง

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

ควรค่าแก่การกล่าวถึงว่ามีความแตกต่างระหว่าง eval และ ast.literal_eval (การประเมินเป็นวิธีที่ไม่ปลอดภัยมากกว่า) ดูการใช้ eval () ของ python กับ ast.literal_eval ()?


0

โค้ดด้านล่างควรใช้งานได้สำหรับ \ n จำเป็นต้องแสดงบนสตริง

import string

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

1
สิ่งนี้ใช้ไม่ได้ตามที่เขียนไว้ (เครื่องหมายทับทำให้replaceไม่ต้องทำอะไรเลย) ใช้ API ที่ล้าสมัยอย่างมาก ( stringฟังก์ชันโมดูลของการเรียงลำดับนี้เลิกใช้แล้วเมื่อ Python 2.0 ถูกแทนที่ด้วยstrวิธีการและหายไปอย่างสมบูรณ์ใน Python 3) และเท่านั้น จัดการกรณีเฉพาะของการเปลี่ยนบรรทัดใหม่เดียวไม่ใช่การประมวลผล Escape ทั่วไป
ShadowRanger
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.