timeit

Програмиране с Python

Курс във Факултета по Математика и Информатика към СУ

Решение на Статичен анализ на python код от Стилиян Стоянов

Обратно към всички решения

Към профила на Стилиян Стоянов

Резултати

  • 6 точки от тестове
  • 0 бонус точки
  • 6 точки общо
  • 7 успешни тест(а)
  • 4 неуспешни тест(а)

Код

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from collections import defaultdict
import ast

ERRORS = ['line too long ({} > {})',
          'multiple expressions on the same line',
          'nesting too deep (<<actual>> > <<allowed>>)',
          'indentation is <<actual>> instead of <<size>>',
          'too many methods in class(<<actual>> > <<allowed>>)',
          'too many arguments({} > {})',
          'trailing whitespace',
          'method with too many lines ({} > {})']


def critic(code, **rules):
    checker = SyntaxChecker(code, **rules)
    return checker.check()


class CodeVisitor(ast.NodeVisitor):

    def visit_FunctionDef(self, node):
        print(node.name)
#        print(node['args'])
        self.generic_visit(node)


class SyntaxChecker():

    def __init__(self, code, **rules):
        self.code = code
        self.result = defaultdict(list)
        self.init_rules(**rules)
        self.tree = ast.parse(code)

    def check(self):
        self.check_long_lines()
        self.check_semicolons()
        self.check_trailing_whitespace()
        if self.max_nesting:
            self.check_nests()
        if self.max_arity:
            self.check_func_args()
        if self.function_sizes:
            self.check_func_sizes()
        return self.result

    def count_args(self, node):
        args = node.args + node.kwonlyargs
        count = len(args)
        if node.vararg:
            count += 1
        if node.kwarg:
            count += 1
        return count

    def check_func_sizes(self):
        nodes = ast.walk(self.tree)
        len(list(nodes))
        for index, node in enumerate(ast.walk(self.tree)):
            if isinstance(node, ast.FunctionDef):
                start_lineno = getattr(node, 'lineno')
                if index + 1 < len(list(nodes)):
                    end_lineno = getattr(list(nodes)[index+1], 'lineno')
                else:
                    end_lineno = len(self.lines)
                func_size = end_lineno - start_lineno
                if func_size > self.function_sizes:
                    error = ERRORS[-1].format(func_size, self.function_sizes)
                    self.result[start_lineno].append(error)

    def check_func_args(self):
        for node in enumerate(ast.walk(self.tree)):
            if isinstance(node, ast.FunctionDef):
                func_args = self.count_args(node.args)
                if func_args > self.max_arity:
                    error = ERRORS[5].format(func_args, self.max_arity)
                    self.result[getattr(node, 'lineno')].append(error)

    def check_nests(self):
        for index, node in enumerate(ast.walk(self.tree)):
            if isinstance(node, ast.FunctionDef):
                depth = self.count_nests(node.body, 1)
                if depth > self.max_nesting:
                    line_no = getattr(node, 'lineno')
                    error = ERRORS[2].format(depth, self.max_nesting)
                    self.result[line_no].append(error)

    def count_nests(self, node, depth):
        if hasattr(node, "body"):
            new_depth = depth
            for sub_element in node.body:
                new_depth = count_nests(sub_element, depth + 1)
                if new_depth > depth:
                    depth = new_depth
        return depth

    def check_long_lines(self):
        self.lines = self.code.splitlines()
        for line_no, line in enumerate(self.lines, start=1):
            if len(line) > self.line_length:
                error = ERRORS[0].format(len(line), self.line_length)
                self.result[line_no].append(error)

    def check_trailing_whitespace(self):
        for line_no, line in enumerate(self.lines, start=1):
            if line.rstrip() != line:
                self.result[line_no].append(ERRORS[6])

    def init_rules(self, **rules):
        self.line_length = rules.get('line_length', 79)
        self.forbid_semicolons = rules.get('forbid_semicolons', True)
        self.max_nesting = rules.get('max_nesting', None)
        self.indentation_size = rules.get('indentation_size', 4)
        self.methods_per_class = rules.get('methods_per_class', None)
        self.max_arity = rules.get('max_arity', None)
        self.no_trailing_whitespace = rules.get('forbid_trailing_whitespace',
                                                True)
        self.function_sizes = rules.get('max_lines_per_function', None)

    def check_semicolons(self):
        semicolon_rows = {}
        for line_no, line in enumerate(self.lines, start=1):
            if not line.strip().startswith('#') and ';' in line:
                self.result[line_no].append(ERRORS[1])

Лог от изпълнението

..F...F.FF.
======================================================================
FAIL: test_indentation (test.TestCritic)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
AssertionError: 0 != 1

