timeit

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

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

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

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

Към профила на Алекс Николов

Резултати

  • 9 точки от тестове
  • 0 бонус точки
  • 9 точки общо
  • 10 успешни тест(а)
  • 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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
from collections import defaultdict
import re
import codeop


DEFAULT_RULES = {'line_length': 79, 'forbid_semicolons': True,
                 'max_nesting': None, 'indentation_size': 4,
                 'methods_per_class': None, 'max_arity': None,
                 'forbid_trailing_whitespace': True,
                 'max_lines_per_function': None}


def critic(code, **rules):
    for rule in DEFAULT_RULES:
        if rule not in rules:
            rules[rule] = DEFAULT_RULES[rule]

    code_in_lines = code.split('\n')
    lines_with_errors = defaultdict(list)
    indentation = rules['indentation_size']

    line_length_errors = get_line_length_errors(code_in_lines,
                                                rules['line_length'])
    semicolon_errors = get_forbid_semicolons_errors(code_in_lines,
                                                    rules['forbid_semicolons'])
    indentation_errors, correct_code = get_indentation_errros(code_in_lines,
        rules['indentation_size'])
    nesting_errors = get_max_nesting(correct_code, rules['max_nesting'],
                                     indentation)
    methods_per_class_errors = get_methods_per_class(code_in_lines,
        rules['methods_per_class'], indentation)
    max_lines_per_function_errors = get_max_lines_per_function(code_in_lines,
        rules['max_lines_per_function'])
    arity_errors = get_max_arity(code_in_lines, rules['max_arity'])
    whitespace_errors = get_trailing_whitespace(code_in_lines,
        rules['forbid_trailing_whitespace'])
    lines_per_function_errors = get_max_lines_per_function(code_in_lines,
        rules['max_lines_per_function'])

    for dictionary in (line_length_errors, semicolon_errors, nesting_errors,
                       methods_per_class_errors, arity_errors,
                       whitespace_errors, lines_per_function_errors,
                       indentation_errors):
        for key, value in dictionary.items():
            lines_with_errors[key].append(value)

    return dict(lines_with_errors)


def get_line_length_errors(code, max_length):
    lines_with_errors = {}

    for index, line in enumerate(code):
        if len(line) > max_length:
            lines_with_errors[index + 1] = 'line too long ({} > {})'.\
                                           format(len(line), max_length)
    return lines_with_errors


def get_forbid_semicolons_errors(code, status):
    errors = {}

    for index, line in enumerate(code):
        if (';' in line) is status:
            errors[index + 1] = 'multiple expressions on the same line'

    return errors


def get_indentation_errros(code, indentation_size):
    lines_with_errors = {}
    nesting = 0
    correctly_indented_code = []
    associated_depth = {0: 0}

    for index, line in enumerate(code):
        leading_spaces = len(line) - len(line.lstrip())
        if (index > 0 and len(code[index - 1]) > 0 and
                code[index - 1].rstrip()[-1] is ':'):
            if leading_spaces in associated_depth:
                nesting = associated_depth[leading_spaces]
            else:
                nesting += 1
                associated_depth[leading_spaces] = nesting

        elif leading_spaces is 0 and len(line) > 0:
            nesting = 0
        else:
            nesting = associated_depth[leading_spaces]

        if leading_spaces % indentation_size is not 0:
            lines_with_errors[index + 1] = 'indentation is {} instead of {}'.\
                format(leading_spaces, nesting * indentation_size)
            correctly_indented_code.append(line.replace(leading_spaces * ' ',
                associated_depth[leading_spaces] * indentation_size * ' '))
        else:
            correctly_indented_code.append(line)

    return (lines_with_errors, correctly_indented_code)


def get_max_nesting(code, max_nesting, indentation_size):
    if not max_nesting:
        return {}

    lines_with_errors = {}
    nesting = 0

    for index, line in enumerate(code):
        if len(line) > 0 and line.lstrip()[-1] is ':':
            nesting = (len(line) - len(line.lstrip())) / indentation_size + 1

            if nesting > max_nesting:
                lines_with_errors[index + 2] = 'nesting too deep ({} > {})'.\
                    format(int(nesting), max_nesting)

    return lines_with_errors


def get_methods_per_class(code, max_methods, max_indentation):
    if not max_methods:
        return {}

    errors = {}
    methods_in_class = 0
    within_class = False

    for index, line in enumerate(code):
        if line.startswith('class '):
            within_class = True
        if len(line) > 0 and not line[0] is ' ':
            within_class = False
            methods_in_class = 0

        if line.lstrip().startswith('def '):
            methods_in_class += 1
            if methods_in_class > max_methods:
                errors[index + 1] = 'too many methods in class({} > {})'.\
                    format(methods_in_class, max_methods)

    return errors


