Một vài trick Python bạn nhất định phải biết trên con đường go-pro

Một vài trick Python bạn nhất định phải biết trên con đường go-pro
Photo by Sam Dan Truong from Unsplash

Với bất kỳ ngôn ngữ nào, nếu bạn muốn thuần thục ngôn ngữ đó, bạn nhất định phải biết các thủ thuật cũng như tính năng đặc trưng của ngôn ngữ đó. Những điều này thường được tích lũy qua quá trình làm việc lâu dài, mà một vài quyển sách dạy lập trình không thể nói hết được. Python cũng không phải là ngoại lệ.

Bài viết này sẽ trình bày một vài thủ thuật trong số đó. Các thủ thuật này sẽ giúp code của bạn trong “pro” hơn, ngắn gọn và dễ hiểu hơn. Nếu bạn đã có kinh nghiệm với Python, có thể bạn đã biết gần hết các thủ thuật, nhưng vẫn có thể có những thứ bạn chưa đụng đến bao giờ. Còn nếu bạn mới làm quen với ngôn ngữ, bài viết này sẽ giúp ích cho bạn rất nhiều.

Mỗi một thủ thuật hoặc tính năng của ngôn ngữ đều được demo bằng code và tôi nghĩ rằng, mình không cần phải nói thêm gì nữa. Mọi thứ để đã được thể hiện ở code rồi.

Trong bài viết này, tôi sẽ sử dụng Python 3, code Python 2 có thể thay đổi đôi chút.

Mục lục

Unpack

>>> a, b, c = 1, 2, 3
>>> a, b, c
(1, 2, 3)
>>> a
1
>>> b
2
>>> c
3
>>> a, b, c = (2 * i + 1 for i in range(3))
>>> a, b, c
(1, 3, 5)
>>> a, (b, c), d = (1, (2, 3), 4)
>>> a, b, c, d
(1, 2, 3, 4)

Swap hai biến

>>> a, b = 1, 2
>>> a, b = b, a
>>> a, b
(2, 1)

Sử dụng toán tử * đại diện cho “phần còn lại”

>>> a, *b, c = [1, 2, 3, 4, 5]
>>> a
1
>>> b
[2, 3, 4]
>>> c
5

Index âm

>>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a[-1]
10
>>> a[-3]
8

Slice

>>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a[2:8]
[2, 3, 4, 5, 6, 7]

Slice với index âm

>>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a[-4:-2]
[7, 8]

Slice nhảy bước

>>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a[::2]
[0, 2, 4, 6, 8, 10]
>>> a[::3]
[0, 3, 6, 9]
>>> a[2:8:2]
[2, 4, 6]

Slice nhảy bước âm

>>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> a[::-1]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> a[::-2]
[10, 8, 6, 4, 2, 0]

Slice kết hợp gán với list

>>> a = [1, 2, 3, 4, 5]
>>> a[2:3] = [0, 0]
>>> a
[1, 2, 0, 0, 4, 5]
>>> a[1:1] = [8, 9]
>>> a
[1, 8, 9, 2, 0, 0, 4, 5]
>>> a[1:-1] = []
>>> a
[1, 5]

Slice đặt tên

>>> a = [0, 1, 2, 3, 4, 5]
>>> LASTTHREE = slice(-3, None)
>>> LASTTHREE
slice(-3, None, None)
>>> a[LASTTHREE]
[3, 4, 5]
a = [0, 1]
>>> a[LASTTHREE]
[0, 1]

Duyệt iterable

Dùng enumerate duyệt qua cả index và giá trị

>>> a = ['Hello', 'world', '!']
>>> for i, x in enumerate(a):
...     print('{}: {}'.format(i, x))
...
0: Hello
1: world
2: !

Duyệt qua key và value của dict

>>> m = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> for k, v in m.items():
...     print('{}: {}'.format(k, v))
...
b: 2
c: 3
a: 1
d: 4

Zip và ứng dụng

>>> a = [1, 2, 3]
>>> b = ['a', 'b', 'c']
>>> z = list(zip(a, b))
>>> z
[(1, 'a'), (2, 'b'), (3, 'c')]
>>> list(zip(*z))
[(1, 2, 3), ('a', 'b', 'c')]

