Lỗi khi xử lý chuỗi ký tự unicode ở python2
Python
39
Unicode
3
White

studybot viết ngày 10/05/2016

Mình có webapp viết bằng flask, chạy bằng python 2.7. Webapp này cho phép đăng ký tên và lưu vào ldap server dùng thư viện python-ldap. Code vẫn chạy bình thường cho đến hôm nay tự nhiên không đăng ký được nữa. Nhìn vào log của hệ thống thấy lỗi sau:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)

Lỗi trên xảy ra ở hàm sau

def add_user(self, spec):
        validate(spec, set(['username', 'password', 'fullname', 'mail']))
        username = str(spec['username'])
        fullname = str(spec['fullname'])
        password = str(spec['password'])
        mail = str(spec['mail'])
        user_attributes = [
            ('objectclass', [str('inetOrgPerson')]),
            ('cn', [username]),
            ('sn', [fullname]),
            ('mail', [mail]),
            ('userPassword', [password]),
        ]
        user_directory = ('uid={0},{1}').format(spec['username'], self.userdn)
        self.conn.add_s(user_directory, user_attributes)

Python lăn ra chết khi mình nhập tên bằng tiếng Việt. Tìm hiểu lại quá trình xảy ra lỗi, mình thấy có đoạn mã mình viết không tốt, vì vậy mình muốn viết để chia sẻ cho mọi người.

Tại sao có lỗi

Lý do xảy ra lỗi là do mình đang sử dụng thư viện python-ldap vốn là thư viện chỉ hỗ trợ python2.

Hàm add_s của thư viện ở dòng cuối cùng chỉ nhận vào 1 dict có các trường là các chuỗi bytes. spec mà mình truyền vào nhận từ form đưa lên bởi user nên là một unicode object. Do vậy để có thể truyền giá trị của form vào hàm add_s, mình phải chuyển các trường được truyền vào sang chuỗi bytes. Việc này thực hiện ở các dòng :

        username = str(spec['username'])
        fullname = str(spec['fullname'])
        password = str(spec['password'])
        mail = str(spec['mail'])

hàm str nhận unicode object và trả về chuỗi byte mã hoá bằng chuẩn ASCII. Nếu như spec['username'] có thể chuyển mã được, thì hàm này chạy thành công. Tuy vậy nếu hàm này không thể chuyển mã được, lỗi như ở đầu bài viết xảy ra. Cụ thể, ta có thể quan sát qua thí nghiệm nhỏ trên interpreter như dưới đây:

$ python
Python 2.7.11 (default, Mar 31 2016, 15:42:43)
[GCC 4.2.1 Compatible Apple LLVM 7.3.0 (clang-703.0.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> str('test')
'test'
>>> str(u'test')
'test'
>>> str(u'Tiếng Việt')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u1ebf' in position 2: ordinal not in range(128)
>>>
>>> type(u'Tiếng Việt')
<type 'unicode'>
>>> type(u'test')
<type 'unicode'>
>>>
>>> type('test')
<type 'str'>

Từ đấy có thể thấy, việc dùng str() để chuyển 1 object unicode sang str là không tốt. Vì vậy mình sửa đoạn mã nhìn viết lại như sau:

        username = spec['username'].encode('utf-8')
        fullname = spec['fullname'].encode('utf-8')
        password = spec['password'].encode('utf-8')
        mail = spec['mail'].encode('utf-8')

Nếu biết trước đối tượng là unicode ta có thể gọi hàm encode để chuyển chuối đó sang byte string. Ví dụ

>>> u'test'.encode('utf-8')
'test'
>>> u'tiếng việt'.encode('utf-8')
'ti\xe1\xba\xbfng vi\xe1\xbb\x87t'

như cách làm trên, chúng ta sẽ không bao giờ gặp phải vấn đề không thể encode được và lăn ra chết như mình đã gặp nữa.

python2 - python3 - unicode

Sau khi tìm hiểu, mình biết được là Unicode là một trở ngại lớn trong việc porting code từ python2 sang python3. Từ python3, unicode thay đổi thế nào? Mình thấy nhanh nhất là mở interpreter lên và kiểm tra như dưới đây:

Python 3.5.1 (default, May  9 2016, 18:24:40)
[GCC 4.2.1 Compatible Apple LLVM 7.3.0 (clang-703.0.29)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> str('tiếng việt')
'tiếng việt'
>>> str(u'tiếng việt')
'tiếng việt'
>>> type(str(u'tiếng việt'))
<class 'str'>
>>> type(str('tiếng việt'))
<class 'str'>

Ở python2, nếu string được bắt đầu bằng u'', chuỗi ký tự sẽ được chuyển đổi sang kiểu unicode. Từ python3 trở đi, chuỗi ký tự sẽ được mặc định mã hoá theo chuẩn Unicode, vì vậy khi ta viết một chuỗi kiểu 'tiếng việt' hay 'vietnamese' thì kiểu dữ liệu của chuỗi sẽ luôn là str và mã hoá sẽ luôn là unicode, chứ không chia thành unicodestr như ở python2 nữa. Điểu này có vẻ đơn giản hoá việc xử lý chuỗi ký tự với unicode đi rất nhiều!

Tại sao không viết webapp bằng python3

Mình cũng đã ước như thế khi sửa lỗi trên cho đến khi nhìn tiến độ porting python3 ở site dưới đây...

http://py3readiness.org/

Thư viện python-ldap vẫn đang chỉ hỗ trợ python2...

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

studybot

15 bài viết.
9 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
14 12
Một vài thủ thuật vim mới biết, ghi lại cho khỏi quên Căn lề các cột dữ liệu Để căn lề ấn phím esc (để sang command mode) :%column t Chọn th...
studybot viết gần 3 năm trước
14 12
White
9 8
Javascript context (ngữ cảnh?) là một khái niệm khó hiểu trong Javascript. Tuy vậy để code Javascript thành thạo thì việc nắm vững context là điểu ...
studybot viết gần 3 năm trước
9 8
White
8 1
Một vài ghi chép về Java Hotspot GC Garbage collector thực hiện việc quản lý bộ nhớ thông qua 3 công việc: Gán đối tượng vào pool các đối tượng m...
studybot viết gần 3 năm trước
8 1
Bài viết liên quan
White
1 0
Mở đầu Như đã nói ở bài trước, mình đang nghiên cứu về Spark nên cần log lại một số thứ để dành sau này dùng đến :smile: Đối tượng hướng đến vẫn ...
Phạm Quốc Thắng viết gần 2 năm trước
1 0
White
4 3
Observer pattern (python example) 1. Observer là gì : Theo như (Link) Observer Pattern là : A software design pattern in which an object, calle...
Khôi Trọng Nguyễn viết hơn 1 năm trước
4 3
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
15 bài viết.
9 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!