Cách bỏ qua lỗi unicode trong python

Được rồi, điều địa phương là một nỗi đau nhưng có thể hiểu được. ngôn ngữ C không hiểu bất kỳ ký tự nào ngoài việc cố gắng hiển thị những ký tự đó một cách tự nhiên sẽ không hoạt động. Bây giờ tại sao việc chuyển hướng đến một tệp lại gây ra sự cố? . Trong khi các đối tượng giống như tệp khác trong python luôn chuyển đổi thành trừ khi bạn thiết lập chúng theo cách khác, việc sử dụng để xuất ra thiết bị đầu cuối sẽ sử dụng ngôn ngữ của người dùng để chuyển đổi trước khi gửi đầu ra tới thiết bị đầu cuối. Khi không xuất ra thiết bị đầu cuối (chẳng hạn như được chuyển hướng đến một tệp), quyết định rằng nó không biết sử dụng ngôn ngữ nào cho tệp đó và do đó, nó cố gắng chuyển đổi sang thay thế

Vậy điều này có ý nghĩa gì đối với bạn, với tư cách là một lập trình viên? . Python thậm chí còn cung cấp cho bạn một cơ sở để làm việc này. Nếu bạn biết rằng mọi unicode chuỗi bạn gửi tới một đối tượng giống như tệp cụ thể (ví dụ: ) sẽ được chuyển đổi thành một mã hóa cụ thể, bạn có thể sử dụng . Cụ thể, sẽ trả về một lớp giúp bạn bọc một đối tượng giống như tệp cho đầu ra. Sử dụng ví dụ của chúng tôi. unicode string into a byte . In particular, will return a class that will help you to wrap a file-like object for output. Using our example:

$ cat test.py
#!/usr/bin/python -tt
# -*- coding: utf-8 -*-
import codecs
import sys

UTF8Writer = codecs.getwriter('utf8')
sys.stdout = UTF8Writer(sys.stdout)
print u'café'
$ ./test.py  >t
$ cat t
café

Thất vọng #4 và #5 – Đôi giày khác

Trong tiếng Anh có câu “chờ chiếc giày kia rơi xuống”. Nó có nghĩa là khi một sự kiện (thường là tồi tệ) xảy ra, bạn sẽ mong đợi một sự kiện khác (thường là tồi tệ hơn) xảy ra sau đó. Trong trường hợp này, chúng tôi có hai chiếc giày khác

Thất vọng #4. Bây giờ nó không lấy chuỗi byte?

Nếu bạn bọc sử dụng và nghĩ rằng bây giờ bạn đã an toàn để in bất kỳ biến nào mà không cần kiểm tra loại của biến đó, tôi e rằng tôi phải thông báo với bạn rằng bạn chưa chú ý đầy đủ đến. Điều đó cung cấp sẽ lấy các chuỗi unicode và chuyển đổi chúng thành byte trước khi chúng nhận được. Vấn đề là nếu bạn cung cấp cho nó thứ gì đó đã là một byte thì nó cũng cố gắng chuyển đổi thứ đó. Để làm điều đó, nó cố gắng biến byte bạn cung cấp thành unicode rồi chuyển ngược lại thành byte. và vì nó sử dụng codec để thực hiện các chuyển đổi đó, nên rất có thể nó sẽ nổ tung khi thực hiện chúng.

>>> import codecs
>>> import sys
>>> UTF8Writer = codecs.getwriter('utf8')
>>> sys.stdout = UTF8Writer(sys.stdout)
>>> print 'café'
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib64/python2.6/codecs.py", line 351, in write
    data, consumed = self.encode(object, self.errors)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 3: ordinal not in range(128)
Traceback (most recent call last):
  File "", line 1, in 
  File "/usr/lib64/python2.6/codecs.py", line 351, in write
    data, consumed = self.encode(object, self.errors)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 3: ordinal not in range(128)

