Bạn có chắc chắn muốn xóa bài viết này không ?
Bạn có chắc chắn muốn xóa bình luận này không ?
Lỗi khi xử lý chuỗi ký tự unicode ở python2
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 unicode
và str
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...
Thư viện python-ldap
vẫn đang chỉ hỗ trợ python2
...