def get_max_arity(code, max_arity):
    if not max_arity:
        return {}

    errors = {}

    for index, line in enumerate(code):
        if line.lstrip().startswith('def '):
            arguments = line.lstrip('(').count(',') + 1
            if arguments > max_arity:
                errors[index + 1] = 'too many arguments({} > {})'.\
                    format(arguments, max_arity)

    return errors


def get_trailing_whitespace(code, status):
    if not status:
        return {}

    errors = {}

    for index, line in enumerate(code):
        if line is not line.rstrip(' '):
            errors[index + 1] = 'trailing whitespace'

    return errors


def is_valid_python_code(line):
    try:
        codeop.compile_command(line)
    except SyntaxError:
        return False
    else:
        return True


def get_max_lines_per_function(code, max_lines):
    if not max_lines:
        return {}

    errors = {}
    logical_lines = 0
    withing_method = False
    last_def_indentation = 0
    last_det_index = 0

    index = 0
    while index < len(code):
        line = code[index]

        if line.lstrip(' ').startswith('def '):
            if logical_lines > max_lines:
                errors[last_def_index] = '{} with too many lines ({} > {})'.\
                    format('method', logical_lines, max_lines)

            withing_method = True
            logical_lines = 0
            last_def_indentation = len(line) - len(line.lstrip())
            last_def_index = index + 1
            index += 1

        elif len(line) - len(line.lstrip()) <= last_def_indentation:
            withing_method = False
            index += 1

        elif withing_method:
            if is_valid_python_code(line.lstrip()):

                semicolons = line.count(';')
                if semicolons > 0 and line.rstrip()[-1] is not ';':

                    logical_lines += (semicolons + 1)
                elif semicolons > 0 and line.rstrip()[-1] is ';':

                    logical_lines += semicolons
                elif semicolons is 0:
                    logical_lines += 1
                index += 1
            else:
                current_logical_line = line
                while not is_valid_python_code(current_logical_line):
                    current_logical_line += code[index + 1]
                    index += 1
                logical_lines += 1

        if len(code) is index and logical_lines > max_lines:
            errors[last_def_index] = 'method with too many lines ({} > {})'.\
                    format(logical_lines, max_lines)

    return errors

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

E..........
======================================================================
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
KeyError: 4

----------------------------------------------------------------------
Ran 11 tests in 0.101s

FAILED (errors=1)

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