Để giải quyết vấn đề này, kitchen cung cấp một phiên bản thay thế có thể xử lý cả chuỗi byte và unicode . Sử dụng thay cho phiên bản như thế này.

>>> import sys
>>> from kitchen.text.converters import getwriter
>>> UTF8Writer = getwriter('utf8')
>>> sys.stdout = UTF8Writer(sys.stdout)
>>> print u'café'
café
>>> print 'café'
café

Thất vọng #5. ngoại lệ

Được rồi, vậy là chúng ta đã tiến xa đến mức này. Chúng tôi chuyển đổi mọi thứ thành chuỗi unicode . Chúng tôi biết rằng chúng tôi cần chuyển đổi ngược lại thành byte trước khi ghi vào thiết bị đầu cuối. Chúng tôi đã giải quyết vấn đề tiêu chuẩn không có khả năng xử lý cả chuỗi byte và unicode . Chúng ta đã sẵn sàng chưa? . tăng ngoại lệ bằng thông báo unicode . Hãy xem.

>>> class MyException(Exception):
>>>     pass
>>>
>>> raise MyException(u'Cannot do this')
Traceback (most recent call last):
  File "", line 1, in 
__main__.MyException: Cannot do this
>>> raise MyException(u'Cannot do this while at a café')
Traceback (most recent call last):
  File "", line 1, in 
__main__.MyException:
>>>

Không, tôi không cắt bớt dòng cuối cùng đó; . Điều gì sẽ xảy ra nếu chúng ta cố gắng sử dụng thủ thuật hữu ích để giải quyết vấn đề này?unicode string and will output an exception without the message if the message contains them. What happens if we try to use the handy dandy trick to work around this?

>>> import sys
>>> from kitchen.text.converters import getwriter
>>> sys.stderr = getwriter('utf8')(sys.stderr)
>>> raise MyException(u'Cannot do this')
Traceback (most recent call last):
  File "", line 1, in 
__main__.MyException: Cannot do this
>>> raise MyException(u'Cannot do this while at a café')
Traceback (most recent call last):
  File "", line 1, in 
__main__.MyException>>>

Điều này không chỉ không thành công mà còn nuốt cả dòng mới ở cuối thường có ở đó. Vì vậy, làm thế nào để làm cho công việc này? . unicode strings to byte manually before outputting:

>>> string = unicode(raw_input(), 'utf8')
café
>>> string_for_output = string.encode('utf8', 'replace')
>>> log = open('/var/tmp/debug.log', 'w')
>>> log.write(string_for_output)
>>>
0

Cảnh báo

Nếu bạn sử dụng on , bạn sẽ thấy rằng việc đưa ra một ngoại lệ với một byte cũng bị phá vỡ theo mặc định. Đừng làm điều đó hoặc bạn sẽ không có cách nào để xuất các ký tự không phải. Nếu bạn muốn sử dụng a để mã hóa những thứ khác trên thiết bị lỗi chuẩn trong khi vẫn có ngoại lệ hoạt động, hãy sử dụng

Thất vọng #6. API không nhất quán Part deux

Đôi khi bạn làm đúng mọi thứ trong mã của mình nhưng mã của người khác lại làm bạn thất bại. Với các sự cố unicode, điều này xảy ra thường xuyên hơn chúng ta muốn. Một ví dụ rõ ràng về điều này là khi bạn nhận lại các giá trị từ một hàm không nhất quán unicode chuỗi hoặc byte.

Một ví dụ từ thư viện chuẩn của python là. Các chức năng được sử dụng để giúp dịch các thông báo mà bạn hiển thị cho người dùng bằng ngôn ngữ mẹ đẻ của người dùng. Vì hầu hết các ngôn ngữ chứa các chữ cái nằm ngoài phạm vi, nên các giá trị được trả về chứa các ký tự unicode. cung cấp cho bạn và trả lại các bản dịch này dưới dạng unicode chuỗi và , , , và trả lại chúng dưới dạng byte được mã hóa. Thật không may, mặc dù chúng được ghi lại để chỉ trả về một loại chuỗi này hoặc loại khác, nhưng việc triển khai có các trường hợp góc trong đó loại sai có thể được trả về.

