ทำไมเราไม่ควรใช้ sys.setdefaultencoding (“ utf-8”) ในสคริปต์ py?


166

ฉันเห็นสคริปต์ py บางตัวซึ่งใช้ที่ด้านบนสุดของสคริปต์ ในกรณีใดควรใช้หรือไม่

import sys
reload(sys)
sys.setdefaultencoding("utf-8")

2
มีปัญหากับการใช้สิ่งนี้ใน ipython% เวลาหยุดทำงานgithub.com/ipython/ipython/issues/8071
seanv507

3
@ seanv507 อ่านคำตอบ - อย่าใช้มันอย่างจริงจัง
Alastair McCormack

5
ที่เกี่ยวข้อง: อันตรายจาก sys.setdefaultencoding ('utf-8')
idbrii

2
นี่เป็นวิธีที่ไม่ซ้ำกันแน่นอนของDangers of sys.setdefaultencoding ('utf-8')อย่างไร แม้ว่าสิ่งนี้ (2010) ถามมาก่อนว่าหนึ่ง (2015)? แต่การถามนั้นก็มีคำตอบที่ดีเช่นกัน จะทำอย่างไร? นอกจากนี้เพื่อความชัดเจนคำถามนี้เหมาะสมกับ Python 2 ไม่ใช่ 3 เท่านั้น แต่ไม่มีการแท็กหรือกล่าวถึงเลย
smci

ควรอ่านก่อนที่จะดำดิ่งลงสู่คำตอบ SO: pythonhosted.org/kitchen/unicode-frrief.html
ccpizza

คำตอบ:


141

ตามเอกสารประกอบ: สิ่งนี้ช่วยให้คุณสามารถเปลี่ยนจาก ASCII เริ่มต้นเป็นการเข้ารหัสอื่น ๆ เช่น UTF-8 ซึ่ง Python runtime จะใช้เมื่อใดก็ตามที่มีการถอดรหัสบัฟเฟอร์สตริงเป็น Unicode

ฟังก์ชั่นนี้ใช้ได้เฉพาะในเวลาเริ่มต้น Python เมื่อ Python สแกนสภาพแวดล้อม มันจะต้องมีการเรียกในโมดูลทั้งระบบsitecustomize.py,, หลังจากโมดูลนี้ได้รับการประเมินsetdefaultencoding()ฟังก์ชั่นจะถูกลบออกจากsysโมดูล

วิธีเดียวที่จะใช้งานได้จริงคือแฮ็กโหลดซ้ำที่นำคุณสมบัติกลับมา

นอกจากนี้การใช้งานsys.setdefaultencoding()นั้นไม่ได้รับการสนับสนุนเสมอมาและมันกลายเป็นสิ่งที่ไม่มี op ใน py3k การเข้ารหัสของ py3k นั้นยากที่จะต่อสายเป็น "utf-8" และการเปลี่ยนมันทำให้เกิดข้อผิดพลาด

ฉันแนะนำตัวชี้บางอย่างสำหรับการอ่าน:


6
สิ่งที่ดีแม้ว่าจะมีข้อมูลเล็กน้อยที่นี่ ฉันได้เรียนรู้มากที่สุดเพียงเน้นบทความนี้: blog.notdot.net/2010/07/Getting-unicode-right-in-Python
mbb

3
ฉันต้องการเพิ่มว่าการเข้ารหัสเริ่มต้นยังใช้สำหรับการเข้ารหัส (เมื่อเขียนถึงsys.stdoutเมื่อมีการNoneเข้ารหัสเช่นเมื่อเปลี่ยนเส้นทางการส่งออกของโปรแกรม Python)
Eric O Lebigot

14
+1 สำหรับ"การใช้งานsys.setdefaultencoding()หมดกำลังใจเสมอ"
jfs

7
'hard-wired to utf-8' ไม่เป็นความจริงมันไม่ใช่การเดินสายและไม่เสมอUTF-8ไป LC_ALL=en_US.UTF-8 python3 -c 'import sys; print(sys.stdout.encoding)'ให้UTF-8แต่LC_ALL=C python3 -c 'import sys; print(sys.stdout.encoding)'ให้ANSI_X3.4-1968(หรืออาจเป็นอย่างอื่น)
Tino

7
@Tino การเข้ารหัสคอนโซลจะแยกเป็นการเข้ารหัสเริ่มต้น
Alastair McCormack

59

TL; DR