======================================================================
FAIL: test_multiple_issues_all_over_the_place (test.TestCritic)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
AssertionError: 4 != 5

======================================================================
FAIL: test_too_deep_nesting (test.TestCritic)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
AssertionError: 0 != 1

======================================================================
FAIL: test_too_many_arguments (test.TestCritic)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
AssertionError: 0 != 1

----------------------------------------------------------------------
Ran 11 tests in 0.144s

FAILED (failures=4)

История (1 версия и 0 коментара)

Стилиян обнови решението на 18.05.2016 16:56 (преди над 1 година)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from collections import defaultdict
import ast

ERRORS = ['line too long ({} > {})',
          'multiple expressions on the same line',
          'nesting too deep (<<actual>> > <<allowed>>)',
          'indentation is <<actual>> instead of <<size>>',
          'too many methods in class(<<actual>> > <<allowed>>)',
          'too many arguments({} > {})',
          'trailing whitespace',
          'method with too many lines ({} > {})']


def critic(code, **rules):
    checker = SyntaxChecker(code, **rules)
    return checker.check()


class CodeVisitor(ast.NodeVisitor):

    def visit_FunctionDef(self, node):
        print(node.name)
#        print(node['args'])
        self.generic_visit(node)


class SyntaxChecker():

    def __init__(self, code, **rules):
        self.code = code
        self.result = defaultdict(list)
        self.init_rules(**rules)
        self.tree = ast.parse(code)

    def check(self):
        self.check_long_lines()
        self.check_semicolons()
        self.check_trailing_whitespace()
        if self.max_nesting:
            self.check_nests()
        if self.max_arity:
            self.check_func_args()
        if self.function_sizes:
            self.check_func_sizes()
        return self.result

    def count_args(self, node):
        args = node.args + node.kwonlyargs
        count = len(args)
        if node.vararg:
            count += 1
        if node.kwarg:
            count += 1
        return count

    def check_func_sizes(self):
        nodes = ast.walk(self.tree)
        len(list(nodes))
        for index, node in enumerate(ast.walk(self.tree)):
            if isinstance(node, ast.FunctionDef):
                start_lineno = getattr(node, 'lineno')
                if index + 1 < len(list(nodes)):
                    end_lineno = getattr(list(nodes)[index+1], 'lineno')
                else:
                    end_lineno = len(self.lines)
                func_size = end_lineno - start_lineno
                if func_size > self.function_sizes:
                    error = ERRORS[-1].format(func_size, self.function_sizes)
                    self.result[start_lineno].append(error)

    def check_func_args(self):
        for node in enumerate(ast.walk(self.tree)):
            if isinstance(node, ast.FunctionDef):
                func_args = self.count_args(node.args)
                if func_args > self.max_arity:
                    error = ERRORS[5].format(func_args, self.max_arity)
                    self.result[getattr(node, 'lineno')].append(error)

    def check_nests(self):
        for index, node in enumerate(ast.walk(self.tree)):
            if isinstance(node, ast.FunctionDef):
                depth = self.count_nests(node.body, 1)
                if depth > self.max_nesting:
                    line_no = getattr(node, 'lineno')
                    error = ERRORS[2].format(depth, self.max_nesting)
                    self.result[line_no].append(error)

    def count_nests(self, node, depth):
        if hasattr(node, "body"):
            new_depth = depth
            for sub_element in node.body:
                new_depth = count_nests(sub_element, depth + 1)
                if new_depth > depth:
                    depth = new_depth
        return depth

    def check_long_lines(self):
        self.lines = self.code.splitlines()
        for line_no, line in enumerate(self.lines, start=1):
            if len(line) > self.line_length:
                error = ERRORS[0].format(len(line), self.line_length)
                self.result[line_no].append(error)

    def check_trailing_whitespace(self):
        for line_no, line in enumerate(self.lines, start=1):
            if line.rstrip() != line:
                self.result[line_no].append(ERRORS[6])

    def init_rules(self, **rules):
        self.line_length = rules.get('line_length', 79)
        self.forbid_semicolons = rules.get('forbid_semicolons', True)
        self.max_nesting = rules.get('max_nesting', None)
        self.indentation_size = rules.get('indentation_size', 4)
        self.methods_per_class = rules.get('methods_per_class', None)
        self.max_arity = rules.get('max_arity', None)
        self.no_trailing_whitespace = rules.get('forbid_trailing_whitespace',
                                                True)
        self.function_sizes = rules.get('max_lines_per_function', None)

    def check_semicolons(self):
        semicolon_rows = {}
        for line_no, line in enumerate(self.lines, start=1):
            if not line.strip().startswith('#') and ';' in line:
                self.result[line_no].append(ERRORS[1])