เปลี่ยนการเข้ารหัสเริ่มต้นของ Python หรือไม่


143

ฉันมีปัญหา "ไม่สามารถเข้ารหัส" และ "ถอดรหัสไม่ได้" กับPythonเมื่อฉันเรียกใช้แอปพลิเคชันของฉันจากคอนโซล แต่ในEclipse PyDev IDE การเข้ารหัสอักขระเริ่มต้นถูกตั้งค่าเป็นUTF-8และฉันไม่เป็นไร

ฉันค้นหารอบ ๆ เพื่อตั้งค่าการเข้ารหัสเริ่มต้นและผู้คนบอกว่า Python ลบsys.setdefaultencodingฟังก์ชันเมื่อเริ่มต้นและเราไม่สามารถใช้งานได้

ดังนั้นทางออกที่ดีที่สุดสำหรับมันคืออะไร?


1
ดูบล็อกโพสต์setdefaultencoding Illusive
djc

3
The best solution is to learn to use encode and decode correctly instead of using hacks.นี่เป็นไปได้อย่างแน่นอนกับpython2ด้วยค่าใช้จ่ายในการจดจำ / การใช้อินเทอร์เฟซของคุณเองอย่างสม่ำเสมอ ประสบการณ์ของฉันแนะนำว่าสิ่งนี้จะเป็นปัญหาอย่างมากเมื่อคุณกำลังเขียนโค้ดที่คุณต้องการทำงานกับทั้ง python2 และ python3
Att Righ

คำตอบ:


159

ต่อไปนี้เป็นวิธีที่ง่ายกว่า (แฮ็ค) ที่ให้setdefaultencoding()ฟังก์ชันที่ลบไปจากคุณsys:

import sys
# sys.setdefaultencoding() does not exist, here!
reload(sys)  # Reload does the trick!
sys.setdefaultencoding('UTF8')

(หมายเหตุสำหรับ Python 3.4+: reload()อยู่ในimportlibไลบรารี)

นี่ไม่ใช่สิ่งที่ปลอดภัยที่ต้องทำแต่นี่เป็นแฮ็คที่เห็นได้ชัดเนื่องจากsys.setdefaultencoding()ถูกลบโดยเจตนาsysเมื่อเริ่มต้น Python การเปิดใช้งานใหม่และการเปลี่ยนการเข้ารหัสเริ่มต้นสามารถทำลายรหัสที่อาศัย ASCII เป็นค่าเริ่มต้น (รหัสนี้อาจเป็นของบุคคลที่สามซึ่งโดยทั่วไปจะทำให้การแก้ไขเป็นไปไม่ได้หรือเป็นอันตราย)


5
ฉันลงคะแนนเนื่องจากคำตอบนั้นไม่ได้ช่วยในการเรียกใช้แอปพลิเคชันที่มีอยู่ (ซึ่งเป็นวิธีหนึ่งในการตีความคำถาม) ผิดเมื่อคุณเขียน / ดูแลแอปพลิเคชันและอันตรายเมื่อเขียนไลบรารี วิธีที่เหมาะสมคือการตั้งค่าLC_CTYPE(หรือในแอปพลิเคชันตรวจสอบว่าตั้งค่าถูกต้องและยกเลิกด้วยข้อความแสดงข้อผิดพลาดที่มีความหมาย)
ibotty

@ibotty ฉันยอมรับว่าคำตอบนี้เป็นแฮ็กและเป็นอันตรายที่จะใช้งาน มันจะตอบคำถาม แต่ ("เปลี่ยนการเข้ารหัสเริ่มต้นของ Python?") คุณมีการอ้างอิงเกี่ยวกับผลกระทบของตัวแปรสภาพแวดล้อม LC_CTYPE ใน Python interpreter หรือไม่?
Eric O Lebigot