Nhóm các phần tử liền nhau

Dùng itertools

>>> a = [1, 2, 3, 4, 5, 6]
>>> group_adjacent = lambda a, k: list(zip(*([iter(a)] * k)))
>>> group_adjacent(a, 3)
[(1, 2, 3), (4, 5, 6)]
>>> group_adjacent(a, 2)
[(1, 2), (3, 4), (5, 6)]
>>> group_adjacent(a, 1)
[(1,), (2,), (3,), (4,), (5,), (6,)]

Dùng slice

>>> from itertools import islice
>>> a = [1, 2, 3, 4, 5, 6]
>>> group_adjacent = lambda a, k: list(zip(*(islice(a, i, None, k) for i in range(k))))
>>> group_adjacent(a, 3)
[(1, 2, 3), (4, 5, 6)]
>>> group_adjacent(a, 2)
[(1, 2), (3, 4), (5, 6)]
>>> group_adjacent(a, 1)
[(1,), (2,), (3,), (4,), (5,), (6,)]

Chuyển đổi dict

>>> m = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> m.items()
dict_items([('b', 2), ('c', 3), ('a', 1), ('d', 4)])
>>> list(zip(m.values(), m.keys()))
[(2, 'b'), (3, 'c'), (1, 'a'), (4, 'd')]
>>> mi = dict(zip(m.values(), m.keys()))
>>> mi
{1: 'a', 2: 'b', 3: 'c', 4: 'd'}

Làm phẳng list

Dùng itertools

>>> import itertools
>>> a = [[1, 2], [3, 4], [5, 6]]
>>> list(itertools.chain.from_iterable(a))
[1, 2, 3, 4, 5, 6]

Dùng sum

>>> a = [[1, 2], [3, 4], [5, 6]]
>>> sum(a, [])
[1, 2, 3, 4, 5, 6]

Dùng list comprehension

>>> a = [[1, 2], [3, 4], [5, 6]]
>>> [x for l in a for x in l]
[1, 2, 3, 4, 5, 6]
>>> a = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
>>> [x for l1 in a for l2 in l1 for x in l2]
[1, 2, 3, 4, 5, 6, 7, 8]

Nâng cao: list với các phần tử bất kỳ

>>> a = [1, 2, [3, 4], [[5, 6], [7, 8]]]
>>> latten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
>>> flatten(a)
[1, 2, 3, 4, 5, 6, 7, 8]

Sử dụng comprehensions

Biểu thứ generator

>>> g = (x ** 2 for x in range(10))
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> sum(x ** 3 for x in range(10))
2025
>>> sum(x ** 3 for x in range(10) if x % 3 == 1)
408

Dict comprehension

>>> m = {x: x ** 2 for x in range(5)}
>>> m
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
>>> m = {x: 'A' + str(x) for x in range(10)}
>>> m
{0: 'A0', 1: 'A1', 2: 'A2', 3: 'A3', 4: 'A4', 5: 'A5', 6: 'A6', 7: 'A7', 8: 'A8', 9: 'A9'}

Chuyển đổi dict dùng comprehension

>>> m = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> m
{'b': 2, 'c': 3, 'a': 1, 'd': 4}
>>> {v: k for k, v in m.items()}
{1: 'a', 2: 'b', 3: 'c', 4: 'd'}

Tập hợp và các phép toán liên quan

>>> A = {1, 2, 3, 3}
>>> B = {3, 4, 5, 6, 7}
>>> A | B
{1, 2, 3, 4, 5, 6, 7}
>>> A & B
{3}
>>> A - B
{1, 2}
>>> B - A
{4, 5, 6, 7}
>>> A ^ B
{1, 2, 4, 5, 6, 7}
>>> A ^ B == ((A - B) | (B - A))
True

Sự vi diệu của collections

namedtupple

