Python là ngôn ngữ lập trình đa mô hình cấp cao, nhấn mạnh về khả năng đọc code. Nó được phát triển, duy trì và thường được sử dụng theo các quy tắc được gọi là The Zen of Python hoặc PEP 20.
Bài viết này cho thấy bạn một số ví dụ về các ví dụ Code Python Tốt và Code Python Tồi mà có thể chưa biết.
Code Python Tốt và Code Python Tồi
Sử dụng Unpacking để viết code Python súc tích
Packing (Đóng gói) và Unpacking (Giải nén) là các tính năng mạnh mẽ của Python.
Nếu bạn đã từng tham gia bất kỳ Khóa Học Python nào thì chắc bạn đã từng sử dụng Unpacking để gán giá trị cho các biến của mình như:
>>> a, b = 2, 'my-string'
>>> a
2
>>> b
'my-string'
Bạn có thể khai thác tính năng này để thực hiện trao đổi các biến ngắn gọn và dễ đọc nhất so với tất cả các ngôn ngữ lập trình khác:
>>> a, b = b, a
>>> a
'my-string'
>>> b
2
Rất ngầu phải không?
Unpacking có thể được sử dụng để gán cho nhiều biến trong các trường hợp phức tạp hơn.
Ví dụ: bạn có thể gán như thế này:
>>> x = (1, 2, 4, 8, 16)
>>> a = x[0]
>>> b = x[1]
>>> c = x[2]
>>> d = x[3]
>>> e = x[4]
>>> a, b, c, d, e
(1, 2, 4, 8, 16)
Nhưng thay vào đó, bạn có thể sử dụng ngắn gọn hơn như thế này, và tăng thêm khả năng đọc hiểu:
>>> a, b, c, d, e = x
>>> a, b, c, d, e
(1, 2, 4, 8, 16)
Rất hay phải không?
Nó còn có thể hay hơn thế kia nữa:
>>> a, *y, e = x
>>> a, e, y
(1, 16, [2, 4, 8])
Ở đây, Python Engine hiểu rằng *y là gom giá trị không được gán lại với nhau.
Sử dụng Chaining để viết mã súc tích
Python cho phép bạn xâu chuỗi các hành động so sánh. Vì vậy, bạn không nên sử dụng và kiểm tra xem hai hoặc nhiều so sánh có đúng không như bên dưới:
>>> x = 4
>>> x >= 2 and x <= 8
True
Thay vào đó, trong chương trình Python, bạn có thể viết ví dụ trên ở dạng gọn hơn, dễ đọc hơn như chúng ta vẫn hay viết trong Toán học:
>>> 2 <= x <= 8
True
>>> 2 <= x <= 3
False
Nếu bạn muốn gán cùng một giá trị cho nhiều biến, bạn có thể thực hiện theo cách đơn giản:
>>> x = 2
>>> y = 2
>>> z = 2
Hoặc sử dụng Unpacking như thế này:
>>> x, y, z = 2, 2, 2
Nhưng, khả năng đọc sẽ tốt hơn nếu bạn sử dụng Chaining:
>>> x = y = z = 2
>>> x, y, z
(2, 2, 2)
Tuy nhiên, hãy cẩn thận khi giá trị của bạn có thể thay đổi! Tất cả các biến đang tham chiếu đến nhau và giữ cùng một giá trị, nếu thay đổi 1 thì cả 3 cũng thay đổi theo.
Kiểm tra None
None là đối tượng đặc biệt và duy nhất trong Python. Nó có một mục đích tương tự giống như null trong các ngôn ngữ giống như C.
Nó có thể kiểm tra xem một biến có tham chiếu đến nó với các toán tử so sánh == hoặc != hay không.
>>> x, y = 2, None
>>> x == None
False
>>> y == None
True
>>> x != None
True
>>> y != None
False
Tuy nhiên, đọc có vẻ lẫn nhỉ :D. Trong trường hợp này, Python mong muốn bạn sử dụng is và is not hơn:
>>> x is None
False
>>> y is None
True
>>> x is not None
True
>>> y is not None
False
Ngoài ra, bạn nên sử dụng cấu trúc x is not None sẽ dễ đọc hơn x is None
Lặp liên tục và Ánh xạ trong Python
Bạn có thể thực hiện các lần lặp và vòng lặp for trong Python theo nhiều cách. Python cung cấp một số class để tạo điều kiện cho nó.
Trong hầu hết các trường hợp, bạn có thể sử dụng range:
>>> x = [1, 2, 4, 8, 16]
>>> for i in range(len(x)):
... print(x[i])
...
1
2
4
8
16
Tuy nhiên, có một cách tốt hơn để lặp lại theo trình tự:
>>> for item in x:
... print(item)
...
1
2
4
8
16
Nhưng nếu bạn muốn lặp theo thứ tự đảo ngược thì sao? range là một tùy chọn bạn có thể sử dụng:
>>> for i in range(len(x)-1, -1, -1):
... print(x[i])
...
16
8
4
2
1
Lặp qua một list được đảo ngược sẽ dễ dàng hơn:
>>> for item in x[::-1]:
... print(item)
...
16
8
4
2
1
Nhưng để tốt nhất, bạn nên sử dụng reversed:
>>> for item in reversed(x):
... print(item)
...
16
8
4
2
1
Đôi khi, có thể bạn cần in ra cả chỉ số và giá trị tương ứng:
>>> for i in range(len(x)):
... print(i, x[i])
...
0 1
1 2
2 4
3 8
4 16
Nó tốt hơn là sử dụng enumerate (Phép liệt kê) để có được một vòng lặp khác mang lại các bộ dữ liệu với các chỉ số và giá trị:
>>> for i, item in enumerate(x):
... print(i, item)
...
0 1
1 2
2 4
3 8
4 16
Nhưng nếu bạn muốn lặp lại qua hai hoặc nhiều chuỗi thì sao? Tất nhiên, bạn lại có thể sử dụng range (Lưu ý: x vẫn là list ở trên):
>>> y = 'abcde'
>>> for i in range(len(x)):
... print(x[i], y[i])
...
1 a
2 b
4 c
8 d
16 e
Trong trường hợp này, Python cũng có một giải pháp tốt hơn. Bạn có thể áp dụng zip để có kết quả tương tự:
>>> for item in zip(x, y):
... print(item)
...
(1, 'a')
(2, 'b')
(4, 'c')
(8, 'd')
(16, 'e')
Bạn có thể kết hợp nó với unpacking để dễ đọc hơn:
>>> for x_item, y_item in zip(x, y):
... print(x_item, y_item)
...
1 a
2 b
4 c
8 d
16 e
Xin hãy nhớ là: range có thể rất tốt. Nhưng rất tiếc là trong nhiều trường hợp, chúng ta có nhiều thay thế khác tối ưu hơn.
Lặp trên một dictionary theo key của nó:
>>> z = {'a': 0, 'b': 1}
>>> for k in z:
... print(k, z[k])
...
a 0
b 1
Tuy nhiên, bạn có thể áp dụng phương thức .key() và .value() để lặp qua key và value tương ứng.
So sánh với số 0 trong Python
Khi bạn dữ liệu dạng Số (Numeric), và bạn cần kiểm tra điều kiện các số có bằng 0 hay không. Bạn có thể sử dụng toán tử so sánh == và !=, nhưng không nên:
>>> x = (1, 2, 0, 3, 0, 4)
>>> for item in x:
... if item != 0:
... print(item)
...
1
2
3
4
Cách viết code Python tốt hơn trong trường hợp này là khai thác thực tế rằng: Số 0 được hiểu là false (trong Boolean context), trong khi tất cả các số khác được coi là true:
>>> bool(0)
False
>>> bool(-1), bool(1), bool(20), bool(28.4)
(True, True, True, True)
Hiểu điều này, bạn chỉ cần kiểm tra tính true / false của item thay vì phải cần đến toán tử != 0
>>> for item in x:
... if item:
... print(item)
...
1
2
3
4
Bạn có thể theo cùng một logic và sử dụng if not item thay vì if item == 0.
Tránh các đối số tùy chọn có thể thay đổi
Python có một hệ thống rất linh hoạt trong việc cung cấp các đối số cho các hàm (function) và phương thức (method).
Đối số tùy chọn là một phần của vấn đề này. Nhưng hãy cẩn thận: Bạn thường không muốn sử dụng các đối số tùy chọn có thể thay đổi. Hãy xem xét ví dụ sau:
>>> def f(value, seq=[]):
... seq.append(value)
... return seq
Thoạt nhìn, có vẻ là ổn, nếu bạn không cung cấp seq, f() sẽ thêm một giá trị vào danh sách trống và trả về một cái gì đó như [value]:
>>> f(value=2)
[2]
Có vẻ ổn, phải không?
Không! Hãy xem xét các ví dụ sau:
>>> f(value=4)
[2, 4]
>>> f(value=8)
[2, 4, 8]
>>> f(value=16)
[2, 4, 8, 16]
Đừng ngạc nhiên.
Có vẻ như cùng một thể hiện của một đối số tùy chọn (list trong trường hợp này) được cung cấp mỗi khi hàm được gọi.
Có thể đôi khi bạn sẽ thực hiện như đoạn mã trên.
Tuy nhiên, trong thực tế, bạn sẽ cần phải tránh điều này. Tránh nó bằng cách bổ sung thêm một ít logic. Một trong những cách có thể làm là:
>>> def f(value, seq=None):
... if seq is None:
... seq = []
... seq.append(value)
... return seq
Cách viết ngắn gọn hơn:
>>> def f(value, seq=None):
... if not seq:
... seq = []
... seq.append(value)
... return seq
Thử kiểm tra lại xem nào:
>>> f(value=2)
[2]
>>> f(value=4)
[4]
>>> f(value=8)
[8]
>>> f(value=16)
[16]
Trong hầu hết các trường hợp, đó là những gì người các lập trình viên Python chuyên nghiệp muốn.
Tránh Getters và Setters cổ điển
Python cho phép định nghĩa các phương thức getter và setter tương tự như C ++ và Java:
>>> class C:
... def get_x(self):
... return self.__x
... def set_x(self, value):
... self.__x = value
Đây là cách bạn có thể sử dụng chúng để get và set trạng thái của một đối tượng:
>>> c = C()
>>> c.set_x(2)
>>> c.get_x()
2
Trong một số trường hợp, đây là cách tốt nhất để làm việc này.
Tuy nhiên, tốt hơn là sử dụng properties, đặc biệt là trong trường hợp đơn giản:
>>> class C:
... @property
... def x(self):
... return self.__x
... @x.setter
... def x(self, value):
... self.__x = value
Properties được coi là tốt hơn phương thức getter và setter cổ điển.
Bạn có thể sử dụng chúng như trong C#, tức là giống như các thuộc tính dữ liệu thông thường:
>>> c = C()
>>> c.x = 2
>>> c.x
2
Vì vậy, nói chung, đây là một thực hành tốt.
Tránh truy cập member của Protected Class
Python không có các member private class thực sự. Tuy nhiên, có một quy ước nói rằng bạn không nên truy cập hoặc sửa đổi các member bắt đầu bằng dấu gạch dưới (_) bên ngoài các thể hiện của họ.
Chúng không được đảm bảo để bảo tồn các hành vi hiện có.
Hãy xem đọa code sau:
>>> class C:
... def __init__(self, *args):
... self.x, self._y, self.__z = args
...
>>> c = C(1, 2, 4)
Những trường hợp của lớp C có ba data member: .x .y, và ._Cz.
Nếu tên của một thành viên bắt đầu với hai dấu gạch dưới (dunder), nó bị đọc sai và được sửa đổi.
Đầy là lý do bạn có ._Cz thay vì ._z.
Bây giờ, nó khá OK để truy cập vào hoặc sửa đổi .x trực tiếp:
>>> c.x # OK
1
Bạn cũng có thể truy cập hoặc sửa đổi ._y từ bên ngoài thể hiện của nó, nhưng nó đã coi đó là code tồi:
>>> c._y # Possible, but a bad practice!
2
Bạn không thể thể truy cập vào .z vì nó không thể truy cập, nhưng bạn có thể truy cập hoặc sửa đổi ._Cz:
>>> c.__z # Error!
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'C' object has no attribute '__z'
>>> c._C__z # Possible, but even worse!
4
>>>
Bạn nên tránh làm điều này. Tác giả của class có lẽ đã bắt đầu những cái tên bằng (các) dấu gạch dưới để nói với bạn, về việc "Đừng sử dụng nó".
Sử dụng Context Manager để giải phóng tài nguyên
Đôi khi, bạn được yêu cầu phải viết code để quản lý tài nguyên đúng cách. Nó thường là trường hợp khi làm việc với các tệp, kết nối cơ sở dữ liệu hoặc các thực thể khác có tài nguyên không được quản lý.
Ví dụ: Bạn có thể mở tệp và xử lý tệp:
>>> my_file = open('filename.csv', 'w')
>>> # do something with `my_file`
Để quản lý bộ nhớ đúng cách, bạn cần đóng tệp này sau khi đã hoàn thành công việc:
>>> my_file = open('filename.csv', 'w')
>>> # do something with `my_file and`
>>> my_file.close()
Làm theo cách này tốt hơn là không làm gì cả.
Nhưng, nếu một ngoại lệ xảy ra trong khi xử lý tệp của bạn thì sao? Khi đó my_file.close() không bao giờ được thực thi.
Bạn có thể xử lý việc này bằng cú pháp xử lý ngoại lệ hoặc với Contex manager. Context manager có nghĩa là bạn đặt code của mình bên trong khối with:
>>> with open('filename.csv', 'w') as my_file:
... # do something with `my_file`
Sử dụng khối with có nghĩa là các phương thức đặc biệt .enter() và .exit() được gọi, ngay cả trong các trường hợp ngoại lệ.
Những phương thức này sẽ tối ưu tài nguyên cho bạn.
Bên cạnh đó, bạn có thể đạt được các cấu trúc đặc biệt mạnh mẽ bằng cách kết hợp Context manager và xử lý ngoại lệ.
Nên nhớ là Code Python phải thanh lịch, súc tích và dễ đọc.
Bạn đã biết kha khá về Code Tốt và Code Tồi rồi đấy
Bài viết này đưa ra một số lời khuyên về cách viết Code Python tốt hơn, hiệu quả hơn, dễ đọc hơn và ngắn gọn hơn.
Nói tóm lại, biết viết Code Python Tốt thì cuộc sống của bạn sẽ dễ dàng hơn.
---
HỌC VIỆN ĐÀO TẠO CNTT NIIT - ICT HÀ NỘI
Dạy học Lập trình chất lượng cao (Since 2002). Học làm Lập trình viên. Hành động ngay!
Đc: Tầng 3, 25T2, N05, Nguyễn Thị Thập, Cầu Giấy, Hà Nội
SĐT: 02435574074 - 0914939543 - 0353655150
Email: hello@niithanoi.edu.vn
Fanpage: https://facebook.com/NIIT.ICT/
#niit #niithanoi #niiticthanoi #hoclaptrinh #khoahoclaptrinh #hoclaptrinhjava #hoclaptrinhphp