มันไม่ได้พูดถึงมันแฮ็คตอนแรก นอกเหนือจากนั้นคำตอบที่เป็นอันตรายที่ไม่มีการเอ่ยถึงถึงพวกเขาจะไม่เป็นประโยชน์
ibotty

1
@EOL คุณพูดถูก มันจะมีผล preferredencoding แม้ว่า (ในหลาม 2 และ 3):LC_CTYPE=C python -c 'import locale; print( locale.getpreferredencoding())'
ibotty

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

70

หากคุณได้รับข้อผิดพลาดนี้เมื่อคุณพยายามที่จะไพพ์ / เอาท์พุทการเปลี่ยนเส้นทางของสคริปต์ของคุณ

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)

เพียงส่งออก PYTHONIOENCODING ในคอนโซลแล้วเรียกใช้รหัสของคุณ

export PYTHONIOENCODING=utf8


3
นี่เป็นทางออกเดียวที่สร้างความแตกต่างให้ฉัน - ฉันใช้ Debian 7 ด้วยการตั้งค่าภาษาที่ใช้งานไม่ได้ ขอบคุณ
Pryo

4
ตั้งค่าLC_CTYPEเป็นสิ่งที่เหมาะสมแทน มันทำให้โปรแกรมอื่น ๆ ทั้งหมดมีความสุขเช่นกัน
ibotty

5
จุดบกพร่องที่ใหญ่กว่าใน Python3 คือนั่นPYTHONIOENCODING=utf8ไม่ใช่ค่าเริ่มต้น สิ่งนี้ทำให้สคริปต์หยุดชะงักเพราะLC_ALL=C
Tino

Set LC_CTYPE to something sensible insteadนี่เป็นข้อเสนอแนะที่สมเหตุสมผล วิธีนี้ใช้ไม่ได้ผลเมื่อคุณพยายามแจกจ่ายรหัสที่ใช้งานได้กับระบบของบุคคลอื่น
Att Righ

เดเบียนและระบบปฏิบัติการ Redhat ใช้C.utf8โลแคลเพื่อให้ C. glibc upstream ทำงานได้ดีขึ้นกำลังเพิ่มมันดังนั้นบางทีเราไม่ควรโทษ Python สำหรับการตั้งค่า locale \ …?
Arthur2e5

52

A) เพื่อควบคุมsys.getdefaultencoding()ผลลัพธ์:

python -c 'import sys; print(sys.getdefaultencoding())'

ascii

แล้วก็

echo "import sys; sys.setdefaultencoding('utf-16-be')" > sitecustomize.py

และ

PYTHONPATH=".:$PYTHONPATH" python -c 'import sys; print(sys.getdefaultencoding())'

utf-16-be

คุณสามารถกำหนดsitecustomize.pyของคุณให้สูงขึ้นPYTHONPATHได้

นอกจากนี้คุณอาจต้องการลองreload(sys).setdefaultencodingโดย @EOL

B) เพื่อควบคุมstdin.encodingและstdout.encodingคุณต้องการตั้งค่าPYTHONIOENCODING:

python -c 'import sys; print(sys.stdin.encoding, sys.stdout.encoding)'

ascii ascii

แล้วก็

PYTHONIOENCODING="utf-16-be" python -c 'import sys; 
print(sys.stdin.encoding, sys.stdout.encoding)'

utf-16-be utf-16-be

ในที่สุด:คุณสามารถใช้A)หรือB)หรือทั้งสองอย่าง!


(python2 เท่านั้น) แยกกัน แต่น่าสนใจกำลังขยายด้านบนด้วยการfrom __future__ import unicode_literalsดูการสนทนา
lukmdo

17

เริ่มต้นด้วยPyDev 3.4.1 การเข้ารหัสเริ่มต้นจะไม่เปลี่ยนอีกต่อไป ดูตั๋วนี้สำหรับรายละเอียด

