Đặt mã hóa chính xác khi thiết bị xuất chuẩn đường ống trong Python


343

Khi dẫn đầu ra của chương trình Python, trình thông dịch Python bị nhầm lẫn về mã hóa và đặt nó thành Không có. Điều này có nghĩa là một chương trình như thế này:

# -*- coding: utf-8 -*-
print u"åäö"

sẽ hoạt động tốt khi chạy bình thường, nhưng không thành công với:

UnicodeEncodeError: 'ascii' codec không thể mã hóa ký tự u '\ xa0' ở vị trí 0: thứ tự không nằm trong phạm vi (128)

khi được sử dụng trong một chuỗi ống.

Cách tốt nhất để làm cho công việc này khi đường ống là gì? Tôi có thể chỉ bảo nó sử dụng bất cứ thứ gì mã hóa shell / filesystem / bất cứ thứ gì đang sử dụng không?

Các đề xuất mà tôi đã thấy cho đến nay là sửa đổi trực tiếp trang web của bạn hoặc mã hóa mã hóa mặc định bằng cách sử dụng bản hack này:

# -*- coding: utf-8 -*-
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
print u"åäö"

Có cách nào tốt hơn để làm cho đường ống làm việc?



2
Nếu bạn gặp vấn đề này trên windows, bạn cũng có thể chạy chcp 65001trước khi thực thi tập lệnh của mình. Điều này có thể có vấn đề, nhưng nó thường giúp ích và không yêu cầu gõ nhiều (ít hơn set PYTHONIOENCODING=utf_8).
Tomasz Gandor

Lệnh chcp không giống như cài đặt PYTHONIOENCODING. Tôi nghĩ rằng chcp chỉ là cấu hình cho chính thiết bị đầu cuối và không liên quan gì đến việc ghi vào một tệp (đó là những gì bạn đang làm khi đặt stdout đường ống). Cố gắng setx PYTHONENCODING utf-8làm cho nó vĩnh viễn nếu bạn muốn lưu gõ.
ejm


Tôi đã phải đối mặt với một vấn đề có liên quan và tìm thấy một giải pháp ở đây -> stackoverflow.com/questions/48782529/ Lời
bkrishna2006

Câu trả lời:


162

Mã của bạn hoạt động khi chạy trong một tập lệnh vì Python mã hóa đầu ra thành bất kỳ mã hóa nào mà ứng dụng đầu cuối của bạn đang sử dụng. Nếu bạn đang đường ống, bạn phải tự mã hóa nó.

Một nguyên tắc nhỏ là: Luôn sử dụng Unicode trong nội bộ. Giải mã những gì bạn nhận được và mã hóa những gì bạn gửi.

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

Một ví dụ điển hình khác là chương trình Python để chuyển đổi giữa ISO-8859-1 và UTF-8, làm cho mọi thứ trở thành chữ hoa ở giữa.

import sys
for line in sys.stdin:
    # Decode what you receive:
    line = line.decode('iso8859-1')

    # Work with Unicode internally:
    line = line.upper()

    # Encode what you send:
    line = line.encode('utf-8')
    sys.stdout.write(line)

Đặt mã hóa mặc định của hệ thống là một ý tưởng tồi, bởi vì một số mô-đun và thư viện bạn sử dụng có thể dựa vào thực tế đó là ASCII. Đừng làm điều đó.


11
Vấn đề là người dùng không muốn chỉ định mã hóa rõ ràng. Anh ta chỉ muốn sử dụng Unicode cho IO. Và mã hóa anh ta sử dụng phải là một mã hóa được chỉ định trong cài đặt ngôn ngữ, không phải trong cài đặt ứng dụng đầu cuối. AFAIK, Python 3 sử dụng mã hóa ngôn ngữ trong trường hợp này. Thay đổi sys.stdoutcó vẻ như một cách dễ chịu hơn.
Andrey Vlasovskikh

