timeit

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

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

Решение на Статичен анализ на python код от Николай Бабулков

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

Към профила на Николай Бабулков

Резултати

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

Код

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
import ast
import re


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 line_length(lines, length):
    error = 'line too long ({} > {})'
    return {lineno + 1: [error.format(len(lines[lineno]), length)]
            for lineno in range(len(lines)) if len(lines[lineno]) > length}


def multiple_expressions(lines):
    error = 'multiple expressions on the same line'
    return {lineno + 1: [error] for lineno in range(len(lines))
            if ';' in lines[lineno]}


def indent_count(line):
    count = 1
    while True:
        if not line.startswith(' ' * count):
            return count - 1
        count += 1
    return count


def col_comparator(node):
    if isinstance(node, ast.stmt):
        return node.col_offset
    return 0


def max_nesting(tree, max_level):
    error = 'nesting too deep ({} > {})'
    errors = {}
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            max_node = max(ast.walk(node), key=col_comparator)
            max_indent = max_node.col_offset
            func_indent = node.col_offset
            difference = int((max_indent - func_indent) / 4)
            if max_level < difference:
                errors[max_node.lineno] = [error.format(difference, max_level)]
    return errors


def trailing_whitespace(lines):
    error = 'trailing whitespace'
    return {lineno + 1: [error] for lineno in range(len(lines))
            if re.search(r' $', lines[lineno])}


def indentation_size(lines, size):
    error = 'indentation is {} instead of {}'
    errors = {}
    indent = 0
    for lineno in range(len(lines)):
        actual_indent = indent_count(lines[lineno])
        if actual_indent % size is 0 and actual_indent < indent:
            indent = int(actual_indent / size)
        if actual_indent is not indent and actual_indent is not 0:
            errors[lineno + 1] = [error.format(actual_indent, indent)]
        if re.search(r': *$', lines[lineno]):
            indent += size
    return errors


def lineno_comparator(node):
    if isinstance(node, ast.stmt) or isinstance(node, ast.expr):
        return node.lineno
    return 0


def max_lines_per_function(tree, max_lines):
    error = 'method with too many lines ({} > {})'
    errors = {}
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            start = node.lineno
            end = max(ast.walk(node), key=lineno_comparator).lineno
            if end - start > max_lines:
                errors[start] = [error.format(end - start, max_lines)]
    return errors


def methods_per_class(tree, methods_count):
    error = 'too many methods in class({} > {})'
    errors = {}
    for node in ast.walk(tree):
        if isinstance(node, ast.ClassDef):
            count = 0
            for func_node in ast.walk(node):
                if isinstance(func_node, ast.FunctionDef):
                    count += 1
            if count > methods_count:
                errors[node.lineno] = [error.format(count, methods_count)]
    return errors


def max_arity(tree, arguments_count):
    error = 'too many arguments({} > {})'
    errors = {}
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            count = 0
            for func_node in ast.walk(node.args):
                if isinstance(func_node, ast.arg):
                    count += 1
            if count > arguments_count:
                errors[node.lineno] = [error.format(count, arguments_count)]
    return errors


def merge_dicts(dict1, dict2):
    for key in dict2.keys():
        if key in dict1.keys():
            dict1[key].extend(dict2[key])
        else:
            dict1[key] = dict2[key]


def critic(code, **rules):
    additional_rules = {key: value for key, value in RULES.items()
                        if key not in rules}
    rules.update(additional_rules)

    lines = code.split('\n')

    errors = line_length(lines, rules['line_length'])
    merge_dicts(errors, indentation_size(lines, rules['indentation_size']))

    if rules['forbid_semicolons'] is True:
        merge_dicts(errors, multiple_expressions(lines))
    if rules['forbid_trailing_whitespace'] is True:
        merge_dicts(errors, trailing_whitespace(lines))

    tree = ast.parse(code)
    if rules['max_nesting'] is not None:
        merge_dicts(errors, max_nesting(tree, rules['max_nesting']))
    if rules['methods_per_class'] is not None:
        merge_dicts(errors,
                    methods_per_class(tree, rules['methods_per_class']))
    if rules['max_arity'] is not None:
        merge_dicts(errors, max_arity(tree, rules['max_arity']))
    if rules['max_lines_per_function'] is not None:
        merge_dicts(errors,
                    max_lines_per_function(tree,
                                           rules['max_lines_per_function']))

    return errors

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

F.....F....
======================================================================
FAIL: 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
AssertionError: 4 != 0