สำหรับเวอร์ชั่นก่อนหน้านี้ทางออกคือต้องแน่ใจว่า PyDev ไม่ได้ทำงานด้วย UTF-8 เป็นการเข้ารหัสเริ่มต้น ภายใต้ Eclipse ให้เรียกใช้การตั้งค่าการโต้ตอบ ("เรียกใช้การกำหนดค่า" หากฉันจำได้อย่างถูกต้อง) คุณสามารถเลือกการเข้ารหัสเริ่มต้นบนแท็บทั่วไป เปลี่ยนเป็น US-ASCII หากคุณต้องการให้ข้อผิดพลาดเหล่านี้ 'เร็ว' (กล่าวอีกนัยหนึ่ง: ในสภาพแวดล้อม PyDev ของคุณ) ดูโพสต์บล็อกดั้งเดิมสำหรับวิธีแก้ปัญหานี้


1
ขอบคุณคริส โดยเฉพาะเมื่อพิจารณาความคิดเห็นของ Mark T ด้านบนคำตอบของคุณน่าจะเหมาะสมที่สุดสำหรับฉัน และสำหรับคนที่ไม่ได้เป็นผู้ใช้ Eclipse / PyDev เป็นหลักฉันจะไม่สามารถหาสิ่งนั้นได้ด้วยตัวเอง
ฌอน

ฉันต้องการที่จะเปลี่ยนแปลงนี้ทั่วโลก (มากกว่าหนึ่งครั้งต่อการกำหนดค่าการเรียกใช้) แต่ยังไม่ได้หาวิธี - ได้ถามคำถามที่แยกต่างหาก q: stackoverflow.com/questions/9394277/ …
ทิม Diggins

13

เกี่ยวกับ python2 (และ python2 เท่านั้น) คำตอบเดิมบางคำนั้นใช้แฮ็คต่อไปนี้:

import sys
reload(sys)  # Reload is a hack
sys.setdefaultencoding('UTF8')

ไม่แนะนำให้ใช้ (ทำเครื่องหมายที่นี่หรือสิ่งนี้ )

ในกรณีของฉันมันมาพร้อมกับผลข้างเคียง: ฉันใช้โน้ตบุ๊ก ipython และเมื่อฉันเรียกใช้รหัสฟังก์ชัน´print´ จะไม่ทำงานอีกต่อไป ฉันเดาว่าจะมีวิธีแก้ปัญหา แต่ก็ยังคิดว่าการใช้แฮ็คไม่ควรเป็นตัวเลือกที่ถูกต้อง

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

ดังนั้นทางออกคือการผนวกไฟล์/usr/lib/python2.7/sitecustomize.pyรหัส:

import sys
sys.setdefaultencoding('UTF8')

เมื่อฉันใช้ virtualenvwrapper ~/.virtualenvs/venv-name/lib/python2.7/sitecustomize.pyไฟล์ที่ผมแก้ไขคือ

และเมื่อฉันใช้กับ python notebooks และ conda ก็เป็นได้ ~/anaconda2/lib/python2.7/sitecustomize.py


8

มีโพสต์บล็อกที่ชาญฉลาดเกี่ยวกับมัน

ดูhttps://anonbadger.wordpress.com/2015/06/16/why-sys-setdefaultencoding-will-break-code/

ฉันถอดความเนื้อหาด้านล่าง

ใน python 2 ซึ่งไม่ได้พิมพ์อย่างมากเกี่ยวกับการเข้ารหัสสตริงคุณสามารถดำเนินการกับสตริงที่เข้ารหัสต่างกันและประสบความสำเร็จ Trueเช่นต่อไปนี้จะกลับมา

u'Toshio' == 'Toshio'

ที่จะเก็บไว้สำหรับสตริง (ปกติ, ไม่ต้องเตรียมการ) ที่ถูกเข้ารหัสsys.getdefaultencoding()ซึ่งเป็นค่าเริ่มasciiต้น แต่ไม่ใช่สายอื่น ๆ