4
Mã hóa / giải mã mọi chuỗi ngoại lệ bị ràng buộc gây ra lỗi khi một cuộc gọi mã hóa hoặc giải mã bị thiếu hoặc thêm một lần vào nhiều nơi nào đó. Mã hóa đầu ra có thể được đặt khi đầu ra là một thiết bị đầu cuối, vì vậy nó có thể được đặt khi đầu ra không phải là một thiết bị đầu cuối. Thậm chí còn có một môi trường LC_CTYPE tiêu chuẩn để chỉ định nó. Đó là một nhưng trong python rằng nó không tôn trọng điều này.
Rasmus Kaj

65
Câu trả lời này là sai. Bạn không nên chuyển đổi thủ công trên từng đầu vào và đầu ra của chương trình; đó là giòn và hoàn toàn không thể nhầm lẫn.
Glenn Maynard

29
@Glenn Maynard: vậy IYO câu trả lời đúng là gì? Thật hữu ích khi nói với chúng tôi thay vì chỉ nói 'Câu trả lời này là sai'
smci

14
@smci: câu trả lời là không sửa đổi tập lệnh của bạn, hãy đặt PYTHONIOENCODINGnếu bạn đang chuyển hướng thiết bị xuất chuẩn của tập lệnh trong Python 2.
jfs

168

Đầu tiên, liên quan đến giải pháp này:

# -*- coding: utf-8 -*-
print u"åäö".encode('utf-8')

Nó không thực tế để in rõ ràng với một mã hóa nhất định mỗi lần. Điều đó sẽ lặp đi lặp lại và dễ bị lỗi.

Một giải pháp tốt hơn là thay đổi sys.stdoutkhi bắt đầu chương trình của bạn, để mã hóa với một mã hóa được chọn. Đây là một giải pháp tôi tìm thấy trên Python: sys.stdout.encoding được chọn như thế nào? , đặc biệt là một nhận xét của "toka":

import sys
import codecs
sys.stdout = codecs.getwriter('utf8')(sys.stdout)

7
thật không may, việc thay đổi sys.stdout để chỉ chấp nhận unicode phá vỡ rất nhiều thư viện mong muốn nó chấp nhận mã hóa mã hóa.
nosklo

6
nosklo: Sau đó, làm thế nào nó có thể hoạt động đáng tin cậy và tự động khi đầu ra là một thiết bị đầu cuối?
Rasmus Kaj

3
@Rasmus Kaj: chỉ cần xác định chức năng in unicode của riêng bạn và sử dụng nó mỗi khi bạn muốn in unicode: def myprint(unicodeobj): print unicodeobj.encode('utf-8')- bạn tự động phát hiện mã hóa đầu cuối bằng cách kiểm tra sys.stdout.encoding, nhưng bạn nên xem xét trường hợp của nó None(tức là khi chuyển hướng đầu ra sang tệp) Vì vậy, bạn cần một chức năng riêng biệt.
nosklo

3
@nosklo: Điều này không khiến sys.stdout chỉ chấp nhận Unicode. Bạn có thể chuyển cả str và unicode cho StreamWriter.
Glenn Maynard

9
Tôi giả sử câu trả lời này là dành cho python2. Hãy cẩn thận với mã này nhằm hỗ trợ cả python2 và python3 . Đối với tôi, nó là thứ phá vỡ khi chạy dưới python3.
wim

130

Bạn có thể muốn thử thay đổi biến môi trường "PYTHONIOENCODING" thành "utf_8". Tôi đã viết một trang về thử thách của tôi với vấn đề này .

Tl; dr của bài viết trên blog:

import sys, locale, os
print(sys.stdout.encoding)
print(sys.stdout.isatty())
print(locale.getpreferredencoding())
print(sys.getfilesystemencoding())
print(os.environ["PYTHONIOENCODING"])
print(chr(246), chr(9786), chr(9787))

mang đến cho bạn