Алекс обнови решението на 18.05.2016 12:11 (преди над 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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
from collections import defaultdict
import re
import codeop


DEFAULT_RULES = {'line_length': 79, 'forbid_semicolons': True,
                 'max_nesting': None, 'indentation_size': 4,
                 'methods_per_class': None, 'max_arity': None,
                 'forbid_trailing_whitespace': True,
                 'max_lines_per_function': None}


def critic(code, **rules):
    for rule in DEFAULT_RULES:
        if rule not in rules:
            rules[rule] = DEFAULT_RULES[rule]

    code_in_lines = code.split('\n')
    lines_with_errors = defaultdict(list)
    indentation = rules['indentation_size']

    line_length_errors = get_line_length_errors(code_in_lines,
                                                rules['line_length'])
    semicolon_errors = get_forbid_semicolons_errors(code_in_lines,
                                                    rules['forbid_semicolons'])
    indentation_errors, correct_code = get_indentation_errros(code_in_lines,
        rules['indentation_size'])
    nesting_errors = get_max_nesting(correct_code, rules['max_nesting'],
                                     indentation)
    methods_per_class_errors = get_methods_per_class(code_in_lines,
        rules['methods_per_class'], indentation)
    max_lines_per_function_errors = get_max_lines_per_function(code_in_lines,
        rules['max_lines_per_function'])
    arity_errors = get_max_arity(code_in_lines, rules['max_arity'])
    whitespace_errors = get_trailing_whitespace(code_in_lines,
        rules['forbid_trailing_whitespace'])
    lines_per_function_errors = get_max_lines_per_function(code_in_lines,
        rules['max_lines_per_function'])

    for dictionary in (line_length_errors, semicolon_errors, nesting_errors,
                       methods_per_class_errors, arity_errors,
                       whitespace_errors, lines_per_function_errors,
                       indentation_errors):
        for key, value in dictionary.items():
            lines_with_errors[key].append(value)

    return dict(lines_with_errors)


def get_line_length_errors(code, max_length):
    lines_with_errors = {}

    for index, line in enumerate(code):
        if len(line) > max_length:
            lines_with_errors[index + 1] = 'line too long ({} > {})'.\
                                           format(len(line), max_length)
    return lines_with_errors


def get_forbid_semicolons_errors(code, status):
    errors = {}

    for index, line in enumerate(code):
        if (';' in line) is status:
            errors[index + 1] = 'multiple expressions on the same line'

    return errors


def get_indentation_errros(code, indentation_size):
    lines_with_errors = {}
    nesting = 0
    correctly_indented_code = []
    associated_depth = {0: 0}

    for index, line in enumerate(code):
        leading_spaces = len(line) - len(line.lstrip())
        if (index > 0 and len(code[index - 1]) > 0 and
                code[index - 1].rstrip()[-1] is ':'):
            if leading_spaces in associated_depth:
                nesting = associated_depth[leading_spaces]
            else:
                nesting += 1
                associated_depth[leading_spaces] = nesting

        elif leading_spaces is 0 and len(line) > 0:
            nesting = 0
        else:
            nesting = associated_depth[leading_spaces]

        if leading_spaces % indentation_size is not 0:
            lines_with_errors[index + 1] = 'indentation is {} instead of {}'.\
                format(leading_spaces, nesting * indentation_size)
            correctly_indented_code.append(line.replace(leading_spaces * ' ',
                associated_depth[leading_spaces] * indentation_size * ' '))
        else:
            correctly_indented_code.append(line)

    return (lines_with_errors, correctly_indented_code)


def get_max_nesting(code, max_nesting, indentation_size):
    if not max_nesting:
        return {}

    lines_with_errors = {}
    nesting = 0

    for index, line in enumerate(code):
        if len(line) > 0 and line.lstrip()[-1] is ':':
            nesting = (len(line) - len(line.lstrip())) / indentation_size + 1

            if nesting > max_nesting:
                lines_with_errors[index + 2] = 'nesting too deep ({} > {})'.\
                    format(int(nesting), max_nesting)

    return lines_with_errors


def get_methods_per_class(code, max_methods, max_indentation):
    if not max_methods:
        return {}

    errors = {}
    methods_in_class = 0
    within_class = False

    for index, line in enumerate(code):
        if line.startswith('class '):
            within_class = True
        if len(line) > 0 and not line[0] is ' ':
            within_class = False
            methods_in_class = 0

        if line.lstrip().startswith('def '):
            methods_in_class += 1
            if methods_in_class > max_methods:
                errors[index + 1] = 'too many methods in class({} > {})'.\
                    format(methods_in_class, max_methods)

    return errors


def get_max_arity(code, max_arity):
    if not max_arity:
        return {}

    errors = {}

    for index, line in enumerate(code):
        if line.lstrip().startswith('def '):
            arguments = line.lstrip('(').count(',') + 1
            if arguments > max_arity:
                errors[index + 1] = 'too many arguments({} > {})'.\
                    format(arguments, max_arity)

    return errors


def get_trailing_whitespace(code, status):
    if not status:
        return {}

    errors = {}

    for index, line in enumerate(code):
        if line is not line.rstrip(' '):
            errors[index + 1] = 'trailing whitespace'

    return errors


def is_valid_python_code(line):
    try:
        codeop.compile_command(line)
    except SyntaxError:
        return False
    else:
        return True


def get_max_lines_per_function(code, max_lines):
    if not max_lines:
        return {}

    errors = {}
    logical_lines = 0
    withing_method = False
    last_def_indentation = 0
    last_det_index = 0

    index = 0
    while index < len(code):
        line = code[index]

        if line.lstrip(' ').startswith('def '):
            if logical_lines > max_lines:
                errors[last_def_index] = '{} with too many lines ({} > {})'.\
                    format('method', logical_lines, max_lines)

            withing_method = True
            logical_lines = 0
            last_def_indentation = len(line) - len(line.lstrip())
            last_def_index = index + 1
            index += 1

        elif len(line) - len(line.lstrip()) <= last_def_indentation:
            withing_method = False
            index += 1

        elif withing_method:
            if is_valid_python_code(line.lstrip()):

                semicolons = line.count(';')
                if semicolons > 0 and line.rstrip()[-1] is not ';':

                    logical_lines += (semicolons + 1)
                elif semicolons > 0 and line.rstrip()[-1] is ';':

                    logical_lines += semicolons
                elif semicolons is 0:
                    logical_lines += 1
                index += 1
            else:
                current_logical_line = line
                while not is_valid_python_code(current_logical_line):
                    current_logical_line += code[index + 1]
                    index += 1
                logical_lines += 1

        if len(code) is index and logical_lines > max_lines:
            errors[last_def_index] = 'method with too many lines ({} > {})'.\
                    format(logical_lines, max_lines)

    return errors