The PyMiers

Đọc code chương trình pycodestyle, pep8


PEP8 là tài liệu hướng dẫn style coding cho code Python, viết bởi tác giả của Python Guido van Rossum từ năm 2001 (23 năm trước!).

Code Python từ đó đều luôn tuân theo chuẩn PEP8, được gọi là "PEP8 compliant". Nhưng tài liệu PEP8 rất dài, có hàng chục mục khác nhau, vì vậy năm 2006, Johann C. Rocholl đã viết ra chương trình tên là pep8 để kiểm tra PEP8 tự động. Chương trình này, hiển nhiên, trở nên vô cùng phổ biến và được chạy trong hầu hết các hệ thống CI kiểm tra code Python.

Để cài đặt, gõ pip install pep8.

Năm 2016, Guido đề nghị đổi tên chương trình pep8, từ đó nó có tên mới là pycodestyle.

Bài viết này tham khảo kiến trúc, thiết kế của chương trình pycodestyle, sau hàng chục năm dùng mà chưa 1 lần đọc code.

Tải code về máy

Dùng git clone tải bản mới nhất hiện tại (2.11.1) về máy.

$ git clone https://github.com/PyCQA/pycodestyle/ --branch 2.11.1 --single-branch
Cloning into 'pycodestyle'...
remote: Enumerating objects: 5413, done.
remote: Counting objects: 100% (794/794), done.
remote: Compressing objects: 100% (375/375), done.
remote: Total 5413 (delta 453), reused 689 (delta 393), pack-reused 4619
Receiving objects: 100% (5413/5413), 1.72 MiB | 3.11 MiB/s, done.
Resolving deltas: 100% (3369/3369), done.
Note: switching to 'cefd59cf8e8a199a978da491e75a37fe4d37de82'.
...

Cấu trúc chương trình

File setup.py để hỗ trợ việc cài đặt qua pip

from setuptools import setup
setup()

chương trình pycodestyle nằm gọn trong 1 file pycodestyle.py, dài 2655 dòng.

$ cd pycodestyle
$ wc -l *.py
 2655 pycodestyle.py
    2 setup.py
 2657 total

Một chương trình hơn 1000 dòng nằm gọn trong 1 file không phải chuyện thường thấy, các project thường sẽ chia nhỏ ra các file chứa code làm nhiệm vụ khác nhau.

pycodestyle không phải chương trình bình thường. Nó có tính năng ổn định, ít thay đổi (chỉ khi Python ra bản mới giới thiệu các cú pháp mới như async/await, := hay switch, ...), cần dễ dàng distribute, thậm chí không cần dùng tới pip, chỉ cần copy file pycodestyle.py là chạy được, nó không có dependencies nào khác ngoài các standard library.

Kiến trúc phần mềm

Mô tả ngắn gọn, pycodestyle làm những việc sau:

  1. đọc config file, options từ dòng lệnh
  2. chạy các check với các file đầu vào
  3. in ra màn hình kết quả

Kiến trúc class, function

Để thực hiện bước 2, các chương trình sẽ thường cần chứa danh sách các check (có đầu vào khác nhau), rồi chạy lần lượt từng check cho từng file.

Các ngôn ngữ lập trình hướng đối tượng OOP sẽ thường tạo class và dùng inheritance (kế thừa):

class BaseCheck():
    def check(self, arg1, arg2):
        pass

class Check1(BaseCheck):
    def check(...)
        pass

class Check2(BaseCheck):
    def check(...)
        pass

pycodestyle không dùng class để làm việc này mà dùng các function. Bỏ qua các function nhận input _line (các check), ta có các function và class của chương trình:

$ grep -E '^def |^class ' pycodestyle.py | grep -v _line
def _get_parameters(function):
def register_check(check, codes=None):
def module_imports_on_top_of_file(
def _is_binary_operator(token_type, text):
def _break_around_binary_operators(tokens):
def readlines(filename):
def stdin_get_value():
def expand_indent(line):
def mute_string(text):
def parse_udiff(diff, patterns=None, parent='.'):
def normalize_paths(value, parent=os.curdir):
def filename_match(filename, patterns, default=True):
def update_counts(s, counts):
def _is_eol_token(token):
class Checker:
class BaseReport:
class FileReport(BaseReport):
class StandardReport(BaseReport):
class DiffReport(StandardReport):
class StyleGuide:
def get_parser(prog='pycodestyle', version=__version__):
def read_config(options, args, arglist, parser):
def process_options(arglist=None, parse_argv=False, config_file=None,
def _parse_multi_options(options, split_token=','):
def _main():

Chương trình chạy bằng cách gọi function _main():

$ tail -n2 pycodestyle.py
if __name__ == '__main__':
    _main()

Các thành phần chính của chương trình

function _main code ngắn gọn:

def _main():
    """Parse options and run checks on Python source."""
    import signal

    # Handle "Broken pipe" gracefully
    try:
        signal.signal(signal.SIGPIPE, lambda signum, frame: sys.exit(1))
    except AttributeError:
        pass    # not supported on Windows

    style_guide = StyleGuide(parse_argv=True)
    options = style_guide.options

    if options.doctest or options.testsuite:
        from testsuite.support import run_tests
        report = run_tests(style_guide)
    else:
        report = style_guide.check_files()

    if options.statistics:
        report.print_statistics()

    if options.benchmark:
        report.print_benchmark()

    if options.testsuite and not options.quiet:
        report.print_results()

    if report.total_errors:
        if options.count:
            sys.stderr.write(str(report.total_errors) + '\n')
        sys.exit(1)

function _main làm 2 việc chính:

  • tạo objects StyleGuide
  • chạy function style_guide.check_files()
  • thực hiện report dựa trên các option tương ứng rồi kết thúc.

class StyleGuide

Khi gọi StyleGuide(), method __init__ thực hiện đọc các options rồi setup checker, runner, options, report:

class StyleGuide:
    def __init__(self, *args, **kwargs):
        self.checker_class = kwargs.pop('checker_class', Checker)
        ...
        options, self.paths = process_options(
            arglist, parse_argv, config_file, parser, verbose)
        ...
        self.runner = self.input_file
        self.options = options
        if not options.reporter:
            options.reporter = BaseReport if options.quiet else StandardReport
        ...
        options.physical_checks = self.get_checks('physical_line')
        options.logical_checks = self.get_checks('logical_line')
        options.ast_checks = self.get_checks('tree')
        self.init_report()

class Checker thực hiện load file cần check, tokenize rồi chạy các check để check coding style:

class Checker:
    """Load a Python source file, tokenize it, check coding style."""

self.runner được gán giá trị self.input_file, là 1 method của StyleGuide:

def input_file(self, filename, lines=None, expected=None, line_offset=0):
    """Run all checks on a Python source file."""
    if self.options.verbose:
        print('checking %s' % filename)
    fchecker = self.checker_class(
        filename, lines=lines, options=self.options)
    return fchecker.check_all(expected=expected, line_offset=line_offset)

method input_file khởi tạo instance của class Checker rồi chạy method check_all để chạy các check với 1 Python source file. Method check_all sẽ được xem lại sau.

self.options chứa mọi option của chương trình.

reporter mặc định dùng class StandardReport.

method StyleGuide.check_files

method check_files thực hiện chạy check cho các file cần kiểm tra PEP8, trả về report

def check_files(self, paths=None):
    """Run all checks on the paths."""
    if paths is None:
        paths = self.paths
    report = self.options.report
    runner = self.runner
    report.start()
    try:
        for path in paths:
            if os.path.isdir(path):
                self.input_dir(path)
            elif not self.excluded(path):
                runner(path)
    except KeyboardInterrupt:
        print('... stopped')
    report.stop()
    return report

path là đường dẫn, có thể tới 1 file hay 1 thư mục, với file, chương trình sẽ check luôn nhờ gọi runner (method input_file), với thư mục, gọi method input_dir(path) để xử lý logic tìm các file trong thư mục rồi chạy runner với từng file.

class Checker

Logic của từng check nằm ở mỗi check funtion, nhưng việc load các check này vào, chạy khi nào, với option nào được gọi bởi Checker object.

register checks

Có 30 function check, mỗi function có số lượng argument khác nhau:

$ grep -E '^def ' pycodestyle.py | grep _line
def tabs_or_spaces(physical_line, indent_char):
def tabs_obsolete(physical_line):
def trailing_whitespace(physical_line):
def trailing_blank_lines(physical_line, lines, line_number, total_lines):
def maximum_line_length(physical_line, max_line_length, multiline,
def _is_one_liner(logical_line, indent_level, lines, line_number):
def blank_lines(logical_line, blank_lines, indent_level, line_number,
def extraneous_whitespace(logical_line):
def whitespace_around_keywords(logical_line):
def missing_whitespace_after_keyword(logical_line, tokens):
def indentation(logical_line, previous_logical, indent_char,
def continued_indentation(logical_line, tokens, indent_level, hang_closing,
def whitespace_before_parameters(logical_line, tokens):
def whitespace_around_operator(logical_line):
def missing_whitespace(logical_line, tokens):
def whitespace_around_comma(logical_line):
def whitespace_around_named_parameter_equals(logical_line, tokens):
def whitespace_before_comment(logical_line, tokens):
def imports_on_separate_lines(logical_line):
def compound_statements(logical_line):
def explicit_line_join(logical_line, tokens):
def break_before_binary_operator(logical_line, tokens):
def break_after_binary_operator(logical_line, tokens):
def comparison_to_singleton(logical_line, noqa):
def comparison_negative(logical_line):
def comparison_type(logical_line, noqa):
def bare_except(logical_line, noqa):
def ambiguous_identifier(logical_line, tokens):
def python_3000_invalid_escape_sequence(logical_line, tokens, noqa):
def maximum_doc_length(logical_line, max_doc_length, noqa, tokens):

parameter đầu tiên của các check đều được chuẩn hóa, với tên:

  • physical_line
  • logical_line

Đây cũng là 2 kind (loại) check trong tổng 3 loại mà pycodestyle hỗ trợ:

_checks = {'physical_line': {}, 'logical_line': {}, 'tree': {}}

class StyleGuide():
    def __init__(...):
        ...
        options.physical_checks = self.get_checks('physical_line')
        options.logical_checks = self.get_checks('logical_line')
        options.ast_checks = self.get_checks('tree')

Các check được lưu vào 1 global dict tên _checks sau khi define các check function, nhờ sử dụng decorator register_check:

def register_check(check, codes=None):
    """Register a new check object."""
    def _add_check(check, kind, codes, args):
        if check in _checks[kind]:
            _checks[kind][check][0].extend(codes or [])
        else:
            _checks[kind][check] = (codes or [''], args)
    if inspect.isfunction(check):
        args = _get_parameters(check)
        if args and args[0] in ('physical_line', 'logical_line'):
            if codes is None:
                codes = ERRORCODE_REGEX.findall(check.__doc__ or '')
            _add_check(check, args[0], codes, args)
    elif inspect.isclass(check):
        if _get_parameters(check.__init__)[:2] == ['self', 'tree']:
            _add_check(check, 'tree', codes, None)
    return check

@register_check
def tabs_or_spaces(physical_line, indent_char):
    pass

Đoạn code này là một magic, sử dụng tính dynamic linh hoạt của Python, dùng inspect để đọc từ các function object: tên, parameters, rồi _add_check vào từng nhóm kind dựa trên tên của parameter đầu tiên.

_add_check(check, args[0], codes, args)

Thử viết 1 function với 2 parameter, rồi dùng code inspect tìm xem parameter đầu tiên tên gì:

>>> def sum_two(x, y):
...     pass
...
>>> import inspect
>>> inspect.signature(sum_two)
<Signature (x, y)>
>>> S = inspect.signature(sum_two)
>>> S.
S.bind(              S.empty()            S.parameters         S.return_annotation
S.bind_partial(      S.from_callable(     S.replace(
>>> S.parameters
mappingproxy(OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y">)]))
>>> S.parameters.values()
odict_values([<Parameter "x">, <Parameter "y">])
>>> list(S.parameters.values())[0].name
'x'
_add_check(check, args[0], codes, args)

codes ở đây là mã error như W101 E201, lọc ra từ docstring của mỗi check function qua check.__doc__.

ERRORCODE_REGEX = re.compile(r'\b[A-Z]\d{3}\b')
if codes is None:
    codes = ERRORCODE_REGEX.findall(check.__doc__ or '')

Dùng grep để liệt kê tất cả các mã error:

$ grep -Eo '\b[A-Z][0-9]{3}\b' pycodestyle.py | sort | uniq | xargs printf '%s '
E101 E111 E112 E113 E114 E115 E116 E121 E122 E123 E124 E125 E126 E127 E128 E129 E131 E133 E201 E202 E203 E211 E221 E222 E223 E224 E225 E226 E227 E228 E231 E241 E242 E251 E252 E261 E262 E265 E266 E271 E272 E273 E274 E275 E301 E302 E303 E304 E305 E306 E401 E402 E501 E502 E701 E702 E703 E704 E711 E712 E713 E714 E721 E722 E731 E741 E742 E743 E901 E902 W191 W291 W292 W293 W391 W503 W504 W505 W605

Sửa code trong _main rồi chạy:

style_guide = StyleGuide(parse_argv=True)
# them 2 dong nay
for kind, checks in _checks.items():
    print(kind, len(checks))

Output:

$ python pycodestyle.py pycodestyle.py
physical_line 5
logical_line 25
tree 0

pycodestyle mặc định có 5 physical checks và 25 logical checks, không có ast (tree) check nào.

Việc thêm check mới đơn giản là viết 1 function, chọn 1 trong 3 type nói trên rồi decorate với @register_check.

physical check và logical check

physical check kiểm tra từng dòng code mà người dùng nhìn thấy trước khi Python load vào, giống như đọc từng dòng trong file.

def tabs_or_spaces(physical_line, indent_char):
def tabs_obsolete(physical_line):
def trailing_whitespace(physical_line):
def trailing_blank_lines(physical_line, lines, line_number, total_lines):
def maximum_line_length(physical_line, max_line_length, multiline,...)

5 physical check trên kiểm tra dòng chứa tab hay space, cuối dòng có whitespace không, blank line có whitespace không và độ dài mỗi dòng, đều có thể dựa trên nội dung text của từng dòng.

logical check kiểm tra các thành phần trong 1 dòng code mà Python hiểu. Ví dụ

Đây là 1 dòng physical

if 2 > 1 and length > 80:

Đây là 2 dòng physical nhưng là 1 dòng logical:

if (2 > 1
    and length > 80):

Việc kiểm tra độ dài của dòng rõ ràng thuộc về physical check.

Checker.check_all

Method "core" của chương trình là check_all, nơi thực sự chạy các check và thu thập kết quả vào Report. 45 dòng:

def check_all(self, expected=None, line_offset=0):
    """Run all checks on the input file."""
    self.report.init_file(self.filename, self.lines, expected, line_offset)
    self.total_lines = len(self.lines)
    if self._ast_checks:
        self.check_ast()
    self.line_number = 0
    self.indent_char = None
    self.indent_level = self.previous_indent_level = 0
    self.previous_logical = ''
    self.previous_unindented_logical_line = ''
    self.tokens = []
    self.blank_lines = self.blank_before = 0
    parens = 0
    for token in self.generate_tokens():
        self.tokens.append(token)
        token_type, text = token[0:2]
        if self.verbose >= 3:
            if token[2][0] == token[3][0]:
                pos = '[{}:{}]'.format(token[2][1] or '', token[3][1])
            else:
                pos = 'l.%s' % token[3][0]
            print('l.%s\t%s\t%s\t%r' %
                  (token[2][0], pos, tokenize.tok_name[token[0]], text))
        if token_type == tokenize.OP:
            if text in '([{':
                parens += 1
            elif text in '}])':
                parens -= 1
        elif not parens:
            if token_type in NEWLINE:
                if token_type == tokenize.NEWLINE:
                    self.check_logical()
                    self.blank_before = 0
                elif len(self.tokens) == 1:
                    # The physical line contains only this token.
                    self.blank_lines += 1
                    del self.tokens[0]
                else:
                    self.check_logical()
    # HVN note: only for last line of file
    if self.tokens:
        self.check_physical(self.lines[-1])
        self.check_logical()
    return self.report.get_file_results()

thực hiện:

  • khởi tạo report
  • chạy các ast_checks nếu có (mặc định là không)
  • duyệt qua từng token, chạy check_logical
  • kết thúc, gọi report lấy kết quả cuối cùng để print ra màn hình.

Không thấy check_physical ở đâu? bởi check_physical được gọi khi generate_tokens.

def generate_tokens(self):
    """Tokenize file, run physical line checks and yield tokens."""
    if self._io_error:
        self.report_error(1, 0, 'E902 %s' % self._io_error, readlines)
    tokengen = tokenize.generate_tokens(self.readline)
    try:
        prev_physical = ''
        for token in tokengen:
            if token[2][0] > self.total_lines:
                return
            self.noqa = token[4] and noqa(token[4])
            self.maybe_check_physical(token, prev_physical)
            yield token
            prev_physical = token[4]
    except (SyntaxError, tokenize.TokenError):
        self.report_invalid_syntax()

method này gọi tokenize.generate_tokens để thực hiện tokenize code, rồi maybe_check_physical để có thể gọi check_physical nếu cần thiết.

Tokenize là việc chia 1 dòng code thành các thành phần nhỏ nhất: token. Ví dụ dòng def sum_two(a, b): có 9 token: def, sum_two, (, a, ,, b, ), :, \n xuống dòng.

Thừ xem tokenize.tokenize xử lý file code sau:

# foo.py
1 def sum_two(a, b):
2     c = a + b
3
4     return c

Chạy tokenize:

>>> import tokenize
>>> f = tokenize.open("foo.py")
>>> for t in tokenize.generate_tokens(f.readline):
...     print(t)
...
TokenInfo(type=1 (NAME), string='def', start=(1, 0), end=(1, 3), line='def sum_two(a, b):\n')
TokenInfo(type=1 (NAME), string='sum_two', start=(1, 4), end=(1, 11), line='def sum_two(a, b):\n')
TokenInfo(type=54 (OP), string='(', start=(1, 11), end=(1, 12), line='def sum_two(a, b):\n')
TokenInfo(type=1 (NAME), string='a', start=(1, 12), end=(1, 13), line='def sum_two(a, b):\n')
TokenInfo(type=54 (OP), string=',', start=(1, 13), end=(1, 14), line='def sum_two(a, b):\n')
TokenInfo(type=1 (NAME), string='b', start=(1, 15), end=(1, 16), line='def sum_two(a, b):\n')
TokenInfo(type=54 (OP), string=')', start=(1, 16), end=(1, 17), line='def sum_two(a, b):\n')
TokenInfo(type=54 (OP), string=':', start=(1, 17), end=(1, 18), line='def sum_two(a, b):\n')
TokenInfo(type=4 (NEWLINE), string='\n', start=(1, 18), end=(1, 19), line='def sum_two(a, b):\n')
TokenInfo(type=5 (INDENT), string='    ', start=(2, 0), end=(2, 4), line='    c = a + b\n')
TokenInfo(type=1 (NAME), string='c', start=(2, 4), end=(2, 5), line='    c = a + b\n')
TokenInfo(type=54 (OP), string='=', start=(2, 6), end=(2, 7), line='    c = a + b\n')
TokenInfo(type=1 (NAME), string='a', start=(2, 8), end=(2, 9), line='    c = a + b\n')
TokenInfo(type=54 (OP), string='+', start=(2, 10), end=(2, 11), line='    c = a + b\n')
TokenInfo(type=1 (NAME), string='b', start=(2, 12), end=(2, 13), line='    c = a + b\n')
TokenInfo(type=4 (NEWLINE), string='\n', start=(2, 13), end=(2, 14), line='    c = a + b\n')
TokenInfo(type=62 (NL), string='\n', start=(3, 0), end=(3, 1), line='\n')
TokenInfo(type=1 (NAME), string='return', start=(4, 4), end=(4, 10), line='    return c\n')
TokenInfo(type=1 (NAME), string='c', start=(4, 11), end=(4, 12), line='    return c\n')
TokenInfo(type=4 (NEWLINE), string='\n', start=(4, 12), end=(4, 13), line='    return c\n')
TokenInfo(type=6 (DEDENT), string='', start=(5, 0), end=(5, 0), line='')
TokenInfo(type=0 (ENDMARKER), string='', start=(5, 0), end=(5, 0), line='')
>>> list(t)
[0, '', (5, 0), (5, 0), '']

tokenize trả về các TokenInfo object, chứa 5 thông tin về token:

  • type của token
  • string biểu diễn token
  • vị trí start (dòng, cột), chú ý dòng bắt đầu từ 1, cột bắt đầu từ 0.
  • vị trí end kết thúc token
  • line: nội dung dòng vật lý

các attribute này có thể truy cập bằng cú pháp attribute như t.start hay dùng index như t[2]. Chú ý rằng file foo.py có 4 dòng vật lý, thì token cuối cùng lại có start là dòng 5, nên mới có điều kiện dừng generator:

def generate_tokens(self):
    ...
    for token in tokengen:
        if token[2][0] > self.total_lines:
            return
        ...
        yield token

mỗi object Checker biểu diễn việc kiểm tra 1 file, có attribute .lines chứa các dòng của file đó. Các check sẽ lấy dòng số mấy dựa theo token start/stop rồi truy cập dòng bằng self.lines[n].

run physical check

Vậy mỗi physical check được gọi như thế nào?

def run_check(self, check, argument_names):
    """Run a check plugin."""
    arguments = []
    for name in argument_names:
        arguments.append(getattr(self, name))
    return check(*arguments)

def init_checker_state(self, name, argument_names):
    """Prepare custom state for the specific checker plugin."""
    if 'checker_state' in argument_names:
        self.checker_state = self._checker_states.setdefault(name, {})

def check_physical(self, line):
    """Run all physical checks on a raw input line."""
    self.physical_line = line
    for name, check, argument_names in self._physical_checks:
        self.init_checker_state(name, argument_names)
        result = self.run_check(check, argument_names)
        if result is not None:
            (offset, text) = result
            self.report_error(self.line_number, offset, text, check)
            if text[:4] == 'E101':
                self.indent_char = line[0]

Với mỗi name, check, argument_names chứa trong self._physical_checks, gọi run_check(check, argument_names), method này lấy tên của các argument, rồi lấy giá trị của chúng chưa trong attribute của Checker, sau đó gọi function check(*arguments). Hãy xem thử với check độ dài của dòng:

def maximum_line_length(physical_line, max_line_length, multiline, line_number, noqa):

ở phần register_check, argument đầu tiên physical_line đã bị tách ra để làm kind của check, còn lại arguments = [max_line_length, multiline, line_number, noqa], các argument này đều có attribute tương ứng trong Checker

class Checker:
    def __init__(self, filename=None, lines=None,
                 options=None, report=None, **kwargs):
        self.max_line_length = options.max_line_length
        self.multiline = False  # in a multiline string?
        self.noqa = False
        ...

line_number được set khi chạy check_all và được update mỗi khi đọc 1 dòng

    def check_all(self, expected=None, line_offset=0):
        ...
        self.line_number = 0
        ...
        for token in self.generate_tokens():
            ...

Như vậy các parameter của function checker đều có attribute tương ứng trong Checker class chứ không phải tùy ý.

xem 1 check đơn giản nhất: dòng chứa Tab

Dùng regex tìm trong dòng có chứa ký tự \t khi indent không, nếu có, trả về tuple 2 phần tử (index của ký tự tab và nội dung error "W191")

@register_check
def tabs_obsolete(physical_line):
    r"""On new projects, spaces-only are strongly recommended over tabs.

    Okay: if True:\n    return
    W191: if True:\n\treturn
    """
    indent = INDENT_REGEX.match(physical_line).group(1)
    if '\t' in indent:
        return indent.index('\t'), "W191 indentation contains tabs"

Xem traceback

Thêm dòng raise vào function tabs_obsolete rồi chạy lại:

$ python pycodestyle.py pycodestyle.py # edited output
Traceback (most recent call last):
    _main()
  in _main
    report = style_guide.check_files()
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  in check_files
    runner(path)
  in input_file
    return fchecker.check_all(expected=expected, line_offset=line_offset)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  in check_all
    for token in self.generate_tokens():
  in generate_tokens
    self.maybe_check_physical(token, prev_physical)
  in maybe_check_physical
    self.check_physical(token.line)
  in check_physical
    result = self.run_check(check, argument_names)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  in run_check
    return check(*arguments)
           ^^^^^^^^^^^^^^^^^
  in tabs_obsolete
    raise
RuntimeError: No active exception to reraise

coding style

global var is okay

pycodestyle sử dụng nhiều biến global như _checks, việc sử dụng biến global thường bị xem như "code smell", nhưng biến global này chỉ để đọc (sau khi đã set giá trị) mà không update, nên các nhược điểm do sử dụng global không ảnh hưởng ở đây. Ngoài ra pycodestyle cũng chỉ chạy mội vài giây (phút) rồi kết thúc chứ không chạy cả ngày như 1 web app nên việc sử dụng global var hoàn toàn OK.

main làm ít việc

function main (_main) của pycodestyle đơn giản, chủ yếu gọi các function khác, đúng như những gì ta thấy trong refactor

dùng class khi cần class, còn đa số là function

class Checker giúp tạo ra các object checker để quản lý state và thực hiện chạy check cho mỗi file.

Các class Report thực hiện kế thừa BaseReport vì có nhiều điểm giống nhau.

class StyleGuide chứa mọi thứ cần có về chương trình pycodestyle.

các check là các function.

không có type annotation

code base đã cũ và không hỗ trợ type annotation có từ Python 3.6

dùng optparse thay vì argparse

Deprecated since version 3.2: The optparse module is deprecated and will not be developed further; development will continue with the argparse module.

Code pycodestyle đã cũ, từ thời cần hỗ trợ Python2.6, khi chưa có argparse, nên vẫn dùng optparse. https://github.com/PyCQA/pycodestyle/pull/455#issuecomment-151712140

Kết luận

pycodestyle hay pep8 là chương trình thuộc top phổ biến của python

requests: 450 triệu download/tháng, pycodestyle: 40 triệu download/tháng.

có code nằm trong 1 file duy nhất với 2600 dòng, với tính năng ổn định, code đơn giản, sử dụng tính dynamic linh hoạt của Python để tạo các check. Code dễ dàng thêm các check mới bằng việc tạo function rồi chạy register. Dùng global var, hàm main ít code, dùng class khi cần.

Hết!

HVN at http://pymi.vn and https://www.familug.org.

Comments