utf_8
False
ANSI_X3.4-1968
ascii
utf_8
ö ☺ ☻

2
Thay đổi sys.stdout.encoding có thể không hoạt động, nhưng thay đổi sys.stdout không hoạt động : sys.stdout = codecs.getwriter(encoding)(sys.stdout). Điều này có thể được thực hiện từ bên trong chương trình python, vì vậy người dùng không bị buộc phải đặt biến env.
blueFast

7
@ jeckyll2 leather: PYTHONIOENCODINGkhông hoạt động. Làm thế nào các byte được hiểu là một văn bản được xác định bởi môi trường người dùng . Kịch bản của bạn không nên giả định và ra lệnh cho môi trường người dùng sử dụng mã hóa ký tự nào. Nếu Python không tự động nhận cài đặt thì PYTHONIOENCODINGcó thể được đặt cho tập lệnh của bạn. Bạn không cần nó trừ khi đầu ra được chuyển hướng đến một tệp / ống.
jfs

8
+1. Thành thật tôi nghĩ đó là một lỗi Python. Khi tôi chuyển hướng đầu ra, tôi muốn các byte tương tự sẽ ở trên thiết bị đầu cuối, nhưng trong một tệp. Có thể nó không dành cho tất cả mọi người nhưng đó là một mặc định tốt. Đâm cứng không có lời giải thích về một hoạt động tầm thường mà thường "chỉ hoạt động" là một mặc định xấu.
SnakE

Tài khoản @ Hoặc thay đổi nó chỉ là một tính năng chưa được thực hiện, trong trường hợp cho phép người dùng thay đổi nó sau này sẽ là một yêu cầu tính năng Python hợp lý.
daveagp

2
@daveagp Quan điểm của tôi là, hành vi của chương trình của tôi không nên phụ thuộc vào việc nó có được chuyển hướng hay không --- trừ khi tôi thực sự muốn nó, trong trường hợp đó tôi tự thực hiện nó. Python hành xử trái với kinh nghiệm của tôi với bất kỳ công cụ điều khiển nào khác. Điều này vi phạm nguyên tắc ít bất ngờ nhất. Tôi coi đây là một lỗ hổng thiết kế trừ khi có một lý do rất mạnh mẽ.
SnakE

62
export PYTHONIOENCODING=utf-8

thực hiện công việc, nhưng không thể tự đặt nó lên trăn ...

những gì chúng ta có thể làm là xác minh nếu không cài đặt và yêu cầu người dùng đặt nó trước tập lệnh cuộc gọi với:

if __name__ == '__main__':
    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)

Cập nhật để trả lời bình luận: vấn đề chỉ tồn tại khi đường ống đến thiết bị xuất chuẩn. Tôi đã thử nghiệm trong Fedora 25 Python 2.7.13

python --version
Python 2.7.13

mèo

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

print sys.stdout.encoding

đang chạy ./b.py

UTF-8

đang chạy ./b.py | ít hơn

None

2
Kiểm tra đó không hoạt động trong Python 2.7.13. sys.stdout.encodingđược đặt tự động dựa trên LC_CTYPEgiá trị miền địa phương.
amphetamachine

1
mail.python.org/pipermail/python-list/2011-June/605938.html ví dụ vẫn còn hoạt động, tức là khi bạn sử dụng ./a.py> out.txt sys.stdout.encoding là Không có
Sérgio

Tôi gặp vấn đề tương tự với tập lệnh đồng bộ hóa từ Backblaze B2 và xuất PYTHONIOENCODING = utf-8 đã giải quyết vấn đề của tôi. Python 2.7 trên Debian Stretch.
0x3333

5

Tôi đã có một vấn đề tương tự tuần trước . Thật dễ dàng để sửa trong IDE của tôi (PyCharm).

Đây là cách khắc phục của tôi:

Bắt đầu từ thanh menu PyCharm: Tệp -> Cài đặt ... -> Trình chỉnh sửa -> Mã hóa tệp, sau đó đặt: "Mã hóa IDE", "Mã hóa dự án" và "Mã hóa mặc định cho các tệp thuộc tính" ALL sang UTF-8 và giờ đây cô ấy hoạt động như một lá bùa.

Hi vọng điêu nay co ich!


4

Một phiên bản vệ sinh có thể tranh cãi của câu trả lời của Craig McQueen.

import sys, codecs
class EncodedOut:
    def __init__(self, enc):
        self.enc = enc
        self.stdout = sys.stdout
    def __enter__(self):
        if sys.stdout.encoding is None:
            w = codecs.getwriter(self.enc)
            sys.stdout = w(sys.stdout)
    def __exit__(self, exc_ty, exc_val, tb):
        sys.stdout = self.stdout

Sử dụng:

with EncodedOut('utf-8'):
    print u'ÅÄÖåäö'

2

Tôi có thể "tự động hóa" nó bằng một cuộc gọi đến:

def __fix_io_encoding(last_resort_default='UTF-8'):
  import sys
  if [x for x in (sys.stdin,sys.stdout,sys.stderr) if x.encoding is None] :
      import os
      defEnc = None
      if defEnc is None :
        try:
          import locale
          defEnc = locale.getpreferredencoding()
        except: pass
      if defEnc is None :
        try: defEnc = sys.getfilesystemencoding()
        except: pass
      if defEnc is None :
        try: defEnc = sys.stdin.encoding
        except: pass
      if defEnc is None :
        defEnc = last_resort_default
      os.environ['PYTHONIOENCODING'] = os.environ.get("PYTHONIOENCODING",defEnc)
      os.execvpe(sys.argv[0],sys.argv,os.environ)
__fix_io_encoding() ; del __fix_io_encoding

Có, có thể có được một vòng lặp vô hạn ở đây nếu "setenv" này không thành công.


1
thú vị, nhưng một đường ống dường như không hài lòng về điều này
n611x007

2

Tôi chỉ nghĩ rằng tôi đã đề cập đến một cái gì đó ở đây mà tôi đã phải trải qua một thời gian dài thử nghiệm trước khi cuối cùng tôi nhận ra những gì đang xảy ra. Điều này có thể quá rõ ràng với mọi người ở đây đến nỗi họ không bận tâm đến việc đề cập đến nó. Nhưng nó sẽ giúp tôi nếu họ có, vì vậy theo nguyên tắc đó ...!

Lưu ý: Tôi đang sử dụng Jython cụ thể, v 2.7, vì vậy có thể điều này có thể không áp dụng cho CPython ...

NB2: hai dòng đầu tiên của tệp .py của tôi ở đây là:

# -*- coding: utf-8 -*-
from __future__ import print_function

Cơ chế xây dựng chuỗi "%" (AKA "toán tử nội suy") cũng gây ra các vấn đề THÊM ... Nếu mã hóa mặc định của "môi trường" là ASCII và bạn cố gắng làm một cái gì đó như

print( "bonjour, %s" % "fréd" )  # Call this "print A"

Bạn sẽ không gặp khó khăn khi chạy trong Eclipse ... Trong Windows CLI (cửa sổ DOS), bạn sẽ thấy rằng mã hóa là mã trang 850 (HĐH Windows 7 của tôi) hoặc một cái gì đó tương tự, có thể xử lý ít nhất các ký tự có dấu châu Âu, vì vậy nó sẽ làm việc

print( u"bonjour, %s" % "fréd" ) # Call this "print B"

cũng sẽ làm việc

Nếu, OTOH, bạn chuyển trực tiếp đến một tệp từ CLI, mã hóa xuất chuẩn sẽ là Không có, sẽ mặc định là ASCII (dù sao trên hệ điều hành của tôi), sẽ không thể xử lý một trong các bản in ở trên ... (mã hóa đáng sợ lỗi).