คำตอบคือไม่เคย ! (เว้นแต่คุณจะรู้ว่ากำลังทำอะไรอยู่)

9/10 เท่าของวิธีการแก้ปัญหาสามารถแก้ไขได้ด้วยความเข้าใจที่เหมาะสมของการเข้ารหัส / ถอดรหัส

1/10 คนมีสถานที่หรือสภาพแวดล้อมที่กำหนดไว้ไม่ถูกต้องและจำเป็นต้องตั้งค่า:

PYTHONIOENCODING="UTF-8"  

ในสภาพแวดล้อมของพวกเขาเพื่อแก้ไขปัญหาการพิมพ์คอนโซล

มันทำอะไร?

sys.setdefaultencoding("utf-8")(ขีดผ่านเพื่อหลีกเลี่ยงการใช้ซ้ำ) เปลี่ยนการเข้ารหัส / ถอดรหัสเริ่มต้นที่ใช้เมื่อใดก็ตามที่ Python 2.x จำเป็นต้องแปลง Unicode () เป็น str () (และในทางกลับกัน) และการเข้ารหัสไม่ได้รับ เช่น:

str(u"\u20AC")
unicode("€")
"{}".format(u"\u20AC") 

ใน Python 2.x การเข้ารหัสเริ่มต้นถูกตั้งค่าเป็น ASCII และตัวอย่างข้างต้นจะล้มเหลวด้วย:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)

(คอนโซลของฉันถูกกำหนดค่าเป็น UTF-8 ดังนั้น"€" = '\xe2\x82\xac'ข้อยกเว้นนี้\xe2)

หรือ

UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)

sys.setdefaultencoding("utf-8")จะอนุญาตให้ใช้งานได้สำหรับฉันแต่จะไม่จำเป็นสำหรับผู้ที่ไม่ได้ใช้ UTF-8 ค่าเริ่มต้นของ ASCII ทำให้มั่นใจได้ว่าสมมติฐานของการเข้ารหัสไม่ได้ถูกอบเข้าสู่โค้ด

ปลอบใจ

sys.setdefaultencoding("utf-8")ยังมีผลข้างเคียงของการปรากฏเพื่อแก้ไขsys.stdout.encodingใช้เมื่อพิมพ์อักขระลงในคอนโซล Python ใช้ locale ของผู้ใช้ (Linux / OS X / Un * x) หรือ codepage (Windows) เพื่อตั้งค่านี้ บางครั้งสถานที่ของผู้ใช้เสียและเพียงแค่ต้องใช้PYTHONIOENCODINGในการแก้ไขปัญหาการเข้ารหัสคอนโซล

ตัวอย่าง:

$ export LANG=en_GB.gibberish
$ python
>>> import sys
>>> sys.stdout.encoding
'ANSI_X3.4-1968'
>>> print u"\u20AC"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u20ac' in position 0: ordinal not in range(128)
>>> exit()

$ PYTHONIOENCODING=UTF-8 python
>>> import sys
>>> sys.stdout.encoding
'UTF-8'
>>> print u"\u20AC"
€

มีอะไรเลวร้ายกับsys.setdefaultencoding ("utf-8") ?

ผู้คนพัฒนากับ Python 2.x เป็นเวลา 16 ปีในการทำความเข้าใจว่าการเข้ารหัสเริ่มต้นคือ ASCII UnicodeErrorมีการเขียนวิธีการจัดการข้อยกเว้นเพื่อจัดการสตริงเป็น Conversion แบบ Unicode บนสตริงที่พบว่าไม่มี ASCII

จากhttps://anonbadger.wordpress.com/2015/06/16/why-sys-setdefaultencoding-will-break-code/

def welcome_message(byte_string):
    try:
        return u"%s runs your business" % byte_string
    except UnicodeError:
        return u"%s runs your business" % unicode(byte_string,
            encoding=detect_encoding(byte_string))