การเข้ารหัสเริ่มต้นนั้นหมายถึงการเปลี่ยนแปลงทั้งระบบsite.pyแต่ไม่ใช่ที่อื่น แฮ็ก (นำเสนอที่นี่) เพื่อตั้งค่าในโมดูลผู้ใช้เป็นแบบนั้น: แฮ็กไม่ใช่โซลูชัน

Python 3 เปลี่ยนการเข้ารหัสระบบเป็นค่าเริ่มต้นเป็น utf-8 (เมื่อ LC_CTYPE รับรู้ถึงยูนิโค้ด) แต่ปัญหาพื้นฐานได้รับการแก้ไขด้วยข้อกำหนดในการเข้ารหัสสตริง "ไบต์" อย่างชัดเจนทุกครั้งที่ใช้กับสตริง Unicode


4

ครั้งแรก: reload(sys)และการตั้งค่าการเข้ารหัสเริ่มต้นแบบสุ่มเพียงแค่เกี่ยวกับความต้องการของกระแสข้อมูลเทอร์มินัลเอาท์พุทคือการปฏิบัติที่ไม่ดี reloadมักจะเปลี่ยนสิ่งต่าง ๆ ใน sys ซึ่งวางไว้ในสภาพแวดล้อม - เช่น sys.stdin / stdout ลำธาร sys.excepthook ฯลฯ

การแก้ปัญหาการเข้ารหัสใน stdout

ทางออกที่ดีที่สุดที่ฉันรู้ในการแก้ปัญหาการเข้ารหัสของprint'ing unicode strings และ Beyond-ascii str' s (เช่นจากตัวอักษร) ใน sys.stdout คือ: การดูแล sys.stdout (วัตถุคล้ายไฟล์) ซึ่งมีความสามารถและ เผื่อเลือกเกี่ยวกับความต้องการ:

  • เมื่อsys.stdout.encodingมีNoneเหตุผลบางอย่างหรือไม่ใช่ที่มีอยู่หรือเท็จผิดหรือ "น้อย" กว่าสิ่งที่เทอร์มินัล stdout หรือสตรีมมีความสามารถจริง ๆ แล้วลองจัดหา.encodingแอตทริบิวต์ที่ถูกต้อง ในที่สุดโดยการแทนที่sys.stdout & sys.stderrโดยวัตถุเหมือนไฟล์แปล

  • เมื่อเทอร์มินัล / สตรีมยังไม่สามารถเข้ารหัสตัวอักษรแบบ unicode ทั้งหมดที่เกิดขึ้นและเมื่อคุณไม่ต้องการทำลายprintเพราะสิ่งนั้นคุณสามารถนำเสนอพฤติกรรมแบบเข้ารหัสพร้อมแทนที่ในวัตถุคล้ายไฟล์แปล

นี่คือตัวอย่าง:

#!/usr/bin/env python
# encoding: utf-8
import sys

class SmartStdout:
    def __init__(self, encoding=None, org_stdout=None):
        if org_stdout is None:
            org_stdout = getattr(sys.stdout, 'org_stdout', sys.stdout)
        self.org_stdout = org_stdout
        self.encoding = encoding or \
                        getattr(org_stdout, 'encoding', None) or 'utf-8'
    def write(self, s):
        self.org_stdout.write(s.encode(self.encoding, 'backslashreplace'))
    def __getattr__(self, name):
        return getattr(self.org_stdout, name)

if __name__ == '__main__':
    if sys.stdout.isatty():
        sys.stdout = sys.stderr = SmartStdout()

    us = u'aouäöüфżß²'
    print us
    sys.stdout.flush()

การใช้ตัวอักษรสตริงแบบธรรมดาเกิน ASCII ในรหัส Python 2/2 + 3