Vì vậy, sau đó bạn có thể nghĩ đến việc chuyển hướng thiết bị xuất chuẩn của mình bằng cách sử dụng

sys.stdout = codecs.getwriter('utf8')(sys.stdout)

và thử chạy trong đường ống CLI vào một tệp ... Rất kỳ lạ, in A ở trên sẽ hoạt động ... Nhưng in B ở trên sẽ gây ra lỗi mã hóa! Tuy nhiên, sau đây sẽ hoạt động tốt:

print( u"bonjour, " + "fréd" ) # Call this "print C"

Kết luận mà tôi đã đưa ra (tạm thời) là nếu một chuỗi được chỉ định là chuỗi Unicode sử dụng tiền tố "u" được gửi đến cơ chế% -handling thì nó dường như liên quan đến việc sử dụng mã hóa môi trường mặc định, bất kể cho dù bạn đã thiết lập thiết bị xuất chuẩn để chuyển hướng!

Làm thế nào mọi người đối phó với điều này là một vấn đề của sự lựa chọn. Tôi sẽ hoan nghênh một chuyên gia về Unicode để nói lý do tại sao điều này xảy ra, cho dù tôi đã hiểu sai theo cách nào đó, giải pháp ưa thích nào cho vấn đề này, liệu nó cũng áp dụng cho CPython , cho dù điều đó xảy ra trong Python 3, v.v., v.v.


Điều đó không lạ, đó là bởi vì "fréd"một chuỗi byte và không phải là một chuỗi Unicode, vì vậy codecs.getwritertrình bao bọc sẽ để nó một mình. Bạn cần một người dẫn đầu u, hoặc from __future__ import unicode_literals.
Matthias Urlichs

@MatthiasUrlichs OK ... cảm ơn ... Nhưng tôi chỉ tìm thấy mã hóa một trong những khía cạnh đáng sợ nhất của CNTT. Bạn lấy sự hiểu biết từ đâu? Ví dụ, tôi vừa đăng một câu hỏi khác về mã hóa ở đây: stackoverflow.com/questions/44483067/iêu : đây là về Java, Eclipse, Cygwin & Gradle. Nếu chuyên môn của bạn đi xa đến thế, xin vui lòng giúp đỡ ... trên hết tôi muốn biết nơi để tìm hiểu thêm!
mike gặm nhấm

1

Tôi gặp vấn đề này trong một ứng dụng cũ và rất khó xác định nơi được in. Tôi đã giúp mình với bản hack này:

# encoding_utf8.py
import codecs
import builtins


def print_utf8(text, **kwargs):
    print(str(text).encode('utf-8'), **kwargs)


def print_utf8(fn):
    def print_fn(*args, **kwargs):
        return fn(str(*args).encode('utf-8'), **kwargs)
    return print_fn


builtins.print = print_utf8(print)

Trên đầu tập lệnh của tôi, test.py:

import encoding_utf8
string = 'Axwell Λ Ingrosso'
print(string)

Lưu ý rằng điều này thay đổi TẤT CẢ các cuộc gọi để in để sử dụng mã hóa, vì vậy bảng điều khiển của bạn sẽ in điều này:

$ python test.py
b'Axwell \xce\x9b Ingrosso'

1

Trên Windows, tôi gặp vấn đề này rất thường xuyên khi chạy mã Python từ trình soạn thảo (như Sublime Text), nhưng không chạy nếu chạy từ dòng lệnh.

Trong trường hợp này, hãy kiểm tra các thông số của biên tập viên của bạn. Trong trường hợp của SublimeText, điều này đã Python.sublime-buildgiải quyết nó:

{
  "cmd": ["python", "-u", "$file"],
  "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
  "selector": "source.python",
  "encoding": "utf8",
  "env": {"PYTHONIOENCODING": "utf-8", "LANG": "en_US.UTF-8"}
}
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.