======================================================================
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: 9 != 5

----------------------------------------------------------------------
Ran 11 tests in 0.140s

FAILED (failures=2)

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

Николай обнови решението на 18.05.2016 16:51 (преди над 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
import ast
import re


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 line_length(lines, length):
    error = 'line too long ({} > {})'
    return {lineno + 1: [error.format(len(lines[lineno]), length)]
            for lineno in range(len(lines)) if len(lines[lineno]) > length}


def multiple_expressions(lines):
    error = 'multiple expressions on the same line'
    return {lineno + 1: [error] for lineno in range(len(lines))
            if ';' in lines[lineno]}


def indent_count(line):
    count = 1
    while True:
        if not line.startswith(' ' * count):
            return count - 1
        count += 1
    return count


def col_comparator(node):
    if isinstance(node, ast.stmt):
        return node.col_offset
    return 0


def max_nesting(tree, max_level):
    error = 'nesting too deep ({} > {})'
    errors = {}
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            max_node = max(ast.walk(node), key=col_comparator)
            max_indent = max_node.col_offset
            func_indent = node.col_offset
            difference = int((max_indent - func_indent) / 4)
            if max_level < difference:
                errors[max_node.lineno] = [error.format(difference, max_level)]
    return errors


def trailing_whitespace(lines):
    error = 'trailing whitespace'
    return {lineno + 1: [error] for lineno in range(len(lines))
            if re.search(r' $', lines[lineno])}


def indentation_size(lines, size):
    error = 'indentation is {} instead of {}'
    errors = {}
    indent = 0
    for lineno in range(len(lines)):
        actual_indent = indent_count(lines[lineno])
        if actual_indent % size is 0 and actual_indent < indent:
            indent = int(actual_indent / size)
        if actual_indent is not indent and actual_indent is not 0:
            errors[lineno + 1] = [error.format(actual_indent, indent)]
        if re.search(r': *$', lines[lineno]):
            indent += size
    return errors


def lineno_comparator(node):
    if isinstance(node, ast.stmt) or isinstance(node, ast.expr):
        return node.lineno
    return 0


def max_lines_per_function(tree, max_lines):
    error = 'method with too many lines ({} > {})'
    errors = {}
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            start = node.lineno
            end = max(ast.walk(node), key=lineno_comparator).lineno
            if end - start > max_lines:
                errors[start] = [error.format(end - start, max_lines)]
    return errors


def methods_per_class(tree, methods_count):
    error = 'too many methods in class({} > {})'
    errors = {}
    for node in ast.walk(tree):
        if isinstance(node, ast.ClassDef):
            count = 0
            for func_node in ast.walk(node):
                if isinstance(func_node, ast.FunctionDef):
                    count += 1
            if count > methods_count:
                errors[node.lineno] = [error.format(count, methods_count)]
    return errors


def max_arity(tree, arguments_count):
    error = 'too many arguments({} > {})'
    errors = {}
    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            count = 0
            for func_node in ast.walk(node.args):
                if isinstance(func_node, ast.arg):
                    count += 1
            if count > arguments_count:
                errors[node.lineno] = [error.format(count, arguments_count)]
    return errors


def merge_dicts(dict1, dict2):
    for key in dict2.keys():
        if key in dict1.keys():
            dict1[key].extend(dict2[key])
        else:
            dict1[key] = dict2[key]


def critic(code, **rules):
    additional_rules = {key: value for key, value in RULES.items()
                        if key not in rules}
    rules.update(additional_rules)

    lines = code.split('\n')

    errors = line_length(lines, rules['line_length'])
    merge_dicts(errors, indentation_size(lines, rules['indentation_size']))

    if rules['forbid_semicolons'] is True:
        merge_dicts(errors, multiple_expressions(lines))
    if rules['forbid_trailing_whitespace'] is True:
        merge_dicts(errors, trailing_whitespace(lines))

    tree = ast.parse(code)
    if rules['max_nesting'] is not None:
        merge_dicts(errors, max_nesting(tree, rules['max_nesting']))
    if rules['methods_per_class'] is not None:
        merge_dicts(errors,
                    methods_per_class(tree, rules['methods_per_class']))
    if rules['max_arity'] is not None:
        merge_dicts(errors, max_arity(tree, rules['max_arity']))
    if rules['max_lines_per_function'] is not None:
        merge_dicts(errors,
                    max_lines_per_function(tree,
                                           rules['max_lines_per_function']))

    return errors