PEP8 - Chuẩn Kết Nối Toàn Cầu Của Python Dev (Phần 1)

PEP8 - Chuẩn Kết Nối Toàn Cầu Của Python Dev (Phần 1)

Nếu bạn làm việc 1 mình, mọi thứ dường như dễ dàng để làm việc. Nhưng điều gì sẽ xảy ra khi code của bạn cần được người khác review, hỗ trợ khi có lỗi hoặc bạn chuyển qua làm việc team-work ? Đến lúc ấy, bạn cần phải biết về "chuẩn định dạng code chung".

Thông thường thì với mỗi công ty/nhóm sẽ có chung một chuẩn quy tắc (code conventions) để dễ dàng review code chéo cho nhau.

Tuy nhiên nếu các bạn là một lập trình viên Python, thực sự là các bạn đã được ưu ái hơn các lập trình viên của ngôn ngữ khác rất nhiều vì kể cả khi các bạn không tham gia vào một team làm việc nào, các bạn cũng dễ dàng được "học" và làm quen với chuẩn định dạng code "toàn cầu" có tên PEP-8.

Python Enhancement Proposal #8 (PEP-8)

Python Enhancement Proposal #8 (thường được viết tắt PEP-8), là tập hợp các chỉ dẫn về định dạng code, phong cách lập trình được chia sẻ chung giữa các lập trình viên Python. PEP-8 được đề xuất bởi các chuyên gia trong "hội đồng" sáng lập ngôn ngữ lập trình Python. Việc viết code theo đúng chuẩn chung PEP-8 sẽ giúp cho các lập trình viên cảm thấy thoải mái hơn khi đọc code của nhau, kiểu như là đọc code người khác mà như đang đọc code của mình vậy.

PEP-8 cung cấp nhiều chỉ dẫn để lập trình viên có thể viết "clean code" trong Python. Bạn có thể tìm thấy nhiều điểm chung trong PEP-8 và những hướng dẫn được đề cập đến trong cuốn sách "Clean Code: A Handbook of Agile Software Craftsmanship - Robert C. Martin".

Toàn bộ các chỉ dẫn của PEP-8 được cung cấp trên trang chủ python.org và được cập nhật mỗi khi có thay đổi. Tóm tắt lại thì chuẩn PEP-8 gồm các mục lớn sau: 

  • Trình bày các đoạn mã nguồn: Code Lay-out, blank-line, white-space,...
  • Naming Conventions ( quy tắc đặt tên): tên biến, function, class, module
  • Expressions and Statements: Các quy tắc viết code tường minh

Trong bài viết, tôi sẽ thông tin đến các bạn chuẩn PEP-8 trong việc trình bày các đoạn mã nguồn gồm:

  1. Indentation - Thụt lề, thụt dòng
  2. White-space -Khoảng trắng

  3. Blank-line - Dòng trống

  4. Công cụ hỗ trợ

Indentation - Thụt lề, dòng

Indentation: Thụt lề, thụt dòng. Đây là "đặc sản" riêng của ngôn ngữ lập trình Python, để bắt đầu và kết thúc mỗi block level lệnh không cần dùng đến các từ khóa BEGIN-END hay cặp { }. Mỗi block level sẽ thụt vào tương đương với 4 white-space so với level phía trên liền kề với nó.

1. Nguyên tắc 04 khoảng trắng:

Sử dụng bốn khoảng trắng cho mỗi level của đoạn code. Điều này mang ý nghĩa cú pháp của Python. Nếu số lượng khoảng trắng không đủ, khi thực hiện chạy, chương trình sẽ báo lỗi.

Ví dụ: 

Theo thói quen, khi thực hiện xuống dòng, chúng ta thường dùng tab để thụt dòng nhưng các pythonista khuyên chúng ta nên sử dụng khoảng trắng thay vì thực hiện nhấn phím tab.

