Tại sao chúng ta KHÔNG nên sử dụng sys.setdefaultencoding (Hiện tại utf-8) trong một tập lệnh py?


166

Tôi đã thấy một vài tập lệnh py sử dụng cái này ở đầu tập lệnh. Trong trường hợp nào người ta nên sử dụng nó?

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

2
có vấn đề với việc sử dụng điều này trong ipython,% time ngừng hoạt động github.com/ipython/ipython/issues/8071
seanv507

3
@ seanv507, đọc câu trả lời - sử dụng nó được khuyến khích nghiêm túc
Alastair McCormack


2
Làm thế nào đây không phải là một bản sao chính xác của Nguy hiểm của sys.setdefaultencoding ('utf-8') ? Mặc dù điều này (2010) hỏi trước đó (2015)? Nhưng câu hỏi đó cũng có câu trả lời tốt. Phải làm sao Ngoài ra, để rõ ràng, câu hỏi này chỉ có ý nghĩa trên Python 2 chứ không phải 3, nhưng đó không phải là nơi được gắn thẻ hoặc đề cập.
smci

đáng đọc trước khi đi sâu vào câu trả lời của SO: pythonhosted.org/kove/unicode-frustations.html
ccpizza

Câu trả lời:


141

Theo tài liệu: Điều này cho phép bạn chuyển từ ASCII mặc định sang các mã hóa khác như UTF-8, thời gian chạy Python sẽ sử dụng bất cứ khi nào nó phải giải mã bộ đệm chuỗi thành unicode.

Hàm này chỉ khả dụng tại thời điểm khởi động Python, khi Python quét môi trường. Nó phải được gọi trong một mô-đun toàn hệ thống sitecustomize.py, Sau khi mô-đun này được đánh giá, setdefaultencoding()chức năng sẽ bị xóa khỏi sysmô-đun.

Cách duy nhất để thực sự sử dụng nó là với một bản hack tải lại mang thuộc tính trở lại.

Ngoài ra, việc sử dụng sys.setdefaultencoding()luôn luôn được khuyến khích , và nó đã trở thành không có trong py3k. Mã hóa của py3k được nối cứng thành "utf-8" và việc thay đổi nó sẽ gây ra lỗi.

Tôi đề nghị một số gợi ý để đọc:


6
Những thứ tuyệt vời, mặc dù có một chút chết vì quá nhiều thông tin ở đây. Tôi đã học được nhiều nhất chỉ tập trung vào bài viết này: blog.notdot.net/2010/07/Getting-unicode-right-in-Python
mbb

3
Tôi muốn thêm rằng mã hóa mặc định cũng được sử dụng để mã hóa (khi viết vào sys.stdoutkhi nó có Nonemã hóa, giống như khi chuyển hướng đầu ra của chương trình Python).
Eric O Lebigot

14
+1 cho "việc sử dụng sys.setdefaultencoding()luôn bị nản lòng"
jfs

7
'cứng cáp đến utf-8' là không đúng sự thật, nó không cứng và không phải lúc nào cũng vậy UTF-8. LC_ALL=en_US.UTF-8 python3 -c 'import sys; print(sys.stdout.encoding)'cho UTF-8nhưng LC_ALL=C python3 -c 'import sys; print(sys.stdout.encoding)'cho ANSI_X3.4-1968(hoặc có lẽ một cái gì đó khác)
Tino

7
@Tino, mã hóa giao diện điều khiển tách biệt với mã hóa mặc định.
Alastair McCormack

59

tl; dr

Câu trả lời là KHÔNG BAO GIỜ ! (trừ khi bạn thực sự biết những gì bạn đang làm)

9/10 lần giải pháp có thể được giải quyết với sự hiểu biết đúng đắn về mã hóa / giải mã.

1/10 người có ngôn ngữ hoặc môi trường được xác định không chính xác và cần đặt:

PYTHONIOENCODING="UTF-8"  

trong môi trường của họ để khắc phục sự cố in bàn điều khiển.

Nó làm gì?

sys.setdefaultencoding("utf-8")(đánh xuyên qua để tránh sử dụng lại) thay đổi mã hóa / giải mã mặc định được sử dụng bất cứ khi nào Python 2.x cần chuyển đổi Unicode () thành str () (và ngược lại) và mã hóa không được cung cấp. I E:

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

