Bắt ma bằng Python

Viet Hung Nguyen
PyMivn
Published in
4 min readMar 13, 2017
Catch ghost by Python

Một đêm trăng sáng không đèn, cu Hít bật IPython Python3 lên để gõ:

Có vẻ hơi dài để so sánh bằng mắt thường, xếp chúng thẳng hàng nhau và đọ chiều dài:

Khi đã xếp thành hàng rồi sẽ thấy rõ, nhìn bằng mắt thường, đeo kính cận hay soi kính lúp, 2 string này trông GIỐNG HỆT nhau.

Đó là lý do người ta tin có ma, cho đến khi … người ta không tin nữa.
Và cũng là lý do người ta thường chỉ tin vào những gì mắt thấy… cho tới khi họ không thấy nữa.

Một thứ không nhìn thấy, không đồng nghĩa với không tồn tại.
Một ký tự “ma” đã tồn tại trong s1, nhưng ta không nhìn thấy được khi print ra, đó là một ký tự “không hình”. Giờ thì hãy làm nó hiện hình:

In [47]: repr(s1)
Out[47]: "'abfa7eb6-657f-49d4-8e13-e816f1957d0c\\ufeff'"
In [48]: print(repr(s1))
'abfa7eb6-657f-49d4-8e13-e816f1957d0c\ufeff'
In [50]: s1
Out[50]: 'abfa7eb6-657f-49d4-8e13-e816f1957d0c\ufeff'

Kí tự ma đó có ký hiệu là \ufeff, biểu diễn Unicode dạng 16-bit của codepoint:

In [72]: '\ufeff'
Out[72]: '\ufeff'
In [73]: ord('\ufeff')
Out[73]: 65279

Đó là “codepoint” thứ 65279 trong bảng mã Unicode.

In [53]: chr(65279)
Out[53]: '\ufeff'
In [54]: print(chr(65279))

print sẽ không thấy gì, nhưng không nghĩa là không tồn tại.
Qua đây ta thấy rõ sự khác nhau giữa function “str” và function “repr” trong Python.

repr
Return the canonical string representation of the object.

``print`` hiển thị ra những gì mã bên dưới muốn hiển thị.
``repr`` trả về biểu diễn string dạng chuẩn của object.

Thêm một ví dụ nữa cho thấy rõ hơn:

In [64]: print('head\nbody\nleg')
head
body
leg
In [65]: repr('head\nbody\nleg')
Out[65]: "'head\\nbody\\nleg'"
In [66]: print(repr('head\nbody\nleg'))
'head\nbody\nleg'
In [67]: 'head\nbody\nleg'
Out[67]: 'head\nbody\nleg'

Khi chỉ gõ string vào Python interpreter ở chế độ tương tác, ta nhìn thấy giá trị hiển thị tương ứng với việc gõ print(repr(string)) và nó khác với những gì nhìn thấy khi print(string).

Kí tự “ma” trong bài này là ký tự BOM (Byte order mark) trong Unicode với mã U+FEFF (zero width no-break space).

Khi đứng đầu văn bản, nó được dùng làm “CON SỐ KỲ DIỆU”(magic number) để đánh dấu một văn bản là ở dạng mã Unicode, chỉ ra thứ tự của các byte khi encode ở dạng 16-bit(2 byte) hay 32-bit(4 bytes). Unicode 8-bit (UTF-8) không cần BOM để đánh dấu thứ tự, vì 8 bit là 1 byte.

Với UTF-16, chia làm 2 thứ tự byte (byte order / endianness), là big-endian (BE) và little-endian (LE). UTF-32 ít được dùng, và luật tương tự UTF-16.
Do byte rất tiện khi biểu diễn ở dạng hex với 2 ký tự (1 byte), hex được dùng rộng rãi khi nói về byte.

Biểu diễn 2 byte này ở dạng binary:

In [76]: bin(ord('\ufeff'))
Out[76]: '0b1111111011111111'

Đây là biểu diễn chính xác ta thu được, với thứ tự từ TRÁI qua PHẢI.
Tức bit có ý nghĩa lớn nhất (MSB — most significant bit), nằm phía bên trái.
Số 1 đầu tiên bên trái biểu diễn giá trị 2**15, trong khi
số 1 bên phải nhất biểu diễn giá trị 2**0 == 1.
Thứ tự bit như thế gọi là left-most-bit, hay trong Unicode gọi là Big-endian (bắt đầu bằng byte chứa MSB). End- ở đây là front-end, hay nghĩa “start” — bắt đầu.

In [74]: hex(ord('\ufeff'))
Out[74]: '0xfeff'

Python3 đang biểu diễn ký tự BOM ở dạng BE.
Khi ta đổi thứ tự các byte, thì byte chứa MBS giờ nằm ở bên tay phải. Thu được 2 byte là: 0xfffe

In [91]: s = bin(ord('\ufeff'))[2:]In [93]: s[:8], s[8:]
Out[93]: ('11111110', '11111111')
In [94]: [hex(int(n, base=2)) for n in (s[8:], s[:8])]
Out[94]: ['0xff', '0xfe']

Thử in ra giá trị này:

In [102]: chr(0xfffe)
Out[102]: '\ufffe'
In [103]: print(chr(0xfffe))

Unicode U+FFFE được coi như không phải một ký tự trong bảng mã Unicode và không bao giờ nên xuất hiện trong 1 văn bản, bởi nó là dạng biểu diễn đảo byte của 2 byte FE FF. Khi chương trình đọc văn bản vào, thấy 2 byte đầu tiên FE FF, chương trình sẽ thử đọc tiếp và nếu gặp lỗi, nó sẽ đảo thứ tự của các bytes (byte 1,2 đổi chỗ, byte 3,4 đổi chỗ …).

Khi thử trên Python 2, độ dài “string ma” sẽ là 39 ký tự trong đó chứa 3 ký tự biểu diễn BOM ở UTF-8:

In [4]: x
Out[4]: 'abfa7eb6-657f-49d4-8e13-e816f1957d0c\xef\xbb\xbf'

Những gì bạn không nhìn thấy, không chắc nó đã không tồn tại!
Lần này Python đã giúp ta đi bắt ma thành công. Đội ơn Python 🤗

Hết.

HVN at http://www.familug.org/ and http://pymi.vn

Đăng ký học #Python tại Hà Nội , lớp khai giảng giữa tháng 4, 2017: https://pymi.vn/

Nhập email vào http://invite.pymi.vn/ để nhận thư mời tham gia forum hỏi đáp Python, Django, Golang …

Tham khảo:

--

--