>>> import collections
>>> Point = collections.namedtuple('Point', ['x', 'y'])
>>> p = Point(x=1.0, y=2.0)
>>> p
Point(x=1.0, y=2.0)
>>> p.x
1.0
>>> p.y
2.0
>>> class Point(collections.namedtuple('PointBase', ['x', 'y'])):
...     __slots__ = ()
...     def __add__(self, other):
...         return Point(x=self.x + other.x, y=self.y + other.y)
...
>>> p = Point(x=1.0, y=2.0)
>>> q = Point(x=2.0, y=3.0)
>>> p + q
Point(x=3.0, y=5.0)

Tập hợp cùng tuần số xuất hiện

>>> import collections
>>> A = collections.Counter([1, 2, 2])
>>> B = collections.Counter([2, 2, 3])
>>> A
Counter({2: 2, 1: 1})
>>> B
Counter({2: 2, 3: 1})
>>> A | B
Counter({2: 2, 1: 1, 3: 1})
>>> A & B
Counter({2: 2})
>>> A + B
Counter({2: 4, 1: 1, 3: 1})
>>> A - B
Counter({1: 1})
>>> B - A
Counter({3: 1})

Phần tử xuất hiện nhiều nhất

>>> import collections
>>> A = collections.Counter([1, 1, 2, 2, 3, 3, 3, 3, 4, 5, 6, 7])
>>> A
Counter({3: 4, 1: 2, 2: 2, 4: 1, 5: 1, 6: 1, 7: 1})
>>> A.most_common(1)
[(3, 4)]
>>> A.most_common(3)
[(3, 4), (1, 2), (2, 2)]

Hàng đợi hai đầu

>>> import collections
>>> Q = collections.deque()
>>> Q.append(1)
>>> Q.appendleft(2)
>>> Q.extend([3, 4])
>>> Q.extendleft([5, 6])
>>> Q
deque([6, 5, 2, 1, 3, 4])
>>> Q.pop()
4
>>> Q.popleft()
6
>>> Q
deque([5, 2, 1, 3])
>>> Q.rotate(3)
>>> Q
deque([2, 1, 3, 5])
>>> Q.rotate(-3)
>>> Q
deque([5, 2, 1, 3])

Hàng đợi hai đầu có giới hạn độ dài

>>> import collections
>>> last_three = collections.deque(maxlen=3)
>>> for i in range(10):
...     last_three.append(i)
...     print(', '.join(str(x) for x in last_three))
...
0
0, 1
0, 1, 2
1, 2, 3
2, 3, 4
3, 4, 5
4, 5, 6
5, 6, 7
6, 7, 8
7, 8, 9

Dict có thứ tự

>>> m = dict((str(x), x) for x in range(10))
>>> print(', '.join(m.keys()))
4, 6, 8, 2, 3, 7, 0, 9, 5, 1
>>> import collections
>>> m = collections.OrderedDict((str(x), x) for x in range(10))
>>> print(', '.join(m.keys()))
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
>>> m = collections.OrderedDict((str(x), x) for x in range(10, 0, -1))
>>> print(', '.join(m.keys()))
10, 9, 8, 7, 6, 5, 4, 3, 2, 1

Dict với giá trị mặc định

>>> m = dict()
>>> m['a']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'a'
>>> import collections
>>> m = collections.defaultdict(int)
>>> m['a']
0
>>> m['b']
0
>>> m = collections.defaultdict(str)
>>> m['a']
''
>>> m['b'] += 'a'
>>> m
defaultdict(<class 'str'>, {'b': 'a', 'a': ''})
>>> m = collections.defaultdict(lambda: '[default value]')
>>> m['a']
'[default value]'

Cấu trúc cây với default dict

