timeit

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

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

Решение на Статичен анализ на python код от Ивета Чампоева

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

Към профила на Ивета Чампоева

Резултати

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

Код

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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import ast


class Critic:
    def __init__(self, code, rules):
        self.code = code
        self.lines = [None] + code.splitlines()
        self.number_of_lines = sum([1 for line in self.lines[1:]])
        self.tree = ast.parse(code)
        self.rules = rules
        self.line_length = self.normalize('line_length', 79)
        self.forbid_semicolons = self.normalize('forbid_semicolons', True)
        self.max_nesting = self.normalize('max_nesting', None)
        self.indentation_size = self.normalize('indentation_size', 4)
        self.methods_per_class = self.normalize('methods_per_class', None)
        self.max_arity = self.normalize('max_arity', None)
        self.forbid_trailing_whitespace = \
            self.normalize('forbid_trailing_whitespace', True)
        self.max_lines_per_function = \
            self.normalize('max_lines_per_function', None)
        self.mistakes = {}
        self.indent_dict = {row: section[1] for section in
                            sorted(self.rows_spec(self.tree), key=self.getKey)
                            for row in range(section[0], section[2]+1)}
        self.indent_dict_correction()

    def normalize(self, variable, value):
        if variable not in self.rules.keys():
            return value
        return self.rules[variable]

    def indent_dict_correction(self):
        count = 1
        for line in self.lines[1:]:
            if not line.strip():
                self.indent_dict[count] = 0
            count += 1

    def dict_input(self, key, value):
        if key not in self.mistakes:
            self.mistakes[key] = [value]
        else:
            self.mistakes[key].append(value)

    def actual_indent(self, row):
        count = 0
        while row.strip() and row[count] == ' ':
            count += 1
        return count

    def getKey(self, item):
        return item[1]

    def rows_spec(self, tree, nest=0, rows_info=[]):
        for node in ast.iter_child_nodes(tree):
            if isinstance(node, (ast.If, ast.For, ast.FunctionDef,
                                 ast.ClassDef, ast.While, ast.Try)):
                rows_info.append([node.lineno+1, nest+1, node.body[-1].lineno])
                rows_info = rows_info + self.rows_spec(node, nest+1, rows_info)
        return rows_info

    def length_whitespace_semicolons(self):
        count = 0
        for line in self.lines[1:]:
            count += 1
            actual_line_length = len(line)
            if actual_line_length > self.line_length:
                message = 'line too long ({} > {})'.format(actual_line_length,
                                                           self.line_length)
                self.dict_input(count, message)
            if ';' in line and self.forbid_semicolons:
                message = 'multiple expressions on the same line'
                self.dict_input(count, message)
            if line and line[-1] == ' ' and self.forbid_trailing_whitespace:
                message = 'trailing whitespace'
                self.dict_input(count, message)

    def indentation(self):
        wrong_indent = []
        for key in self.indent_dict.keys():
            if key <= self.number_of_lines:
                actual = self.actual_indent(self.lines[key])
                right = self.indent_dict[key]*self.indentation_size
                if actual != right:
                    wrong_indent.append([key, actual, right])
        for row, wrong, right in wrong_indent:
            message = 'indentation is {} instead of {}'.format(wrong, right)
            self.dict_input(row, message)

    def nesting(self):
        if self.max_nesting:
            wrong_nesting = min([key for key in self.indent_dict.keys()
                                 if key <= self.number_of_lines and
                                 self.indent_dict[key] > self.max_nesting])
            message = 'nesting too deep ({} > {})'.format(self.max_nesting+1,
                                                          self.max_nesting)
            self.dict_input(wrong_nesting, message)

    def class_methods(self):
        counter = 0
        class_rows = []
        for node in ast.iter_child_nodes(self.tree):
            counter += 1
            if isinstance(node, ast.ClassDef):
                method_count = 0
                for method in node.body:
                    if isinstance(method, ast.FunctionDef):
                        method_count += 1
                if method_count > self.methods_per_class:
                    class_rows.append([node.lineno, method_count])
        if self.methods_per_class:
            for incorrect_class in class_rows:
                message = 'too many methods in class({} > {})'\
                          .format(incorrect_class[1], self.methods_per_class)
                self.dict_input(incorrect_class[0], message)

    def function_arguments(self):
        for node in ast.iter_child_nodes(self.tree):
            if isinstance(node, ast.FunctionDef):
                arguments = 0
                for arg in node.args.args:
                    arguments += 1
                if node.args.vararg:
                    arguments += 1
                for arg in node.args.kwonlyargs:
                    arguments += 1
                if node.args.kwarg:
                    arguments += 1
                if arguments > self.max_arity:
                    message = 'too many arguments({} > {})'\
                              .format(arguments, self.max_arity)
                    self.dict_input(node.lineno, message)

    def lines_per_function(self, tree_):
        indent = self.indent_dict
        for node in ast.iter_child_nodes(tree_):
            if isinstance(node, ast.FunctionDef):
                line_count = 1
                in_order = node.lineno+line_count
                while in_order in indent.keys() and\
                        indent[in_order] > indent[node.lineno+1]-1:
                    line_count += 1
                    in_order = node.lineno+line_count
                if line_count > self.max_lines_per_function:
                    message = 'method with too many lines ({} > {})'\
                              .format(line_count, self.max_lines_per_function)
                    self.dict_input(node.lineno, message)
            else:
                self.lines_per_function(node)
        return

    def analyze(self):
        self.length_whitespace_semicolons()
        self.indentation()
        self.nesting()
        if self.methods_per_class:
            self.class_methods()
        if self.max_arity:
            self.function_arguments()
        if self.max_lines_per_function:
            self.lines_per_function(self.tree)
        return self.mistakes