Trong Python 2.x, mã hóa mặc định được đặt thành ASCII và các ví dụ trên sẽ thất bại với:

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

(Bảng điều khiển của tôi được định cấu hình là UTF-8, do đó "€" = '\xe2\x82\xac', ngoại lệ trên \xe2)

hoặc là

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

sys.setdefaultencoding("utf-8")sẽ cho phép những thứ này hoạt động với tôi , nhưng sẽ không nhất thiết phải hoạt động cho những người không sử dụng UTF-8. Mặc định của ASCII đảm bảo rằng các giả định về mã hóa không được đưa vào mã

Bảng điều khiển

sys.setdefaultencoding("utf-8")cũng có tác dụng phụ xuất hiện để sửa chữa sys.stdout.encoding, được sử dụng khi in các ký tự lên bàn điều khiển. Python sử dụng ngôn ngữ của người dùng (Linux / OS X / Un * x) hoặc codepage (Windows) để thiết lập điều này. Đôi khi, ngôn ngữ của người dùng bị hỏng và chỉ cần PYTHONIOENCODINGsửa mã hóa bàn điều khiển .

Thí dụ:

$ 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"
€

Có gì tệ với sys.setdefaultencoding ("utf-8") ?

Mọi người đã phát triển chống lại Python 2.x trong 16 năm với sự hiểu rằng mã hóa mặc định là ASCII. UnicodeErrorphương pháp xử lý ngoại lệ đã được viết để xử lý chuyển đổi chuỗi sang Unicode trên các chuỗi được tìm thấy có chứa không phải ASCII.

Từ 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"))

Trước đây để thiết lập mã hóa mặc định, mã này sẽ không thể giải mã được “và trong mã hóa ascii và sau đó sẽ nhập trình xử lý ngoại lệ để đoán mã hóa và biến nó thành unicode. In ấn: Angstrom (Å®) điều hành doanh nghiệp của bạn. Khi bạn đã đặt mã hóa mặc định thành utf-8, mã sẽ thấy rằng byte_ chuỗi có thể được hiểu là utf-8 và do đó, nó sẽ thu thập dữ liệu và trả về điều này thay vào đó: Angstrom () điều hành doanh nghiệp của bạn.

Thay đổi những gì nên là một hằng số sẽ có tác động lớn đến các mô-đun bạn phụ thuộc vào. Tốt hơn hết là chỉ sửa dữ liệu vào và ra khỏi mã của bạn.

Vấn đề mẫu

Mặc dù cài đặt mã hóa mặc định thành UTF-8 không phải là nguyên nhân gốc trong ví dụ sau, nhưng nó cho thấy các vấn đề được che giấu như thế nào và khi mã hóa đầu vào thay đổi, mã bị phá vỡ theo cách không thể hiểu được: UnicodeDecodeError: 'utf8' codec có thể 't giải mã byte 0x80 ở vị trí 3131: byte bắt đầu không hợp lệ


2
Mặc dù có những điều bất ngờ sys.setdefaultencoding("utf-8"), nhưng thật tốt khi làm cho mã hoạt động giống với Python 3. Bây giờ là năm 2017. Ngay cả khi bạn viết câu trả lời vào năm 2015, tôi nghĩ rằng đã tốt hơn để mong đợi thay vì lạc hậu. Nó thực sự là giải pháp đơn giản nhất đối với tôi, khi tôi thấy mã của mình hoạt động khác với Python 2 tùy thuộc vào việc đầu ra có được chuyển hướng hay không (vấn đề rất khó chịu đối với Python 2). Không cần phải nói, tôi đã có # coding: utf-8và tôi không cần bất kỳ cách giải quyết nào cho Python 3 (tôi thực sự phải che setdefaultencodingdấu kiểm tra phiên bản bằng cách sử dụng).
Yongwei Wu

Điều đó thật tuyệt và nó phù hợp với bạn nhưng sys.setdefaultencoding("utf-8")không làm cho mã Py 2.x của bạn tương thích với Python 3. Nó cũng không sửa các mô-đun bên ngoài giả định mã hóa mặc định là ASCII. Làm cho mã của bạn tương thích Python 3 rất đơn giản và không yêu cầu hack khó chịu này. Ví dụ: tại sao điều này gây ra vấn đề rất thực tế, hãy xem trải nghiệm của tôi với Amazon gây rối với giả định này: stackoverflow.com/questions/39465220/
Kẻ