>>> import collections
>>> import json
>>> tree = lambda: collections.defaultdict(tree)
>>> root = tree()
>>> root['menu']['id'] = 'file'
>>> root['menu']['value'] = 'File'
>>> root['menu']['menuitems']['new']['value'] = 'New'
>>> root['menu']['menuitems']['new']['onclick'] = 'new();'
>>> root['menu']['menuitems']['open']['value'] = 'Open'
>>> root['menu']['menuitems']['open']['onclick'] = 'open();'
>>> root['menu']['menuitems']['close']['value'] = 'Close'
>>> root['menu']['menuitems']['close']['onclick'] = 'close();'
>>> root
defaultdict(<function <lambda> at 0x7f5ce09e4488>, {'menu': defaultdict(<function <lambda> at 0x7f5ce09e4488>, {'value': 'File', 'menuitems': defaultdict(<function <lambda> at 0x7f5ce09e4488>, {'new': defaultdict(<function <lambda> at 0x7f5ce09e4488>, {'value': 'New', 'onclick': 'new();'}), 'close': defaultdict(<function <lambda> at 0x7f5ce09e4488>, {'value': 'Close', 'onclick': 'close();'}), 'open': defaultdict(<function <lambda> at 0x7f5ce09e4488>, {'value': 'Open', 'onclick': 'open();'})}), 'id': 'file'})})
>>> print(json.dumps(root, sort_keys=True, indent=4, separators=(',', ': ')))
{
    "menu": {
        "id": "file",
        "menuitems": {
            "close": {
                "onclick": "close();",
                "value": "Close"
            },
            "new": {
                "onclick": "new();",
                "value": "New"
            },
            "open": {
                "onclick": "open();",
                "value": "Open"
            }
        },
        "value": "File"
    }
}

Sinh uniqe id cho từng giá trị

>>> import collections
>>> import itertools
>>> value_to_numeric_map = collections.defaultdict(itertools.count().__next__)
>>> value_to_numeric_map['a']
0
>>> value_to_numeric_map['b']
1
>>> value_to_numeric_map['c']
2
>>> value_to_numeric_map['a']
0
>>> value_to_numeric_map['b']
1

Những phần tử lớn nhất và nhỏ nhất trong 1 list

>>> import heapq
>>> import random
>>> a = [random.randint(0, 100) for __ in range(100)]
>>> a
[77, 59, 53, 12, 57, 81, 90, 67, 62, 6, 93, 35, 28, 45, 32, 95, 55, 52, 0, 19, 99, 47, 3, 74, 15, 81, 69, 73, 94, 37, 51, 58, 36, 51, 57, 96, 15, 65, 4, 42, 42, 0, 36, 89, 34, 73, 12, 62, 13, 34, 84, 51, 92, 37, 17, 56, 59, 15, 8, 31, 36, 90, 70, 95, 41, 56, 100, 84, 50, 91, 72, 43, 15, 12, 90, 34, 58, 97, 72, 67, 79, 28, 84, 9, 25, 75, 47, 43, 100, 9, 27, 86, 12, 90, 4, 19, 45, 62, 91, 76]
>>> heapq.nsmallest(5, a)
[0, 0, 3, 4, 4]
>>> heapq.nlargest(5, a)
[100, 100, 99, 97, 96]

Ứng dụng của itertools

Tích Descartes

>>> import itertools
>>> for p in itertools.product('123', '45'):
...     print(''.join(p))
...
14
15
24
25
34
35
>>> for p in itertools.product('12', repeat=4):
...     print(''.join(p))
...
1111
1112
1121
1122
1211
1212
1221
1222
2111
2112
2121
2122
2211
2212
2221
2222

Tổ hợp

>>> import itertools
>>> for c in itertools.combinations('12345', 3):
...    print(''.join(c))
...
123
124
125
134
135
145
234
235
245
345
>>> for c in itertools.combinations_with_replacement('12345', 2):
...    print(''.join(c))
...
11
12
13
14
15
22
23
24
25
33
34
35
44
45
55

Chỉnh hợp

>>> import itertools
>>> for p in itertools.permutations('1234', 3):
...    print(''.join(p))
...
123
124
132
134
142
143
213
214
231
234
241
243
312
314
321
324
341
342
412
413
421
423
431
432

Ghép các iterable

>>> import itertools
>>> a = [1, 2, 3, 4]
>>> for p in itertools.chain(itertools.combinations(a, 2), itertools.combinations(a, 3)):
...     print(p)
...
(1, 2)
(1, 3)
(1, 4)
(2, 3)
(2, 4)
(3, 4)
(1, 2, 3)
(1, 2, 4)
(1, 3, 4)
(2, 3, 4)
>>> for subset in itertools.chain.from_iterable(itertools.combinations(a, n) for n in range(len(a) + 1)):
...     print(subset)
...
()
(1,)
(2,)
(3,)
(4,)
(1, 2)
(1, 3)
(1, 4)
(2, 3)
(2, 4)
(3, 4)
(1, 2, 3)
(1, 2, 4)
(1, 3, 4)
(2, 3, 4)
(1, 2, 3, 4)

