Bài viết này nêu lên một số khía cạnh thú vị (nhưng cũng rắc rối) của kiểu dữ liệu None cùng các phép toán liên quan đến nó, qua đó giúp các bạn mới học hiểu sâu sắc hơn về các kiểu dữ liệu có trong Python. Bài viết này có sử dụng một số khái niệm có trong lý thuyết nhóm chỉ nhằm mục đích minh họa những ý đồ của người viết.

1. Khái niệm phần tử không

Trong lý thuyết nhóm có khái niệm về phần tử không hay phần tử trung hòa. Phần tử không θ của một tập S trên phép toán * là phần tử thỏa mãn: a*θ = θ*a = a với mọi a trên S.

Các kiểu dữ liệu cơ bản trong Python cũng có phần tử không như vậy. Xem bảng tổng kết sau:

Kiểu dữ liệu Phép toán Phần tử không Ví dụ
Số nguyên + 0 0 + 69 = 69 + 0
Số thực + 0.0 0.0 + 96.0 = 96.0 + 0.0
Danh sách +, extend [] [1] + [] = [] + [1] = [1]
tuple + () (1,) + () = () + (1,) = (1,)
Tập hợp union set([]) set([1]).union(set([])) = set([]).union(set([1])) = set([1])
Chuỗi + "" "abc" + "" = "" + "abc" = "abc"
Từ điển update {} {1:1}.update({}) = {}.update({1:1}) = {1:1}
Số phức + 0 + 0j  
Logic or False False or True = True or False = True

2. None và các toán tử logic

None là một tập hợp đặc biệt, chỉ có chính nó, mà cũng không phải là chính nó. Không phải là chính nó vì None bao hàm ý nghĩa là thiếu giá trị (denoting lack of value):

>>> type(None)
<type 'NoneType'>

None tương tác với tất cả các phần tử không:

>>> assert(([] and None) == [])
>>> assert(({} and None) == {})
>>> assert((0.0 and None) == 0.0)
>>> assert(("" and None) == "")
>>> assert((set([]) and None) == set([]))
>>> assert((() and None) == ())
>>> assert((0j and None) == 0J)
>>> assert((False and None) == False)

Nếu ta gọi S là tập hợp các phần tử không, bao gồm chính None, thì với phép toán and, None chính là phần tử không bên phải của S. Ai bảo None không phải là giá trị.

Tuy nhiên:

>>> assert((None and []) == None)
>>> assert((None and {}) == None)
>>> assert((None and set([])) == None)
>>> assert((None and "") == None)
>>> assert((None and 0) == None)

Tương tự:

>>> assert((None or set([])) == set([]))
>>> assert((None or []) == [])
>>> assert((None or {}) == {})
>>> assert((None or "") == "")
>>> assert((None or 0) == 0)
>>> assert((None or 0j) == 0j)
>>> assert((None or ()) == ())

Nghĩa là None là phần tử không bên phải đối với tập S nói trên trong phép toán or.

Và:

>>> assert(({} or None) is None)
>>> assert(([] or None) is None)
>>> assert((0 or None) is None)
>>> assert((set([]) or None) is None)
>>> assert(("" or None) is None)
>>> assert((0j or None) is None)
>>> assert((() or None) is None)
>>> assert((0.0 or None) is None)

Như vậy khi None tương tác với các phần tử không, thứ tự các toán hạng là điều bạn cần chú ý.

Phép toán or giữa None và object bất kỳ không phải là phần tử không là ánh xạ đồng nhất bất kể thứ tự phép toán:

>>> set(['a']) or None
set(['a'])
>>> None or set(['a'])
set(['a'])
>>> None or "abc"
'abc'
>>> "abc" or None
'abc'
>>> None or {1:1}
{1: 1}
>>> {1:1} or None
{1: 1}

Phép toán and thì luôn cho kết quả None với bất kỳ phần tử nào không phải là phần tử không, không kể thứ tự toán hạng:

>>> assert((None and 1) is None)
>>> assert((1 and None) is None)
>>> assert(([1] and None) is None)
>>> assert((None and [1]) is None)
>>> assert(("abc" and None) is None)
>>> assert((None and "abc") is None)

Một biểu thức logic chứa None có thể trả về None, nghĩa là không có giá trị. Điều này làm cho None trong python trở lên phức tạp và không giống với null trong C# hay nothing trong VB.NET. Trong VB.NET một biểu thức logic luôn luôn trả về giá trị logic.