1
@AlastairMcCormack bạn rock, Trang web của tôi đã có từ nhiều tháng và không thể biết phải làm gì. Cuối cùng, PYTHONIOENCODING="UTF-8"đã giúp môi trường Python2.7 Django-1.11 của tôi. Cảm ơn.
sam

Tôi biết bạn đã sao chép ví dụ, nhưng tôi có thể tìm thấy gói nào có detect_encoding.
dlamblin

@dlamblin Ví dụ về mã là để chứng minh trích dẫn và không được sử dụng trong mã của bạn. Hãy tưởng tượng đó detect_encodinglà một phương pháp có thể phát hiện mã hóa chuỗi dựa trên manh mối ngôn ngữ.
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)

trên shell hoạt động, gửi đến sdtout không, vì vậy đó là một cách giải quyết, để viết vào thiết bị xuất chuẩn.

Tôi đã thực hiện một cách tiếp cận khác, không chạy nếu sys.stdout.encoding không được xác định, hay nói cách khác, cần xuất PYTHONIOENCODING = UTF-8 trước tiên để ghi vào thiết bị xuất chuẩn.

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)


vì vậy, sử dụng cùng một ví dụ:

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

sẽ làm việc


3
Điều này không trả lời câu hỏi như đã hỏi. Thay vào đó là một số suy nghĩ tiếp tuyến về chủ đề này.
ivan_pozdeev

3
  • Mối nguy hiểm đầu tiên nằm ở reload(sys).

    Khi bạn tải lại một mô-đun, bạn thực sự nhận được hai bản sao của mô-đun trong thời gian chạy của bạn. Mô-đun cũ là một đối tượng Python giống như mọi thứ khác và vẫn tồn tại miễn là có các tham chiếu đến nó. Vì vậy, một nửa các đối tượng sẽ được trỏ đến mô-đun cũ và một nửa cho mô-đun mới. Khi bạn thực hiện một số thay đổi, bạn sẽ không bao giờ thấy nó đến khi một số đối tượng ngẫu nhiên không thấy thay đổi:

    (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>
  • Bây giờ, sys.setdefaultencoding()thích hợp

    Tất cả những gì nó ảnh hưởng là chuyển đổi ngầmstr<->unicode . Bây giờ, utf-8là mã hóa rõ ràng nhất trên hành tinh (tương thích ngược với ASCII và tất cả), việc chuyển đổi bây giờ "chỉ hoạt động", điều gì có thể xảy ra?

    Vâng, bất cứ điều gì. Và đó là mối nguy hiểm.

    • Có thể có một số mã dựa trên việc UnicodeErrorbị ném cho đầu vào không phải ASCII hoặc chuyển mã với trình xử lý lỗi, hiện tạo ra kết quả không mong muốn. Và vì tất cả các mã được kiểm tra với cài đặt mặc định, bạn hoàn toàn thuộc về lãnh thổ "không được hỗ trợ" ở đây và không ai đảm bảo cho bạn về cách mã của họ sẽ hoạt động.
    • Việc chuyển mã có thể tạo ra kết quả không mong muốn hoặc không sử dụng được nếu không phải mọi thứ trên hệ thống đều sử dụng UTF-8 vì Python 2 thực sự có nhiều "mã hóa chuỗi mặc định" độc lập . (Hãy nhớ rằng, một chương trình phải hoạt động cho khách hàng, trên thiết bị của khách hàng.)
      • Một lần nữa, điều tồi tệ nhất là bạn sẽ không bao giờ biết điều đó bởi vì việc chuyển đổi là ngầm định - bạn không thực sự biết khi nào và nơi nó xảy ra. (Python Zen, koan 2 ahoy!) Bạn sẽ không bao giờ biết tại sao (và nếu) mã của bạn hoạt động trên một hệ thống và phá vỡ hệ thống khác. (Hoặc tốt hơn nữa, hoạt động trong IDE và ngắt trong bảng điều khiển.)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.