Nhóm các phần tử theo một tiêu chí

>>> import itertools
>>> for k, g in itertools.groupby('122333444455555'):
...     print(k, ''.join(g))
...
1 1
2 22
3 333
4 4444
5 55555
>>> m = {'a': 1, 'b': 2, 'c': 1, 'd': 2}
>>> sorted_m = dict(sorted(m.items(), key=lambda x: x[1]))
>>> for k, g in itertools.groupby(sorted_m, lambda x: x[1]):
...     print(k, ', '.join(e[0] for e in g))
...
1 c, a
2 b, d

Tham số của startswith

>>> s = 'http://google.com'
>>> s.startswith('http://') or s.startswith('https://')
True
>>> s.startswith(('http://', 'https://'))
True

Tương tự với endswithisinstance

>>> s.endswith('.com') or s.endswith('.net')
True
>>> s.endswith(('.com', '.net'))
True
>>> isinstance(1, (int, float))
True

Trong trình thông dịch, _ sẽ lưu giá trị phép tính cuối cùng

>>> 1 + 2
3
>>> _
3
>>> _ + 1
4
>>> foo = 'bar'
>>> foo
'bar'
>>> _ + 'foo'
'barfoo'

Tham số mặc định là mutable thì phải cẩn thận

Điều này đã được nhắc nhiều khi nói đến “Idiomatic Python”

>>> def foo(l=[]):
...     l.append(1)
...     print(l)
...
>>> foo()
[1]
>>> foo()
[1, 1]
>>> foo()
[1, 1, 1]
>>> def foo(l=None):
...     l = l or []
...     l.append(1)
...     print(l)
...
>>> foo()
[1]
>>> foo()
[1]
>>> foo()
[1]

Mở một server HTTP đơn giản

python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 ...
127.0.0.1 - - [24/Jul/2017 13:56:51] "GET / HTTP/1.1" 200 -

So sánh theo phong cách “toán học”

>>> x = 2
>>> 3 > x == 1
False
>>> 1 < x < 3
True
>>> 10 < 10*x < 30
True
>>> 10 < x**5 < 30
False
>>> 100 < x*100 >= x**6 + 34 > x <= 2*x < 5
True

Đảo ngược list

>>> list(reversed([1, 2, 3, 4]))
[4, 3, 2, 1]
>>> [1, 2, 3, 4][::-1]
[4, 3, 2, 1]
>>> tuple(reversed((1, 2, 3, 4)))
(4, 3, 2, 1)
>>> (1, 2, 3, 4)[::-1]
(4, 3, 2, 1)

Chuyển iterable thành string

>>> x = ['Hello', 'world', '!']
>>> ' '.join(x)
'Hello world !'

Class của Python có thể thay đổi tùy ý

>>> class Foo:
...     def one(self):
...         return 1
...
>>> c = Foo()
>>> c.one()
1
>>> def two(self):
...     return 2
...
>>> Foo.two = two
>>> c.two()
2

Hiển thị thuộc tính của 1 đối tượng

>>> x = [1, 2, 3]
>>> dir(x)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

Check dung lượng bộ nhớ mà đối tượng chiếm giữ

>>> x = [1, 2, 3]
>>> import sys
>>> sys.getsizeof(x)
88

Đặt breakpoint để debug

>>> import pdb
>>> for i in range(10):
...     if i == 5:
...         pdb.set_trace()
...
> <stdin>(1)<module>()
(Pdb) i
5

Zen of Python

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Tôi xin lỗi nếu bài viết có bất kỳ typo nào. Nếu bạn nhận thấy điều gì bất thường, xin hãy cho tôi biết.

Nếu có bất điều gì muốn nói, bạn có thể liên hệ với tôi qua các mạng xã hội, tạo discussion hoặc report issue trên Github.