None là tận cùng (bottom), là nhỏ hơn tất cả.

>>> None < 0.0 == 0 < {} < [1] < "" < ()
True

None không có bất kỳ một method nào, thậm chí chính nó là một method khi được dùng với filter hoặc map (xem phần dưới).

Cuối cùng NoneNone - đừng băn khoăn (^-^):

>>> None is None
True
>>> None == None
True

Việc hiểu rõ bản chất của None cùng các tương tác của nó với các phần tử không qua các phép toán logic là RẤT quan trọng khi bạn viết các lệnh if, filter...

Một hàm bất kỳ có thể trả về None nếu bạn viết return None, hoặc không return ở bất kỳ chỗ nào trong hàm. Một hàm như vậy khi tham gia vào biểu thức logic sẽ tạo ra những hiệu ứng mà bạn cần phải nắm rõ bản chất.

3. Loại bỏ các phần tử không trong danh sách

Nói chung các phần tử không sẽ không có ý nghĩa khi nó nằm trong danh sách. Một trong những cách để loại bỏ phần tử không là dùng filter. filter cùng với hàm func sẽ loại bỏ tất cả các phần tử trong danh sách mà tác động của hàm func lên phần tử này tạo ra phần tử không. Diễn giải dễ hiểu của nó như sau:

filter(func, list)

tương đương với

S = [0, 0.0, [], (), {}, set([]), "", 0j, False]
def filter(func, list):
    return [x for x in list if func(x) not in S]

Ví dụ:

>>> filter(lambda x: not x, [3, 4, False, True, {1: 0}, []])
[False, []]

Để loại bỏ các phần tử không trong danh sách L, đơn giản là bạn dùng hàm ánh xạ đồng nhất f(x) = x ví dụ như filter(lambda x: x, L). Hàm này tương đương với:

def filter(func, list):
    return [x for x in list if x not in S]

Cuối cùng một cách viết gây khó hiểu là thay thế lambda x: x bằng hàm None.

filter(None, list) # <==> filter(lambda x: x, list)

Ví dụ:

>>> filter(None, [0, 0.0, "", 0+0j, set([]), (), [], {}, False])
[]
>>> filter(None, ["", "abc", " "])
['abc', ' ']
>>> filter(None, [1, 0, 2, 0.0])
[1, 2]

Ở đây chúng ta có cảm giác như None được dùng như một hàm. Thực ra không phải vậy, None có nghĩa là không có hàm nào ở đây cả, nói cách khác đối số đầu tiên của filter bị khuyết. Python lúc đó sẽ dùng hàm mặc định gì đó tương tự như lambda x: x.

4. Dùng None với map

>>> map(None, [1, 2, {1:2}])
[1, 2, {1: 2}]

map không thay đổi danh sách khi nó bị khuyết hàm tác động. Khi đó hàm None tương đương với ánh xạ đồng nhất (mặc dù không chính xác, vì thực ra là không có hàm nào cả, Python có thể đã sử dụng hàm mặc định). Tuy nhiên dùng hàm None khi có hai danh sách trở lên thì lại tạo ra khả năng đặc biệt.

>>> map(None, [1, 2], [3, 4])
[(1, 3), (2, 4)]
>>> map(None, [1, 2], [3, 4], [5, 6])
[(1, 3, 5), (2, 4, 6)]

Khả năng này tương đương với zip.

>>> zip([1, 2], [3, 4], [5, 6])
[(1, 3, 5), (2, 4, 6)]
>>> zip([1, 2], [3, 4])
[(1, 3), (2, 4)]

Diễn giải của nó như sau:

>>> map(lambda x, y: (x, y), [1, 2], [3, 4])
[(1, 3), (2, 4)]
>>> map(lambda x, y, z: (x, y, z), [1, 2], [3, 4], [5, 6])
[(1, 3, 5), (2, 4, 6)]

Một lần nữa trong mục này, mặc dù None bao hàm ý nghĩa về sự khuyết giá trị, song trong triển khai của hàm bất kỳ việc khuyết giá trị dẫn đến việc sử dụng các hàm mặc định. Lúc đó None có ý nghĩa là những gì mặc định. Đôi khi có thể ngắn gọn coi nó cũng là một hàm đặc biệt, hàm None.

Chú ý quan trọng: Các test của bài viết này được thử trên python 2.5.x.