def critic(code, **rules):
    new_code = Critic(code, rules)
    return new_code.analyze()

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

E....FF....
======================================================================
ERROR: test_dict_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
ValueError: min() arg is an empty sequence

======================================================================
FAIL: test_max_lines_per_function (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: Items in the first set but not the second:
'method with too many lines (7 > 3)'
Items in the second set but not the first:
'method with too many lines (6 > 3)'

======================================================================
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

----------------------------------------------------------------------
Ran 11 tests in 0.092s

FAILED (failures=2, errors=1)

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

Ивета обнови решението на 18.05.2016 15:13 (преди над 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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import ast


class Critic:
    def __init__(self, code, rules):
        self.code = code
        self.lines = [None] + code.splitlines()
        self.number_of_lines = sum([1 for line in self.lines[1:]])
        self.tree = ast.parse(code)
        self.rules = rules
        self.line_length = self.normalize('line_length', 79)
        self.forbid_semicolons = self.normalize('forbid_semicolons', True)
        self.max_nesting = self.normalize('max_nesting', None)
        self.indentation_size = self.normalize('indentation_size', 4)
        self.methods_per_class = self.normalize('methods_per_class', None)
        self.max_arity = self.normalize('max_arity', None)
        self.forbid_trailing_whitespace = \
            self.normalize('forbid_trailing_whitespace', True)
        self.max_lines_per_function = \
            self.normalize('max_lines_per_function', None)
        self.mistakes = {}
        self.indent_dict = {row: section[1] for section in
                            sorted(self.rows_spec(self.tree), key=self.getKey)
                            for row in range(section[0], section[2]+1)}
        self.indent_dict_correction()

    def normalize(self, variable, value):
        if variable not in self.rules.keys():
            return value
        return self.rules[variable]

    def indent_dict_correction(self):
        count = 1
        for line in self.lines[1:]:
            if not line.strip():
                self.indent_dict[count] = 0
            count += 1

    def dict_input(self, key, value):
        if key not in self.mistakes:
            self.mistakes[key] = [value]
        else:
            self.mistakes[key].append(value)

    def actual_indent(self, row):
        count = 0
        while row.strip() and row[count] == ' ':
            count += 1
        return count

    def getKey(self, item):
        return item[1]

    def rows_spec(self, tree, nest=0, rows_info=[]):
        for node in ast.iter_child_nodes(tree):
            if isinstance(node, (ast.If, ast.For, ast.FunctionDef,
                                 ast.ClassDef, ast.While, ast.Try)):
                rows_info.append([node.lineno+1, nest+1, node.body[-1].lineno])
                rows_info = rows_info + self.rows_spec(node, nest+1, rows_info)
        return rows_info

    def length_whitespace_semicolons(self):
        count = 0
        for line in self.lines[1:]:
            count += 1
            actual_line_length = len(line)
            if actual_line_length > self.line_length:
                message = 'line too long ({} > {})'.format(actual_line_length,
                                                           self.line_length)
                self.dict_input(count, message)
            if ';' in line and self.forbid_semicolons:
                message = 'multiple expressions on the same line'
                self.dict_input(count, message)
            if line and line[-1] == ' ' and self.forbid_trailing_whitespace:
                message = 'trailing whitespace'
                self.dict_input(count, message)

    def indentation(self):
        wrong_indent = []
        for key in self.indent_dict.keys():
            if key <= self.number_of_lines:
                actual = self.actual_indent(self.lines[key])
                right = self.indent_dict[key]*self.indentation_size
                if actual != right:
                    wrong_indent.append([key, actual, right])
        for row, wrong, right in wrong_indent:
            message = 'indentation is {} instead of {}'.format(wrong, right)
            self.dict_input(row, message)

    def nesting(self):
        if self.max_nesting:
            wrong_nesting = min([key for key in self.indent_dict.keys()
                                 if key <= self.number_of_lines and
                                 self.indent_dict[key] > self.max_nesting])
            message = 'nesting too deep ({} > {})'.format(self.max_nesting+1,
                                                          self.max_nesting)
            self.dict_input(wrong_nesting, message)

    def class_methods(self):
        counter = 0
        class_rows = []
        for node in ast.iter_child_nodes(self.tree):
            counter += 1
            if isinstance(node, ast.ClassDef):
                method_count = 0
                for method in node.body:
                    if isinstance(method, ast.FunctionDef):
                        method_count += 1
                if method_count > self.methods_per_class:
                    class_rows.append([node.lineno, method_count])
        if self.methods_per_class:
            for incorrect_class in class_rows:
                message = 'too many methods in class({} > {})'\
                          .format(incorrect_class[1], self.methods_per_class)
                self.dict_input(incorrect_class[0], message)

    def function_arguments(self):
        for node in ast.iter_child_nodes(self.tree):
            if isinstance(node, ast.FunctionDef):
                arguments = 0
                for arg in node.args.args:
                    arguments += 1
                if node.args.vararg:
                    arguments += 1
                for arg in node.args.kwonlyargs:
                    arguments += 1
                if node.args.kwarg:
                    arguments += 1
                if arguments > self.max_arity:
                    message = 'too many arguments({} > {})'\
                              .format(arguments, self.max_arity)
                    self.dict_input(node.lineno, message)

    def lines_per_function(self, tree_):
        indent = self.indent_dict
        for node in ast.iter_child_nodes(tree_):
            if isinstance(node, ast.FunctionDef):
                line_count = 1
                in_order = node.lineno+line_count
                while in_order <= self.number_of_lines and\
                        indent[in_order] > indent[node.lineno+1]-1:
                    line_count += 1
                    in_order = node.lineno+line_count
                if line_count > self.max_lines_per_function:
                    message = 'method with too many lines ({} > {})'\
                              .format(line_count, self.max_lines_per_function)
                    self.dict_input(node.lineno, message)
            else:
                self.lines_per_function(node)
        return

    def analyze(self):
        self.length_whitespace_semicolons()
        self.indentation()
        self.nesting()
        if self.methods_per_class:
            self.class_methods()
        if self.max_arity:
            self.function_arguments()
        if self.max_lines_per_function:
            self.lines_per_function(self.tree)
        return self.mistakes


def critic(code, **rules):
    new_code = Critic(code, rules)
    return new_code.analyze()

Ивета обнови решението на 18.05.2016 15:22 (преди над 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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import ast


class Critic:
    def __init__(self, code, rules):
        self.code = code
        self.lines = [None] + code.splitlines()
        self.number_of_lines = sum([1 for line in self.lines[1:]])
        self.tree = ast.parse(code)
        self.rules = rules
        self.line_length = self.normalize('line_length', 79)
        self.forbid_semicolons = self.normalize('forbid_semicolons', True)
        self.max_nesting = self.normalize('max_nesting', None)
        self.indentation_size = self.normalize('indentation_size', 4)
        self.methods_per_class = self.normalize('methods_per_class', None)
        self.max_arity = self.normalize('max_arity', None)
        self.forbid_trailing_whitespace = \
            self.normalize('forbid_trailing_whitespace', True)
        self.max_lines_per_function = \
            self.normalize('max_lines_per_function', None)
        self.mistakes = {}
        self.indent_dict = {row: section[1] for section in
                            sorted(self.rows_spec(self.tree), key=self.getKey)
                            for row in range(section[0], section[2]+1)}
        self.indent_dict_correction()

    def normalize(self, variable, value):
        if variable not in self.rules.keys():
            return value
        return self.rules[variable]

    def indent_dict_correction(self):
        count = 1
        for line in self.lines[1:]:
            if not line.strip():
                self.indent_dict[count] = 0
            count += 1

    def dict_input(self, key, value):
        if key not in self.mistakes:
            self.mistakes[key] = [value]
        else:
            self.mistakes[key].append(value)

    def actual_indent(self, row):
        count = 0
        while row.strip() and row[count] == ' ':
            count += 1
        return count

    def getKey(self, item):
        return item[1]

    def rows_spec(self, tree, nest=0, rows_info=[]):
        for node in ast.iter_child_nodes(tree):
            if isinstance(node, (ast.If, ast.For, ast.FunctionDef,
                                 ast.ClassDef, ast.While, ast.Try)):
                rows_info.append([node.lineno+1, nest+1, node.body[-1].lineno])
                rows_info = rows_info + self.rows_spec(node, nest+1, rows_info)
        return rows_info

    def length_whitespace_semicolons(self):
        count = 0
        for line in self.lines[1:]:
            count += 1
            actual_line_length = len(line)
            if actual_line_length > self.line_length:
                message = 'line too long ({} > {})'.format(actual_line_length,
                                                           self.line_length)
                self.dict_input(count, message)
            if ';' in line and self.forbid_semicolons:
                message = 'multiple expressions on the same line'
                self.dict_input(count, message)
            if line and line[-1] == ' ' and self.forbid_trailing_whitespace:
                message = 'trailing whitespace'
                self.dict_input(count, message)

    def indentation(self):
        wrong_indent = []
        for key in self.indent_dict.keys():
            if key <= self.number_of_lines:
                actual = self.actual_indent(self.lines[key])
                right = self.indent_dict[key]*self.indentation_size
                if actual != right:
                    wrong_indent.append([key, actual, right])
        for row, wrong, right in wrong_indent:
            message = 'indentation is {} instead of {}'.format(wrong, right)
            self.dict_input(row, message)

    def nesting(self):
        if self.max_nesting:
            wrong_nesting = min([key for key in self.indent_dict.keys()
                                 if key <= self.number_of_lines and
                                 self.indent_dict[key] > self.max_nesting])
            message = 'nesting too deep ({} > {})'.format(self.max_nesting+1,
                                                          self.max_nesting)
            self.dict_input(wrong_nesting, message)

    def class_methods(self):
        counter = 0
        class_rows = []
        for node in ast.iter_child_nodes(self.tree):
            counter += 1
            if isinstance(node, ast.ClassDef):
                method_count = 0
                for method in node.body:
                    if isinstance(method, ast.FunctionDef):
                        method_count += 1
                if method_count > self.methods_per_class:
                    class_rows.append([node.lineno, method_count])
        if self.methods_per_class:
            for incorrect_class in class_rows:
                message = 'too many methods in class({} > {})'\
                          .format(incorrect_class[1], self.methods_per_class)
                self.dict_input(incorrect_class[0], message)

    def function_arguments(self):
        for node in ast.iter_child_nodes(self.tree):
            if isinstance(node, ast.FunctionDef):
                arguments = 0
                for arg in node.args.args:
                    arguments += 1
                if node.args.vararg:
                    arguments += 1
                for arg in node.args.kwonlyargs:
                    arguments += 1
                if node.args.kwarg:
                    arguments += 1
                if arguments > self.max_arity:
                    message = 'too many arguments({} > {})'\
                              .format(arguments, self.max_arity)
                    self.dict_input(node.lineno, message)

    def lines_per_function(self, tree_):
        indent = self.indent_dict
        for node in ast.iter_child_nodes(tree_):
            if isinstance(node, ast.FunctionDef):
                line_count = 1
                in_order = node.lineno+line_count
                while in_order in indent.keys() and\
                        indent[in_order] > indent[node.lineno+1]-1:
                    line_count += 1
                    in_order = node.lineno+line_count
                if line_count > self.max_lines_per_function:
                    message = 'method with too many lines ({} > {})'\
                              .format(line_count, self.max_lines_per_function)
                    self.dict_input(node.lineno, message)
            else:
                self.lines_per_function(node)
        return

    def analyze(self):
        self.length_whitespace_semicolons()
        self.indentation()
        self.nesting()
        if self.methods_per_class:
            self.class_methods()
        if self.max_arity:
            self.function_arguments()
        if self.max_lines_per_function:
            self.lines_per_function(self.tree)
        return self.mistakes


def critic(code, **rules):
    new_code = Critic(code, rules)
    return new_code.analyze()