Nếu vẫn muốn sử dụng tab thì trên các text editor/IDE , bạn cần setup/config thật chuẩn xác sao cho 1 lần nhấn tab tương ứng với 2 hoặc 4 khoảng trắng.

Nếu một ngày nào đó, bạn phải edit code trên một editor "lạ", đừng vội dùng tab, hãy dùng 4 lần space đề phòng trường hợp báo lỗi IndentationError mà không biết tại sao.

2. Mỗi dòng nên có độ dài nhỏ hơn 79 ký tự.

Có khá nhiều tranh cãi trong việc setup layout code 1 dòng nên có bao nhiêu ký tự là vừa đủ, 79, 109 hay nhiều hơn.
PEP-8 đề xuất là 79 ký tự vì liên quan đến chuẩn độ phân giải màn hình thường gặp (1680x1040), khi thực hiện edit code, 79 ký tự sẽ không bị wrap-text (tự động điều chỉnh xuống dòng cho dễ nhìn - nhưng thực tế vẫn là trên một dòng).

Các biểu thức dài khi biễu diễn trong một dòng sẽ quá 79 ký tự, nếu cần phải xuống dòng sẽ phải thực hiện thụt vào 4 khoảng trắng (xuống 1 level) so với khoảng thụt vào của dòng đầu tiên.

Nếu dòng cuối cùng của những ngắt dòng 79 ký tự là dấu : và chuẩn bị cho một block ở level thấp hơn thì dòng cuối cần thụt dòng vào thêm 4 white-space so với dòng bên trên để phân biệt rõ hơn các block level.

Ví dụ: 

White-space -Khoảng trắng

Khoảng trắng, tương đương với 01 lần gõ phím space (phím dài nhất trên bàn phím cho bạn nào chưa biết  ). PEP-8 cung cấp khá nhiều chỉ dẫn để các lập trình viên làm theo với white-space sao cho code có thể nhìn thật thân thiện.

1. Đặt khoảng trắng trước và sau (bao xung quanh) các toán tử nhị phân, logic và phép gán: =, +=, -=, ==, <, >, !=, <>, <=, >=, in, not in, is, is not, and, or, not

wrong:

index=index+1
count +=1
count >=0
index!= 10

correct:

index = index + 1
count += 1
count >= 0
index != 10


Lưu ý: Khi thực hiện gán dữ liệu mặc định cho tham số truyền vào function thì không đặt khoảng trắng bao quanh dấu =

wrong:

def sum_number_v1(number1, number2 = 0):
    total = number1 + number2
    return total

correct:

def sum_number_v1(number1, number2=0):
    total = number1 + number2
    return total

2. Với các toán tử số học +, -, *, /, // lựa chọn bao xung quanh các toán tử bằng khoảng trắng (ở cả hai bên) hoặc không có khoảng trắng nào.

wrong:

def calculate_number_v1():
    number1 = 10
    number2 = 20
    total = number1 +number2
    minus = number1- number2
    return total, minus

correct:

def calculate_number_v1():
    number1 = 10
    number2 = 20
    total = number1 + number2
    minus = number1-number2
    return total, minus


3. Trong các dòng lệnh có chứa dấu :, dòng lệnh tiếp theo là chuyển level của block code (cuối cùng của dòng chứa từ khóa def, class, if, while,...) thì phía trước và phía sau dấu : không có khoảng trắng (trừ trường hợp in-line code).

Trong trường hợp sử dụng : với khai báo đữ liệu thì phía trước : không có khoảng trắng, phía sau sẽ có khoảng trắng
Thường sử dụng trong khai báo dữ liệu của dictionary, kiểu dữ liệu của biến.

Ví dụ:

wrong:

values = {
    "VT1":"Gói dịch vụ 1",
    "VT2": "Gói dịch vụ 2"
}


def sum_number_v1(number1:int, number2=0) :
    total = number1 + number2
    return total

correct:

values = {
    "VT1": "Gói dịch vụ 1",
    "VT2": "Gói dịch vụ 2"
}


def sum_number_v1(number1: int, number2=0):
    total = number1 + number2
    return total

4. Khi bạn muốn comment code, sau dấu # nên đặt ít nhất một khoảng trắng.

wrong:

#This is a wrong style comment

correct:

# This is a correct style comment

Blank-line - Dòng trống

Dòng trống, đúng như cái tên của nó, đây là một dòng khi chúng ta nhấn ENTER trên bàn phím và lưu ý xóa hết toàn bộ white-space (nếu có trên dòng này), tức là con trỏ chuột khi ở blank-line sẽ nằm ở vị trí đầu tiên bên trái.

1. Trong một file (module), phía trên và phía dưới (bao xung quanh) của phần định nghĩa function, class là 02 blank-line.

2. Với các function được định nghĩa trong một class thì bao xung quanh của function là 01 blank-line.

3. Dòng cuối cùng của một file/module là một dòng trống.

Ví dụ:


4. Trong một block code, có thể đặt 01 dòng trống vào vị trí nào đó để code có thể tạo điểm nhấn và dễ nhìn hơn :)
Ví dụ:

def is_prime_optimal(number):
    if number in [2, 3, 5]:
        return True
    if number % 2 == 0 or number % 3 == 0 or number % 5 == 0 or number < 2:
        return False
    if number < 49:
        return True
        
    if (number % 7) == 0 or (number % 11) == 0 or (number % 13) == 0 or \
        (number % 17) == 0 or (number % 19) == 0 or (number % 23) == 0 or \
        (number % 29) == 0 or (number % 31) == 0 or (number % 37) == 0 or \
            (number % 41) == 0 or (number % 43) == 0 or (number % 47) == 0:
        return False
    if number < 2809:
        return True

    max_range = int(math.sqrt(number)) + 1
    for value in range(53, max_range, 2):
        if number % value == 0:
            return False
    return True

Công cụ hỗ trợ