เหตุผลเดียวที่ดีในการเปลี่ยนการเข้ารหัสเริ่มต้นทั่วโลก (เป็น UTF-8 เท่านั้น) ฉันคิดว่าเกี่ยวข้องกับการตัดสินใจซอร์สโค้ดของแอปพลิเคชัน- และไม่ใช่เพราะปัญหาการเข้ารหัส I / O สตรีม: สำหรับการเขียนตัวอักษรสตริงที่เกิน ascii ลงในรหัส เพื่อใช้การu'string'หลบหนีแบบ Unicode สไตล์เสมอ สิ่งนี้สามารถทำได้ค่อนข้างสม่ำเสมอ (แม้จะมีบทความของanonbadgerกล่าวไว้) โดยการดูแลพื้นฐานซอร์สโค้ด Python 2 หรือ Python 2 + 3 ซึ่งใช้ตัวอักษรสตริงธรรมดา ASCII หรือ UTF-8 อย่างสม่ำเสมอ - ตราบใดที่สตริงเหล่านั้นอาจถูกปิดเสียง แปลง Unicode และย้ายไปมาระหว่างโมดูลหรืออาจไปที่ stdout สำหรับที่ต้องการ "# encoding: utf-8"หรือ ascii (ไม่มีการประกาศ) เปลี่ยนหรือวางไลบรารี่ซึ่งยังคงพึ่งพาวิธีที่โง่มากในการเข้ารหัสข้อผิดพลาดเริ่มต้นของ ASCII เกิน chr # 127 (ซึ่งหาได้ยากในปัจจุบัน)

และทำเช่นนี้เมื่อเริ่มต้นแอปพลิเคชัน (และ / หรือผ่าน sitecustomize.py) นอกเหนือจากSmartStdoutรูปแบบข้างต้น - โดยไม่ต้องใช้reload(sys):

...
def set_defaultencoding_globally(encoding='utf-8'):
    assert sys.getdefaultencoding() in ('ascii', 'mbcs', encoding)
    import imp
    _sys_org = imp.load_dynamic('_sys_org', 'sys')
    _sys_org.setdefaultencoding(encoding)

if __name__ == '__main__':
    sys.stdout = sys.stderr = SmartStdout()
    set_defaultencoding_globally('utf-8') 
    s = 'aouäöüфżß²'
    print s

วิธีนี้จะใช้ตัวอักษรสตริงและการดำเนินการส่วนใหญ่ (ยกเว้นการวนซ้ำตัวอักษร) ทำงานได้อย่างสะดวกสบายโดยไม่ต้องนึกถึงการแปลง Unicode ราวกับว่าจะมี Python3 เท่านั้น แน่นอนว่าไฟล์ I / O นั้นจะต้องมีความระมัดระวังเป็นพิเศษเกี่ยวกับการเข้ารหัสเนื่องจากมันอยู่ใน Python3

หมายเหตุ: สตริงของ plains นั้นจะถูกแปลงแบบไม่แน่นอนจาก utf-8 เป็น unicode SmartStdoutก่อนที่จะถูกแปลงเป็นเอาต์พุตสตรีมที่เข้ารหัส


4

นี่คือวิธีที่ฉันใช้ในการสร้างรหัสที่เข้ากันได้กับทั้งpython2และpython3และสร้างผลลัพธ์utf8เสมอ ฉันพบคำตอบนี้ที่อื่น แต่ไม่สามารถจำแหล่งที่มาได้

วิธีนี้ใช้งานได้โดยแทนที่sys.stdoutด้วยบางสิ่งที่ไม่เหมือนไฟล์เลยทีเดียว (แต่ยังคงใช้สิ่งต่าง ๆ ในไลบรารีมาตรฐานเท่านั้น) สิ่งนี้อาจทำให้เกิดปัญหากับไลบรารี่พื้นฐานของคุณได้ แต่ในกรณีง่าย ๆ ที่คุณสามารถควบคุมว่า sys.stdout จะถูกใช้ผ่านเฟรมเวิร์กของคุณได้อย่างไรนี่อาจเป็นแนวทางที่สมเหตุสมผล