print(welcome_message(u"Angstrom (Å®)".encode("latin-1"))

ก่อนหน้าการตั้งค่าเริ่มต้นการเข้ารหัสรหัสนี้จะไม่สามารถถอดรหัส“ Å” ในการเข้ารหัส ascii จากนั้นจะป้อนตัวจัดการข้อยกเว้นเพื่อคาดเดาการเข้ารหัสและเปลี่ยนเป็นยูนิโค้ดอย่างถูกต้อง การพิมพ์: อังสตรอม (Å®) ดำเนินธุรกิจของคุณ เมื่อคุณตั้งค่าการเข้ารหัสเริ่มต้นเป็น utf-8 แล้วโค้ดจะพบว่า byte_string สามารถตีความได้ว่าเป็น utf-8 ดังนั้นมันจะทำให้ข้อมูลยุ่งเหยิงและส่งคืนสิ่งนี้แทน: Angstrom (Ů) ดำเนินธุรกิจของคุณ

การเปลี่ยนแปลงสิ่งที่ควรเป็นค่าคงที่จะมีผลอย่างมากต่อโมดูลที่คุณพึ่งพา ดีกว่าที่จะแก้ไขข้อมูลที่เข้าและออกจากรหัสของคุณ

ตัวอย่างปัญหา

ในขณะที่การตั้งค่าเริ่มต้นการเข้ารหัสเป็น UTF-8 ไม่ใช่รากสาเหตุในตัวอย่างต่อไปนี้มันแสดงให้เห็นว่าปัญหาถูกหลอกลวงและอย่างไรเมื่อการเข้ารหัสการป้อนข้อมูลเปลี่ยนแปลงรหัสจะแบ่งตัวในลักษณะที่ไม่เกรงกลัว: UnicodeDecodeError: 'utf8' ไม่ต้องถอดรหัส 0x80 ในตำแหน่ง 3131: ไบต์เริ่มต้นที่ไม่ถูกต้อง


2
ในขณะที่มีเรื่องที่น่าประหลาดใจsys.setdefaultencoding("utf-8")ก็เป็นการดีที่จะทำให้โค้ดทำงานเหมือน Python 3 ตอนนี้เป็นปี 2017 แล้ว แม้ว่าคุณจะเขียนคำตอบกลับไปในปี 2558 ฉันคิดว่ามันจะดีกว่าถ้ามองไปข้างหน้าแทนที่จะถอยหลัง มันเป็นทางออกที่ง่ายที่สุดสำหรับฉันเมื่อฉันพบว่าโค้ดของฉันทำงานแตกต่างกันใน Python 2 ขึ้นอยู่กับว่าเอาต์พุตถูกเปลี่ยนเส้นทางหรือไม่ (เป็นปัญหาที่น่ารังเกียจสำหรับ Python 2) ไม่จำเป็นต้องพูดว่าฉันมีอยู่แล้ว# coding: utf-8และฉันไม่ต้องการแก้ไขปัญหาใด ๆ สำหรับ Python 3 (จริง ๆ แล้วฉันต้องปิดบังการsetdefaultencodingตรวจสอบรุ่นที่ใช้)
Yongwei Wu

มันยอดเยี่ยมมากและใช้งานได้ดีสำหรับคุณ แต่sys.setdefaultencoding("utf-8")ไม่ได้ทำให้รหัส Py 2.x ของคุณเข้ากันได้กับ Python 3 และไม่แก้ไขโมดูลภายนอกที่ถือว่าการเข้ารหัสเริ่มต้นคือ ASCII การทำให้โค้ดของคุณรองรับ Python 3 นั้นง่ายมากและไม่ต้องการแฮ็คที่น่ารังเกียจนี้ ตัวอย่างเช่นสาเหตุที่ทำให้เกิดปัญหาจริงลองดูประสบการณ์ของฉันกับ Amazon ที่ยุ่งกับสมมติฐานนี้: stackoverflow.com/questions/39465220//
Alastair McCormack

1
@AlastairMcCackack คุณสนุกไปกับเว็บไซต์ของฉันมาหลายเดือนแล้วและไม่สามารถคิดได้ว่าจะทำอย่างไร ในที่สุดPYTHONIOENCODING="UTF-8"ช่วยให้สภาพแวดล้อม Python2.7 Django-1.11 ของฉันดีขึ้น ขอบคุณ
แซม

ฉันรู้ว่าคุณคัดลอกตัวอย่าง detect_encodingแต่ฉันสามารถค้นหาสิ่งที่แพคเกจมี
dlamblin

@dlamblin ตัวอย่างรหัสคือการพิสูจน์คำพูดและไม่ควรใช้ในรหัสของคุณ ลองจินตนาการว่าdetect_encodingเป็นวิธีการที่สามารถตรวจจับการเข้ารหัสของสตริงตามเบาะแสภาษา
Alastair McCormack

18
#!/usr/bin/env python
#-*- coding: utf-8 -*-
u = u'moçambique'
print u.encode("utf-8")
print u

chmod +x test.py
./test.py
moçambique
moçambique

./test.py > output.txt
Traceback (most recent call last):
  File "./test.py", line 5, in <module>
    print u
UnicodeEncodeError: 'ascii' codec can't encode character 
u'\xe7' in position 2: ordinal not in range(128)

บนเชลล์ทำงานส่งไปยัง sdtout ไม่ได้ดังนั้นนั่นเป็นวิธีแก้ไขปัญหาเดียวเพื่อเขียนไปยัง stdout

ฉันใช้วิธีการอื่นซึ่งไม่ได้ทำงานหาก sys.stdout.encoding ไม่ได้กำหนดไว้หรือในคำอื่น ๆ จำเป็นต้องส่งออก PYTHONIOENCODING = UTF-8 ก่อนเพื่อเขียนไปยัง stdout

import sys
if (sys.stdout.encoding is None):            
    print >> sys.stderr, "please set python env PYTHONIOENCODING=UTF-8, example: export PYTHONIOENCODING=UTF-8, when write to stdout." 
    exit(1)


ดังนั้นใช้ตัวอย่างเดียวกัน:

export PYTHONIOENCODING=UTF-8
./test.py > output.txt

จะทำงาน


3
สิ่งนี้ไม่ตอบคำถามตามที่ถาม ค่อนข้างความคิดสัมผัสในเรื่อง
ivan_pozdeev

3
  • reload(sys)ครั้งแรกที่อันตรายอยู่ใน

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

    (This is IPython shell)
    
    In [1]: import sys
    
    In [2]: sys.stdout
    Out[2]: <colorama.ansitowin32.StreamWrapper at 0x3a2aac8>
    
    In [3]: reload(sys)
    <module 'sys' (built-in)>
    
    In [4]: sys.stdout
    Out[4]: <open file '<stdout>', mode 'w' at 0x00000000022E20C0>
    
    In [11]: import IPython.terminal
    
    In [14]: IPython.terminal.interactiveshell.sys.stdout
    Out[14]: <colorama.ansitowin32.StreamWrapper at 0x3a9aac8>
  • ตอนนี้sys.setdefaultencoding()เหมาะสม

    str<->unicodeทุกสิ่งที่มันมีผลต่อการแปลงเป็นนัย ตอนนี้utf-8การเข้ารหัส sanest บนดาวเคราะห์ (เข้ากันได้กับ ASCII และย้อนหลัง) การแปลงตอนนี้ "เพิ่งได้ผล" สิ่งที่อาจผิดไป?

    อะไรก็ได้ และนั่นคืออันตราย

    • อาจมีรหัสบางส่วนที่ขึ้นอยู่กับUnicodeErrorการโยนสำหรับอินพุตที่ไม่ใช่ ASCII หรือการแปลงรหัสด้วยตัวจัดการข้อผิดพลาดซึ่งตอนนี้สร้างผลลัพธ์ที่ไม่คาดคิด และเนื่องจากรหัสทั้งหมดได้รับการทดสอบด้วยการตั้งค่าเริ่มต้นคุณจะอยู่ในอาณาเขต "ไม่ได้รับการสนับสนุน" ที่นี่อย่างเคร่งครัดและไม่มีผู้ใดให้การรับประกันแก่คุณเกี่ยวกับการทำงานของรหัสของพวกเขา
    • การแปลงรหัสอาจให้ผลลัพธ์ที่คาดไม่ถึงหรือใช้ไม่ได้หากไม่ใช่ทุกอย่างในระบบที่ใช้ UTF-8 เพราะ Python 2 จริง ๆ แล้วมี "การเข้ารหัสสตริงเริ่มต้น"หลายตัว (โปรดจำไว้ว่าโปรแกรมต้องทำงานให้กับลูกค้าในอุปกรณ์ของลูกค้า)
      • สิ่งที่เลวร้ายที่สุดคือคุณจะไม่มีทางรู้ว่าเพราะการแปลงนั้นมีความหมายโดยปริยายคุณไม่รู้จริงๆว่ามันเกิดขึ้นเมื่อใดและที่ไหน (Python Zen, koan 2 ahoy!) คุณจะไม่มีทางรู้ว่าทำไม (และถ้า) รหัสของคุณทำงานบนระบบใดระบบหนึ่งและหยุดอีกระบบหนึ่ง (หรือดีกว่ายังทำงานใน IDE และหยุดพักในคอนโซล)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.