Để nhớ hết chỗ rule trên cũng mệt đầu vô cùng, tất nhiên đã đưa ra chuẩn thì ông Guido và các cộng sự cũng sẽ đưa ra công cụ để hỗ trợ các lập trình viên phát hiện các vi phạm là style. Công cụ thường được mọi người dùng trên command-line có tên flake8 (https://pypi.org/project/flake8/). 

Các bạn có thể install package này từ kho của python:

>pip install flake8

Collecting flake8
  Using cached https://files.pythonhosted.org/packages/6c/20/6326a9a0c6f0527612bae748c4c03df5cd69cf06dfb2cf59d85c6e165a6a/flake8-3.8.3-py2.py3-none-any.whl
Requirement already satisfied: mccabe<0.7.0,>=0.6.0 in c:\users\tieubavuong\appdata\roaming\python\python38\site-packages (from flake8) (0.6.1)
Collecting pyflakes<2.3.0,>=2.2.0 (from flake8)
  Using cached https://files.pythonhosted.org/packages/69/5b/fd01b0c696f2f9a6d2c839883b642493b431f28fa32b29abc465ef675473/pyflakes-2.2.0-py2.py3-none-any.whl
Collecting pycodestyle<2.7.0,>=2.6.0a1 (from flake8)
  Using cached https://files.pythonhosted.org/packages/10/5b/88879fb861ab79aef45c7e199cae3ef7af487b5603dcb363517a50602dd7/pycodestyle-2.6.0-py2.py3-none-any.whl
Installing collected packages: pyflakes, pycodestyle, flake8
Successfully installed flake8-3.8.3 pycodestyle-2.6.0 pyflakes-2.2.0

Sau khi cài đặt xong, copy/paste hoặc gõ lại script dưới đây, lưu lại vào thành file: pep8_sample.py

import math
def add_number_v1():
    #  add number version 1
    pass

def add_number_v2():
    #add number version 2
    pass




def is_prime_optimal(number):
    if number in [2, 3,5] :
        return True
    if number % 2 == 0 or number % 3 == 0 or number % 5 == 0 or number < 2: 
        return False
    if number < 49:
        return True
    if (number %  7) == 0 or (number % 11) == 0 or (number % 13) == 0 or (number % 17) == 0 or \
       (number % 19) == 0 or (number % 23) == 0 or (number % 29) == 0 or (number % 31) == 0 or \
       (number % 37) == 0 or (number % 41) == 0 or (number % 43) == 0 or (number % 47) == 0:
        return False
    if number < 2809:
        return True

    
    max_range = int(math.sqrt(number)) + 1
    for value in range(53, max_range, 2):
        if number % value == 0:
            return False
    return True


class SubNumber:
    

    def check_number_v1(self):
        pass


    def check_number_v2(self):
        pass




Mở terminal, di chuyển đến thư mục có chứa file pep8_sample.py, thực hiện câu lệnh:

flake8 pep8_sample.py

Bạn sẽ nhận được một loạt các lỗi:

pep8_sample.py:2:1: E302 expected 2 blank lines, found 0
pep8_sample.py:6:1: E302 expected 2 blank lines, found 1
pep8_sample.py:7:5: E265 block comment should start with '# '
pep8_sample.py:13:1: E303 too many blank lines (4)
pep8_sample.py:14:23: E231 missing whitespace after ','
pep8_sample.py:14:26: E203 whitespace before ':'
pep8_sample.py:16:76: W291 trailing whitespace
pep8_sample.py:20:17: E222 multiple spaces after operator
pep8_sample.py:20:80: E501 line too long (96 > 79 characters)
pep8_sample.py:21:80: E501 line too long (96 > 79 characters)
pep8_sample.py:22:80: E501 line too long (92 > 79 characters)
pep8_sample.py:27:1: W293 blank line contains whitespace
pep8_sample.py:28:5: E303 too many blank lines (2)
pep8_sample.py:36:1: W293 blank line contains whitespace
pep8_sample.py:38:5: E303 too many blank lines (2)
pep8_sample.py:42:5: E303 too many blank lines (2)
pep8_sample.py:46:1: W391 blank line at end of file

Đừng hốt hoảng, chúng ta sẽ từ từ phân tích từng đoạn nhé (phân tích từ cuối lên để đỡ phải "reload" não nhiều lần)

pep8_sample.py:46:1: W391 blank line at end of file
Warning này cảnh báo cho chúng ta biết vi phạm vào rule blank-line ở trên: Dòng cuối cùng của một file sẽ là 01 dòng trắng.

Chúng ta sẽ xóa bớt các dòng trắng, giữ lại 01 dòng trắng là dòng 44. và chạy thử lại lệnh flake8 nhé.

> flake8 pep8_sample.py
pep8_sample.py:2:1: E302 expected 2 blank lines, found 0
pep8_sample.py:6:1: E302 expected 2 blank lines, found 1
pep8_sample.py:7:5: E265 block comment should start with '# '
pep8_sample.py:13:1: E303 too many blank lines (4)
pep8_sample.py:14:23: E231 missing whitespace after ','
pep8_sample.py:14:26: E203 whitespace before ':'
pep8_sample.py:16:76: W291 trailing whitespace
pep8_sample.py:20:17: E222 multiple spaces after operator
pep8_sample.py:20:80: E501 line too long (96 > 79 characters)
pep8_sample.py:21:80: E501 line too long (96 > 79 characters)
pep8_sample.py:22:80: E501 line too long (92 > 79 characters)
pep8_sample.py:27:1: W293 blank line contains whitespace
pep8_sample.py:28:5: E303 too many blank lines (2)
pep8_sample.py:36:1: W293 blank line contains whitespace
pep8_sample.py:42:5: E303 too many blank lines (2)

Warning tại dòng 46 đã hết, chúng ta chuyển tiếp sang warning tiếp theo: pep8_sample.py:42:5: E303 too many blank lines (2).
Cái này vi phạm vào blank-line đã được nêu ở phía trên: Các function trong một class được bao xung quanh bởi 01 dòng trắng. Như vậy, chúng ta chỉ cần xóa dòng số 41 đi là hết lỗi

pep8_sample.py:36:1: W293 blank line contains whitespace
pep8_sample.py:27:1: W293 blank line contains whitespace
Warning này báo hiệu tại dòng số 27, 36 có tồn tại khoảng trắng, chúng ta chỉ cần đến dòng 27, 36, xóa hết khoảng trắng và giữ cho dòng này đúng là blank-line.

pep8_sample.py:28:5: E303 too many blank lines (2)
Tương tự như ở trên, chúng ta sẽ đến dòng 28 và thực hiện xóa bớt một blank-line

pep8_sample.py:20:80: E501 line too long (96 > 79 characters)
pep8_sample.py:21:80: E501 line too long (96 > 79 characters)
pep8_sample.py:22:80: E501 line too long (92 > 79 characters)

Báo hiệu tại dòng 20, 21, 22 có vấn đề về độ dài của dòng (>79 ký tự). Chúng ta sẽ phải xử lý xuống dòng thật khéo léo để xóa bỏ phần warning này.
Giả sử chúng ta sẽ xuống dòng như dưới đây:

def is_prime_optimal(number):
    if number in [2, 3,5] :
        return True
    if number % 2 == 0 or number % 3 == 0 or number % 5 == 0 or number < 2: 
        return False
    if number < 49:
        return True
    if (number %  7) == 0 or (number % 11) == 0 or (number % 13) == 0 or \
     (number % 17) == 0 or (number % 19) == 0 or (number % 23) == 0 \
     or (number % 29) == 0 or (number % 31) == 0 or \
       (number % 37) == 0 or (number % 41) == 0 or (number % 43) == 0 or (number % 47) == 0:
        return False
    if number < 2809:
        return True

    max_range = int(math.sqrt(number)) + 1
    for value in range(53, max_range, 2):
        if number % value == 0:
            return False
    return True

Warning sẽ đổi từ mã lỗi nọ sang mã lỗi khác:

pep8_sample.py:2:1: E302 expected 2 blank lines, found 0
pep8_sample.py:6:1: E302 expected 2 blank lines, found 1
pep8_sample.py:7:5: E265 block comment should start with '# '
pep8_sample.py:13:1: E303 too many blank lines (4)
pep8_sample.py:14:23: E231 missing whitespace after ','
pep8_sample.py:14:26: E203 whitespace before ':'
pep8_sample.py:16:76: W291 trailing whitespace
pep8_sample.py:20:17: E222 multiple spaces after operator
pep8_sample.py:21:6: E127 continuation line over-indented for visual indent
pep8_sample.py:22:6: E127 continuation line over-indented for visual indent
pep8_sample.py:23:8: E127 continuation line over-indented for visual indent
pep8_sample.py:23:80: E501 line too long (92 > 79 characters)

Với 3 warning ở dòng 21, 22, 23, chúng ta cần phải điều chỉnh lại số lượng space giống như rule số 3 ở phần Indentation phía trên (thụt vào đúng 4 khoảng trắng với dòng trên)

Nhưng khi thực hiện check warning, chúng ta sẽ nhận được 01 warning mới

pep8_sample.py:23:9: E129 visually indented line with same indent as next logical line

Có nghĩa là dòng kiểm tra logic cuối cùng phải được đẩy vào một level (4 space)

Tiếp tục check flake8 cho file này

pep8_sample.py:2:1: E302 expected 2 blank lines, found 0
pep8_sample.py:6:1: E302 expected 2 blank lines, found 1
pep8_sample.py:7:5: E265 block comment should start with '# '
pep8_sample.py:13:1: E303 too many blank lines (4)
pep8_sample.py:14:23: E231 missing whitespace after ','
pep8_sample.py:14:26: E203 whitespace before ':'
pep8_sample.py:16:76: W291 trailing whitespace
pep8_sample.py:20:17: E222 multiple spaces after operator

pep8_sample.py:20:17: E222 multiple spaces after operator
Dòng số 20, có nhiều hơn một dấu space, căng mắt một chút ra (hoặc đếm từ trái qua phải, vị trí số 17), chúng ta sẽ thấy đúng là có 2 khoảng trắng thật

Xóa bớt 01 khoảng trắng đi và tiếp tục check flaske8

Đến đây, chúng ta sẽ dễ dàng thực hiện sửa các lỗi tại dòng 13, 14, 16
pep8_sample.py:16:76: W291 trailing whitespace --> Thừa ký tự space ở cuối dòng
pep8_sample.py:14:26: E203 whitespace before ':' --> Thừa ký tự space ở trước dấu :
pep8_sample.py:14:23: E231 missing whitespace after ',' --> Thiếu ký tự space đằng sau dấu ,
pep8_sample.py:13:1: E303 too many blank lines (4) --> Nhiều dòng trống quá, giữa các function không nằm trong class, cách nhau 2 dòng trắng
pep8_sample.py:7:5: E265 block comment should start with '# ' --> Đằng sau ký tự # để thực hiện ghi chú, chứa ít nhất 01 khoảng trắng.
pep8_sample.py:6:1: E302 expected 2 blank lines, found 1 --> Cần 02 blank-line, hiện tại mới chỉ có 01
pep8_sample.py:2:1: E302 expected 2 blank lines, found 0 --> Cần 02 blank-line, hiện tại không có blank-line nào

Sau khi sửa lại, chúng ta sẽ có một đoạn script "đúng chuẩn" như sau:

import math


def add_number_v1():
    # add number version 1
    pass


def add_number_v2():
    # add number version 2
    pass


def is_prime_optimal(number):
    if number in [2, 3, 5]:
        return True
    if number % 2 == 0 or number % 3 == 0 or number % 5 == 0 or number < 2:
        return False
    if number < 49:
        return True
    if (number % 7) == 0 or (number % 11) == 0 or (number % 13) == 0 or \
        (number % 17) == 0 or (number % 19) == 0 or (number % 23) == 0 or \
        (number % 29) == 0 or (number % 31) == 0 or (number % 37) == 0 or \
            (number % 41) == 0 or (number % 43) == 0 or (number % 47) == 0:
        return False
    if number < 2809:
        return True

    max_range = int(math.sqrt(number)) + 1
    for value in range(53, max_range, 2):
        if number % value == 0:
            return False
    return True


class SubNumber:

    def check_number_v1(self):
        #  sub number version 1
        pass

    def check_number_v2(self):
        pass

Kiểm tra flake8 đã clear hết warning.

Ngoài công cụ flake8 ở trên, cộng đồng lập trình viên Python còn cung cấp nhiều các công cụ khác để giúp các lập trình viên Python có thể "hoàn thiện" code của mình như autopep8, pylint,....Hoặc các bạn có thể sử dụng các tính năng hiện đại của IDE (VSCode, Pycharm,..) để tự động hiển thị warning vê style theo các rule của pep8.

Kết luận

Nếu xác định theo nghề lập trình viên Python thì bạn nên đọc toàn bộ hướng dẫn trên trang chủ, nếu không có nhiều thời gian, bạn có thể đọc một vài quy tắc (tóm tắt) đã được liệt kê bên trên - những thứ mà bạn chắc chắn nên làm theo để có thể vươn ra "biển lớn" - kết nối với các lập trình viên Python ở khắp mọi nơi.

Cảm ơn các bạn đã quan tâm đến bài viết của tôi, hẹn gặp lại ở bài viết tiếp theo liên quan đến pep8: Naming