2013-06-24 37 views
7

Dường như mã hóa UTF-8 của Python (codecs gói) diễn giải các ký tự Unicode 28, 29 và 30 làm dòng kết thúc. Tại sao? Và làm thế nào tôi có thể ngăn chặn nó làm như vậy?Codec Python dòng kết thúc

Ví dụ mã:

with open('unicodetest.txt', 'w') as f: 
    f.write('a'+chr(28)+'b'+chr(29)+'c'+chr(30)+'d'+chr(31)+'e') 
with open('unicodetest.txt', 'r') as f: 
    for i,l in enumerate(f): 
    print i, l 
# prints "0 abcde" with special characters in between. 

Vấn đề ở đây là nó đọc nó như một dòng như tôi mong đợi nó để làm. Bây giờ khi tôi sử dụng codecs để đọc nó trong UTF-8, nó diễn giải nó như nhiều dòng.

import codecs 
with codecs.open('unicodetest.txt', 'r', 'UTF-8') as f: 
    for i,l in enumerate(f): 
    print i, l 
# 0 a 
# 1 b 
# 2 c 
# 3 de 
# (again with the special characters after each a, b, c, d 

Ký tự từ 28 đến 31 được mô tả là "Dấu phân tách thông tin" qua "Một" (theo thứ tự đó). Hai điều đánh tôi: 1) 28 đến 30 được hiểu là dòng kết thúc, 2) 31 thì không. Hành vi này có dự định không? Tôi có thể tìm định nghĩa về các ký tự nào được hiểu là dòng kết thúc? Có cách nào để không giải thích chúng như là dòng kết thúc?

Cảm ơn.

chỉnh sửa quên sao chép đối số 'UTF-8' trong codecs.open. Mã trong câu hỏi của tôi hiện đã được sửa.

+0

Điều gì sẽ xảy ra nếu bạn mở tệp ở chế độ '' rb''? – unutbu

+0

Không tạo sự khác biệt. – Paul

+2

@Paul, bạn có thể trả lời câu hỏi của riêng mình và chấp nhận câu hỏi nếu bạn thích –

Trả lời

5

Đây là một câu hỏi hay.

Điều này tạo sự khác biệt cho dù bạn mở tệp có open() hoặc codecs.open(). Các hoạt động trước đây về các chuỗi byte. Sau này hoạt động theo các chuỗi Unicode. Trong Python, các behave differently này.

Câu hỏi tương tự này xuất hiện là Python Issue 7643, What is a Unicode line break character?. Cuộc thảo luận và các trích dẫn cho Unicode Character Database, rất hấp dẫn. Vấn đề 7643 cũng cung cấp đoạn mã ngắn gọn này để minh họa sự khác biệt:

for s in '\x0a\x0d\x1c\x1d\x1e': 
    print u'a{}b'.format(s).splitlines(1), 'a{}b'.format(s).splitlines(1) 

Nhưng nó sẽ giảm xuống.

Để xác định xem byte trong chuỗi byte là ngắt dòng (hoặc khoảng trắng), Python sử dụng các quy tắc của ASCII control characters. Theo đó, byte 10 và 13 là các ký tự ngắt dòng (và Python xử lý byte 13 theo sau là 10 ký tự ngắt dòng).

Tuy nhiên, để xác định xem nhân vật trong chuỗi Unicode là ngắt dòng, Python theo phân loại nhân vật của Unicode Character Database, ghi nhận tại UAX #44, và của UAX #14 Line Breaking Algorithm, section 5 Line Breaking Properties. Theo Issue 7643, các tài liệu này xác định ba đặc tính nhân vật mà xác định một nhân vật như một linebreak cho các mục đích của Python:

  • chung Thể loại Zl "Line Separator"
  • chung Thể loại Zp "Đoạn Separator"
  • hai chiều Lớp B "Dấu phân cách đoạn"

Ký tự 28 (0x001C), 29 (0x001D) và 30 (0x001E) có các đặc tính ký tự đó. Ký tự 31 (0x001F) thì không. Tại sao? Đó là một câu hỏi cho Ủy ban kỹ thuật Unicode. Nhưng trong ASCII, các ký tự này được gọi là "Phân tách tệp", "Dấu phân tách nhóm", "Dấu phân tách bản ghi" và "Dấu tách đơn vị".Sử dụng một tập tin dữ liệu văn bản theo thẻ như một so sánh, ba kết hợp đầu tiên ít nhất là tách nhiều như một ngắt dòng, trong khi thứ tư có lẽ là tương tự như tab.

Bạn có thể thấy mã thực sự xác định ba ký tự Unicode này dưới dạng ngắt dòng trong chuỗi Unicode Unicode trong Objects/unicodeobject.c. Tìm mảng ascii_linebreak[]. Mảng này nhấn mạnh việc triển khai unicode.splitlines(). Mã dưới dạng mã khác nhau str.splitlines(). Tôi tin, nhưng không truy tìm nó trong mã nguồn Python, rằng enumerate() trên một tệp được mở với codecs.open() được triển khai theo điều khoản unicode.splitlines().

Bạn hỏi, "làm cách nào để ngăn không cho làm như vậy?" Tôi không thấy cách nào để làm cho splitlines() hoạt động khác đi. Tuy nhiên, bạn có thể mở các tập tin như một dòng byte, đọc dòng như byte với hành vi str.splitlines(), sau đó giải mã mỗi dòng như UTF-8 để sử dụng như là một chuỗi unicode:

with open('unicodetest.txt', 'r') as f: 
    for i,l in enumerate(f): 
    print i, l.decode('UTF-8') 
# prints "0 abcde" with special characters in between. 

tôi giả sử bạn đang sử dụng Python 2 .x, không phải là 3.x. Câu trả lời của tôi dựa trên Python 2.7.

+1

Cảm ơn bạn. Điều này là phức tạp. Và cảm ơn cho giải pháp của bạn. Có ý nghĩa. – Paul

Các vấn đề liên quan