Điều này có nghĩa là ngay cả khi bạn tách chuỗi unicode và byte một cách chính xác trước khi chuyển các chuỗi của mình tới một hàm, thì sau đó, bạn có thể có .

Ghi chú

cung cấp các đối tượng dịch gettext thay thế chỉ trả về chuỗi byte hoặc chỉ unicode .

Một vài giải pháp

Bây giờ chúng tôi đã xác định được các vấn đề, liệu chúng tôi có thể xác định một chiến lược toàn diện để giải quyết chúng không?

Chuyển đổi văn bản ở đường viền

Nếu bạn lấy một đoạn văn bản nào đó từ thư viện, đọc từ tệp, v.v., hãy biến đoạn văn bản đó thành chuỗi unicode ngay lập tức. Vì python đang di chuyển theo hướng unicode chuỗi ở khắp mọi nơi nên sẽ dễ dàng hơn để làm việc với unicode strings within your code.

Nếu mã của bạn liên quan nhiều đến việc sử dụng những thứ ở dạng byte, bạn có thể làm ngược lại và chuyển đổi tất cả văn bản thành byte ở đường viền và chỉ chuyển đổi thành unicode when you need it for passing to another library or performing string operations on it.

Trong cả hai trường hợp, điều quan trọng là chọn một loại chuỗi mặc định và gắn bó với nó trong suốt mã của bạn. Khi bạn kết hợp các loại, thao tác trên một chuỗi với một hàm chỉ có thể sử dụng loại khác do nhầm lẫn sẽ trở nên dễ dàng hơn nhiều

Ghi chú

Trong python3, kiểu unicode trừu tượng trở nên nổi bật hơn nhiều. Loại có tên str tương đương với unicode của python2 và unicode của python3 . Hầu hết các API xử lý loại chuỗi unicode chỉ với một số phần ở mức độ thấp xử lý byte. Chuyển đổi ẩn giữa byte và unicode bị loại bỏ và bất cứ khi nào bạn muốn thực hiện chuyển đổi, bạn cần thực hiện rõ ràng. unicode của python3 . Hầu hết các API xử lý loại chuỗi unicode chỉ với một số phần ở mức độ thấp xử lý byte. Chuyển đổi ẩn giữa byte và unicode bị loại bỏ và bất cứ khi nào bạn muốn thực hiện chuyển đổi, bạn cần thực hiện rõ ràng. unicode của python3 . Hầu hết các API xử lý loại chuỗi unicode chỉ với một số phần ở mức độ thấp xử lý byte. Chuyển đổi ẩn giữa byte và unicode bị loại bỏ và bất cứ khi nào bạn muốn thực hiện chuyển đổi, bạn cần thực hiện rõ ràng. unicode của python3 . Hầu hết các API xử lý loại chuỗi unicode chỉ với một số phần ở mức độ thấp xử lý byte. Chuyển đổi ẩn giữa byte và unicode bị loại bỏ và bất cứ khi nào bạn muốn thực hiện chuyển đổi, bạn cần thực hiện rõ ràng. unicode của python3 . Hầu hết các API xử lý loại chuỗi unicode chỉ với một số phần ở mức độ thấp xử lý byte. Chuyển đổi ẩn giữa byte và unicode bị loại bỏ và bất cứ khi nào bạn muốn thực hiện chuyển đổi, bạn cần thực hiện rõ ràng. unicode của python3 . Hầu hết các API xử lý loại chuỗi unicode chỉ với một số phần ở mức độ thấp xử lý byte. Chuyển đổi ẩn giữa byte và unicode bị loại bỏ và bất cứ khi nào bạn muốn thực hiện chuyển đổi, bạn cần thực hiện rõ ràng. bytes type replaces python2’s . Most APIs deal in the unicode type of string with just some pieces that are low level dealing with bytes. The implicit conversions between bytes and unicode is removed and whenever you want to make the conversion you need to do so explicitly.

