Courses

Thiền Python (Zen of Python) là gì?
Thiền Python là tập hợp mười chín châm ngôn đóng vai trò là các nguyên tắc định hướng cho thiết kế của Python. Thực ra, lẽ ra phải có hai mươi châm ngôn; nhưng Guido van Rossum, người tạo ra Python, đến nay vẫn chưa thêm châm ngôn cuối cùng như Tim Peters, tác giả của Thiền Python, dự định. Người ta kể rằng Guido nói châm ngôn thứ hai mươi bị thiếu là “một trò đùa nội bộ kỳ quặc của Tim Peters.”
Mặc dù thiếu châm ngôn thứ hai mươi, Thiền Python đã được chuẩn hóa thành PEP 20 vào năm 2004 do ảnh hưởng mạnh mẽ của nó đến quy trình phát triển của các lập trình viên Python. Làm theo Thiền Python không bắt buộc, nhưng nên biết và ghi nhớ. Nếu quên các bài học này, bạn có thể dễ dàng làm mới trí nhớ bằng cách chạy import this từ trình thông dịch 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!
Trong bài viết này, chúng ta sẽ đi sâu vào từng bài học.
#1 Đẹp thì tốt hơn xấu
Nếu bạn là lập trình viên, không nghi ngờ gì bạn rất thành thạo. Khả năng giải quyết vấn đề bằng mã không hề dễ, nhưng với tư cách Pythonista, kỳ vọng dành cho bạn còn cao hơn. Mã đẹp thì tốt hơn mã xấu. Sự trỗi dậy nhanh chóng của Python một phần là nhờ tính dễ tiếp cận: đơn giản, dễ đọc và tao nhã. Là nhà phát triển Python, bạn có trách nhiệm duy trì tiêu chuẩn “pythonic” bằng cách viết mã đẹp.
Thế nào là mã đẹp? Điều này mang tính chủ quan - vẻ đẹp nằm trong mắt người xem. Một quy tắc đơn giản để tự kiểm soát là viết mã sạch, dễ đọc và có thể được các nhà phát triển khác dễ dàng hiểu. Ví dụ:
"""
Initial code was created by Vishal Sharma
See: https://towardsdatascience.com/the-zen-of-python-a-guide-to-pythons-design-principles-93f3f76d088a
"""
def collatz(num):
if num % 2 == 0:
return num // 2
else:
return 3 * num + 1
number = input("Enter a number: ")
while number != 1:
number = collatz(int(number))
print(number)
"""
Enter a number: 5
16
8
4
2
1
"""
Đoạn mã trên hoạt động hoàn toàn ổn, nhưng không nhất thiết là đẹp. Chúng ta có một nhánh else không cần thiết trong hàm collatz có thể bỏ đi như sau:
def collatz(num):
if num % 2 == 0:
return num // 2
return 3 * num + 1
Vì vậy, khi đứng trước hai đoạn mã đều chạy được, bạn nên chọn phương án đơn giản hơn, dễ đọc hơn và dễ hiểu hơn.
#2 Tường minh thì tốt hơn ngầm định
Gọi một châm ngôn là “tự nó đã rõ” là một sai lầm lớn - tuy vậy bạn vẫn sẽ thấy người ta nói thế. Cốt lõi của “tường minh thì tốt hơn ngầm định” là mã dài dòng rõ ràng được ưa chuộng hơn mã gián tiếp. Hãy làm mọi cách để chức năng của mã không bị ẩn sau ngôn ngữ khó hiểu. Người chưa từng biết về chương trình của bạn vẫn nên có thể hiểu chuyện gì đang diễn ra trong mã.
#3 Đơn giản thì tốt hơn phức tạp
Bạn có lẽ sẽ bật cười nếu bạn mình lôi máy rửa áp lực cao ra để rửa bát sau bữa tối. Không chỉ là giải pháp ngốc nghếch, mà còn có khả năng làm hỏng bát đĩa. Tương tự, áp dụng một giải pháp cực kỳ phức tạp cho một vấn đề lập trình đơn giản là rất không thực tế. Không cần làm phức tạp hóa giải pháp của bạn, dù bạn nghĩ như vậy trông mình “thông minh” hơn. Bạn có thể gây hại nhiều hơn lợi, và điều đó không được đánh giá cao trong Python.
Hãy xem hàm đảo ngược chuỗi này:
def reverse_string(string):
if len(string) == 1:
return string
else:
return reverse_string(string[1:]) + string[0]
string = "kurtis"
print(reverse_string(string))
"""
sitruk
"""
Đoạn mã trên chắc chắn giải quyết vấn đề, nhưng không cần thiết phải phức tạp như vậy. Nếu bạn nắm vững Python, bạn biết rằng có thể đảo chuỗi bằng indexing.
string = "kurtis"
# annonymous function solution
reverse_string = lambda x: x[::-1]
# function solution
def reverse(string):
return string[::-1]
print(reverse_string(string))
print(reverse(string))
"""
sitruk
sitruk
"""
#4 Phức hợp thì tốt hơn rối rắm
Rửa bát bằng máy xịt áp lực là không thực tế: bạn sẽ hiệu quả hơn với giải pháp đơn giản như nước vòi. Nhưng nếu vấn đề là rửa một chiếc Range Rover 4x4 thì sao? Châm ngôn trước và châm ngôn này nhắc chúng ta rằng cả kỹ thuật đơn giản lẫn phức hợp đều có thể được áp dụng để giải quyết một vấn đề. Rửa một chiếc Range Rover 4x4 không phải là vấn đề đơn giản và cố áp dụng các kỹ thuật như khi rửa bát có thể làm được nhưng lại rối rắm hơn so với dùng máy xịt. Vì vậy, hãy ưu tiên đơn giản thay vì phức tạp, nhưng trong những tình huống đơn giản là không thực tế, bạn nên chọn phương án phức hợp - hãy biết giới hạn của sự đơn giản.
#5 Phẳng thì tốt hơn lồng nhau
Lập trình viên thích tổ chức mọi thứ thành danh mục, danh mục con, và danh mục con của danh mục con để tách biệt chức năng. Nghe có vẻ ngăn nắp, nhưng tổ chức mã theo cách như vậy có thể gây rối hơn là gọn.
Không có gì sai khi đặt toàn bộ mã của bạn trong một mô-đun lớp trên cùng: nghĩa là bạn sẽ không phải làm điều gì đó như from spam.foo.bar.john.doe import chicken để truy cập một chức năng cụ thể. Càng thêm nhiều danh mục con, mã của bạn càng trở nên phức tạp. Hãy cố gắng bám vào cấu trúc phẳng khi có thể.
#6 Thưa thì tốt hơn đặc
Lập trình viên được xem là những người thông minh bậc nhất, nên không cần phô trương trí tuệ bằng các “mẹo vặt” quá mức. Câu quen thuộc nhất bạn sẽ nghe là “làm [một tác vụ phức tạp nào đó] chỉ trong 1 dòng mã.” Đôi khi, one-liner là hợp lý, nhưng hãy tránh việc hy sinh khả năng đọc chỉ để nhét toàn bộ chức năng vào một dòng.
Xem ví dụ đoạn mã này:
"""
Code source Al Sweigart
See: https://inventwithpython.com/blog/author/al-sweigart.html0
"""
print('\n'.join("%i bytes = %i bits which has %i possible values." % (j, j*8, 256**j-1) for j in (1 << i for i in range(8))))
"""
1 bytes = 8 bits which has 255 possible values.
2 bytes = 16 bits which has 65535 possible values.
4 bytes = 32 bits which has 4294967295 possible values.
8 bytes = 64 bits which has 18446744073709551615 possible values.
16 bytes = 128 bits which has 340282366920938463463374607431768211455 possible values.
32 bytes = 256 bits which has 115792089237316195423570985008687907853269984665640564039457584007913129639935 possible values.
64 bytes = 512 bits which has 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095 possible values.
128 bytes = 1024 bits which has 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137215 possible values.
"""
Đoạn mã này hoạt động, nhưng thực sự rất khó hiểu: chật chội và khó đọc.
Hãy xem cùng đoạn mã đó khi viết theo kiểu thưa:
# Sparse example
bytes_and_bits = {j: j*8 for j in (1 << i for i in range(8))}
for bytes, bits in bytes_and_bits.items():
print(f"{bytes} bytes = {bits} which has {256**bytes-1} possible values")
"""
1 bytes = 8 which has 255 possible values
2 bytes = 16 which has 65535 possible values
4 bytes = 32 which has 4294967295 possible values
8 bytes = 64 which has 18446744073709551615 possible values
16 bytes = 128 which has 340282366920938463463374607431768211455 possible values
32 bytes = 256 which has 115792089237316195423570985008687907853269984665640564039457584007913129639935 possible values
64 bytes = 512 which has 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095 possible values
128 bytes = 1024 which has 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137215 possible values
"""
Đoạn mã trên dễ đọc hơn nhiều và làm đúng y như one-liner kia.
#7 Khả năng đọc quan trọng
Nếu bạn chưa đoán ra, thì trong Python, khả năng đọc rất quan trọng. Nhiệm vụ của bạn là viết mã một lần. Tuy nhiên, nhiều khả năng nó sẽ được đọc nhiều lần. Xét điều này, việc lược nguyên âm trong tên biến và hàm không phải ý hay: trong tình huống bạn có thể đặt tên hàm là create_lst hoặc create_list, hãy chọn phương án sau.
#8 Trường hợp đặc biệt không đủ đặc biệt để phá vỡ quy tắc
Python [và lập trình nói chung] đầy rẫy các thực hành tốt cần tuân thủ. Tốt hơn là làm theo các thực hành tốt thay vì làm theo cách riêng, vì cách riêng thường dẫn đến mã không nhất quán, khó đọc.
#9 Tuy vậy, tính thực dụng vượt lên trên sự thuần khiết
Bài học chín là phần mở rộng của bài học tám. Đúng, tốt hơn là làm theo thực hành tốt, nhưng cố gắng bằng mọi giá để tuân thủ quy tắc cũng có thể tạo ra mã khó đọc. Vì vậy, mỗi quy tắc có thể có ngoại lệ. Nếu cách bạn giải quyết vấn đề thực dụng hơn, dễ đọc hơn và dễ hiểu hơn, thì tốt hơn là bạn nên lệch khỏi các thực hành tốt đã thiết lập.
#10 Lỗi không bao giờ nên bị bỏ qua trong im lặng
Lỗi im lặng là khi chương trình trả về mã lỗi hoặc None thay vì ném ngoại lệ. Tốt hơn là chương trình bị dừng hẳn còn hơn là lỗi bị lặng lẽ bỏ qua và tiếp tục chạy. Lâu dài, việc ém lỗi có thể dẫn đến bug khó xử lý hơn nhiều trong chương trình của bạn.
#11 Trừ khi bạn chủ động dập tắt
Bài học mười một là phần mở rộng của bài học mười. Có những trường hợp bạn muốn bỏ qua lỗi do chương trình gây ra. Khi đó, thực hành tốt nhất là chủ động dập tắt lỗi đó trong mã của bạn.
#12 Khi mơ hồ, hãy khước từ cám dỗ đoán mò
Máy tính chỉ làm những gì ta bảo: nếu mã của bạn không hoạt động như mong muốn, là vì nó đang làm đúng thứ bạn đã chỉ thị. Cố sửa hành vi bằng cách mù quáng thử nhiều giải pháp khác nhau cho đến khi một cách “ăn” là chiến lược tệ - bạn có thể chỉ che lấp vấn đề thay vì giải quyết nó. Hãy cưỡng lại cám dỗ đó. Thay vào đó, suy nghĩ logic về vấn đề và vận dụng tư duy phản biện để đưa ra giải pháp phù hợp.
#13 Nên có một - và tốt nhất chỉ một - cách rõ ràng để làm việc đó
Phương châm của ngôn ngữ Perl là: “Có nhiều hơn một cách để làm!” Quá nhiều lựa chọn thường dẫn đến quá tải lựa chọn. Tương tự xảy ra khi có nhiều cách viết mã để đạt cùng mục tiêu. Bạn có nhiều linh hoạt hơn khi viết mã, nhưng giờ bạn phải học tất cả các cách có thể mà mã có thể đã được viết để còn đọc được nó: lượng công việc thừa đó là không cần thiết.
#14 Dù cách đó lúc đầu có thể không hiển nhiên, trừ khi bạn là người Hà Lan
Châm ngôn này thể hiện khiếu hài hước của Tim Peters: Guido van Rossum, người tạo ra ngôn ngữ lập trình Python và từng là Benevolent Dictator For Life (BDFL) của Python, là người Hà Lan. Đây là một câu đùa nhằm nhắc chúng ta rằng việc hiểu và nhớ các quy tắc ngôn ngữ của Python là khó với tất cả mọi người, trừ người đã tạo ra nó.
#15 Bây giờ thì tốt hơn là không bao giờ
Châm ngôn này nhắc rằng mã bị kẹt trong vòng lặp vô hạn hoặc treo đơ còn tệ hơn cả mã không chạy.
#16 Tuy nhiên, “không bao giờ” thường tốt hơn là ngay lập tức
Tiếp nối bài học mười sáu: tốt hơn là chờ chương trình chạy xong hơn là dừng sớm và nhận kết quả sai.
#17 Nếu cách hiện thực khó giải thích, đó là ý tưởng tồi
Việc bạn tự hiểu hiện thực của mình là chưa đủ - “tôi biết mình định nói gì” không ổn. Lập trình là hoạt động tập thể, và nếu bạn không thể giải thích cách hiện thực cho đồng đội, rất có thể bạn đã làm giải pháp quá phức tạp.
#18 Nếu cách hiện thực dễ giải thích, đó có thể là ý tưởng hay
Tuy nhiên, mã dễ giải thích không nhất thiết đồng nghĩa với mã tốt - nó chỉ dễ giải thích mà thôi. Bạn vẫn có thể có mã chưa tốt, nhưng việc nó dễ giải thích nghĩa là bạn đang đi đúng hướng.
#19 Namespace là một ý tưởng quá tuyệt - hãy dùng chúng nhiều hơn!
Namespace là một trừu tượng trong Python để tổ chức các tên gán cho đối tượng trong chương trình. Khi có một namespace và phạm vi cụ thể, Python có thể xác định bạn đang nói đến đối tượng nào khi gọi một tên ký hiệu. Tất cả châm ngôn này muốn nói là cách Python tổ chức các tên ký hiệu ở tầng bên dưới là rất tuyệt.
Kết luận
Trong bài viết này, chúng tôi đã trình bày cách hiểu của mình về Thiền Python, bộ hướng dẫn nhằm khuyến khích lập trình viên Python viết mã sạch, dễ đọc. Có người coi Thiền Python là bản thiết kế tối thượng cho việc viết mã trơn tru, có người lại không quá coi trọng. Chúng tôi gợi ý bạn thử áp dụng các nguyên tắc này vào mã của chính mình để tự thấy chúng cải thiện công việc như thế nào. Nếu bạn muốn tìm hiểu thêm về viết mã tối ưu, hãy xem khóa Writing Efficient Python Code của DataCamp.