sys.stdout = io.open(sys.stdout.fileno(), 'w', encoding='utf8')


1

นี่เป็นแฮ็คด่วนสำหรับทุกคนที่ (1) บนแพลตฟอร์ม Windows (2) ที่ใช้งาน Python 2.7 และ (3) รำคาญเพราะซอฟต์แวร์ที่ดี (เช่นคุณไม่ได้เขียนโดยคุณดังนั้นจึงไม่ใช่ผู้สมัครสำหรับการพิมพ์เข้ารหัส / ถอดรหัสทันที ประลองยุทธ์) จะไม่แสดง "ตัวอักษร Unicode สวย" ในสภาพแวดล้อมที่ไม่ได้ใช้งาน (PythonWin พิมพ์ Unicode ละเอียด) ตัวอย่างเช่นสัญลักษณ์เรียบร้อยการสั่งซื้อลอจิกเป็นครั้งแรกที่สเตฟานบอยเยอร์ที่ใช้ในการส่งออกจาก prover สอนของเขาที่ครั้งแรกที่สั่งซื้อลอจิก Prover

ฉันไม่ชอบความคิดในการบังคับให้โหลด sys และฉันไม่สามารถให้ระบบร่วมมือกับการตั้งค่าตัวแปรสภาพแวดล้อมเช่น PYTHONIOENCODING (ลองใช้ตัวแปรสภาพแวดล้อมของ Windows โดยตรงและยังวางไว้ใน sitecustomize.py ในไซต์แพ็กเกจเป็นหนึ่ง liner = 'utf-8')

ดังนั้นหากคุณยินดีที่จะแฮ็คไปสู่ความสำเร็จให้ไปที่ไดเรกทอรี IDLE ของคุณโดยทั่วไป: "C: \ Python27 \ Lib \ idlelib" ค้นหาไฟล์ IOBinding.py ทำสำเนาของไฟล์นั้นและเก็บไว้ที่อื่นเพื่อให้คุณสามารถย้อนกลับไปสู่พฤติกรรมดั้งเดิมเมื่อคุณเลือก เปิดไฟล์ใน idlelib ด้วยโปรแกรมแก้ไข (เช่น IDLE) ไปที่พื้นที่รหัสนี้:

# Encoding for file names
filesystemencoding = sys.getfilesystemencoding()

encoding = "ascii"
if sys.platform == 'win32':
    # On Windows, we could use "mbcs". However, to give the user
    # a portable encoding name, we need to find the code page 
    try:
        # --> 6/5/17 hack to force IDLE to display utf-8 rather than cp1252
        # --> encoding = locale.getdefaultlocale()[1]
        encoding = 'utf-8'
        codecs.lookup(encoding)
    except LookupError:
        pass

กล่าวอีกนัยหนึ่งออกความคิดเห็นบรรทัดรหัสต้นฉบับตาม ' ลอง ' ที่ทำให้ตัวแปรการเข้ารหัสเท่ากับlocale.getdefaultlocale (เพราะจะให้ cp1252 ที่คุณไม่ต้องการ) และแทนที่จะบังคับให้ 'utf-8 '(โดยเพิ่มบรรทัด' encoding = 'utf-8 ' ตามที่แสดง)

ฉันเชื่อว่าสิ่งนี้จะส่งผลกระทบต่อ IDLE ที่แสดงเป็น stdout เท่านั้นไม่ใช่การเข้ารหัสที่ใช้สำหรับชื่อไฟล์เป็นต้น หากคุณมีปัญหากับรหัสอื่น ๆ ที่คุณเรียกใช้ใน IDLE ในภายหลังเพียงแค่แทนที่ไฟล์ IOBinding.py ด้วยไฟล์ที่ไม่ได้แก้ไขดั้งเดิม


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.