Khi dữ liệu cần được coi là byte (hoặc unicode), hãy sử dụng quy ước đặt tên

Đôi khi bạn đang chuyển đổi gần như tất cả dữ liệu của mình thành chuỗi unicode nhưng bạn có một hoặc hai giá trị mà bạn phải giữ xung quanh byte. Đây thường là trường hợp khi bạn cần sử dụng nguyên văn giá trị với một số tài nguyên bên ngoài. Chẳng hạn, tên tệp hoặc giá trị khóa trong cơ sở dữ liệu. Khi bạn làm điều này, hãy sử dụng quy ước đặt tên cho dữ liệu bạn đang làm việc để bạn (và những người khác đọc mã của bạn sau này) không bị nhầm lẫn về những gì đang được lưu trữ trong giá trị.

Nếu bạn cần cả một chuỗi văn bản để hiển thị cho người dùng và một giá trị byte để khớp chính xác, hãy xem xét giữ nguyên cả hai phiên bản. Bạn có thể sử dụng hai biến cho biến này hoặc biến có khóa là giá trị byte

Ghi chú

Bạn có thể sử dụng quy ước đặt tên được sử dụng trong nhà bếp làm hướng dẫn để thực hiện quy ước đặt tên của riêng mình. Nó đặt trước các biến byte của mã hóa không xác định bằng b_ và byte của mã hóa đã biết với tên mã hóa như. utf8_ . Nếu giá trị mặc định là xử lý và chỉ giữ một vài giá trị unicode , thì các biến đó sẽ có tiền tố là u_.

Khi xuất dữ liệu, chuyển ngược lại thành byte

Khi bạn gửi dữ liệu của mình trở lại bên ngoài chương trình của mình (tới hệ thống tệp, qua mạng, hiển thị cho người dùng, v.v.), hãy chuyển dữ liệu trở lại thành một byte. Cách bạn thực hiện việc này sẽ phụ thuộc vào định dạng đầu ra dự kiến ​​của dữ liệu. Để hiển thị cho người dùng, bạn có thể sử dụng mã hóa mặc định của người dùng bằng cách sử dụng. Để nhập vào một tệp, tốt nhất bạn nên chọn một mã hóa duy nhất và gắn bó với nó

Cảnh báo

Khi sử dụng mã hóa mà người dùng đã đặt (ví dụ: sử dụng , hãy nhớ rằng họ có thể đặt mã hóa thành thứ gì đó không thể hiển thị từng ký tự unicode. Điều đó có nghĩa là khi bạn chuyển đổi từ unicode thành byte, bạn cần quyết định điều gì sẽ xảy ra nếu giá trị byte không hợp lệ trong mã hóa của người dùng. Với mục đích hiển thị thông báo cho người dùng, bạn thường có thể sử dụng trình xử lý lỗi mã hóa thay thế để thay thế các ký tự không hợp lệ bằng dấu chấm hỏi hoặc ý nghĩa biểu tượng khác .

Bạn có thể sử dụng để làm điều này tự động cho. Khi tạo thông báo ngoại lệ, hãy đảm bảo chuyển đổi thành byte theo cách thủ công

Khi viết unittests, bao gồm các giá trị không phải ASCII và cả unicode và str type

Trừ khi bạn biết rằng một phần cụ thể trong mã của bạn sẽ chỉ xử lý , hãy đảm bảo bao gồm các giá trị không phải trong các bài kiểm tra của bạn. Bao gồm một vài ký tự từ một số tập lệnh khác nhau cũng rất được khuyến khích vì một số mã có thể có các ký tự La Mã có dấu cách đặc biệt nhưng không biết cách xử lý các ký tự được sử dụng trong bảng chữ cái châu Á