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
import ast
import re
from collections import defaultdict, Counter

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
}

ERROR_MESSAGES = {
    'line_length': 'line too long ({} > {})',
    'forbid_semicolons': 'multiple expressions on the same line',
    'max_nesting': 'nesting too deep ({} > {})',
    'indentation_size': 'indentation is {} instead of {}',
    'methods_per_class': 'too many methods in class({} > {})',
    'max_arity': 'too many arguments({} > {})',
    'forbid_trailing_whitespace': 'trailing whitespace',
    'max_lines_per_function': 'method with too many lines ({} > {})'
}


def critic(code, **rules):
    rules_to_check = DEFAULT_RULES
    rules_to_check.update(rules)

    result = defaultdict(list)

    tree = ast.parse(code)
    lines = code.splitlines()

    for index, line in enumerate(lines):
        long_line_error = check_line_length(line,
                                            rules_to_check['line_length'])
        if long_line_error:
            result[index + 1].append(long_line_error)

        indentation_error = check_line_indent(
            line,
            rules_to_check['indentation_size'])
        if indentation_error:
            result[index + 1].append(indentation_error)

        if rules_to_check['forbid_trailing_whitespace']:
            trailing_whitespace_error = check_trailing_whitespace(
                line.lstrip())
            if trailing_whitespace_error:
                result[index + 1].append(trailing_whitespace_error)

    if rules_to_check['max_arity'] or rules_to_check['max_arity'] == 0:
        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                max_arity_error = check_max_arity(
                    node,
                    rules_to_check['max_arity'])
                if max_arity_error:
                    result[node.lineno].append(max_arity_error)

    if rules_to_check['methods_per_class'] or rules_to_check['methods_per_class'] == 0:
        for node in ast.walk(tree):
            if isinstance(node, ast.ClassDef):
                methods_count = 0
                for n in node.body:
                    if isinstance(n, ast.FunctionDef):
                        methods_count += 1

                if methods_count > rules_to_check['methods_per_class']:
                    result[node.lineno].append(
                        ERROR_MESSAGES['methods_per_class'].format(
                            methods_count,
                            rules_to_check['methods_per_class']))

    if rules_to_check['forbid_semicolons']:
        assignments_line_numbers = [node.lineno for node in ast.walk(tree)
                                    if isinstance(node, ast.Assign)]
        assignments_per_lineno = Counter(assignments_line_numbers)
        for lineno in assignments_per_lineno:
            if assignments_per_lineno[lineno] > 1:
                result[lineno].append(ERROR_MESSAGES['forbid_semicolons'])

    if rules_to_check['max_nesting']:
        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                max_nesting = calculate_max_nesting(node)
                if max_nesting[0] > rules_to_check['max_nesting']:
                    result[max_nesting[1]].append(
                        ERROR_MESSAGES['max_nesting'].format(
                            max_nesting[0], rules_to_check['max_nesting']))
    return result


def check_line_length(line, max_line_length):
    if len(line) > max_line_length:
        return ERROR_MESSAGES['line_length'].format(len(line), max_line_length)


def check_line_indent(line, allowed_indent):
    line_indent = len(line) - len(line.lstrip(' '))
    if line_indent % allowed_indent != 0:
        return ERROR_MESSAGES['indentation_size'].format(
            line_indent, allowed_indent)


def check_trailing_whitespace(line):
    if re.search(r'\s$', line):
        return ERROR_MESSAGES['forbid_trailing_whitespace']


def check_max_arity(node, max_arity):
    arguments_count = len(node.args.args)
    if node.args.vararg:
        arguments_count += 1
    if node.args.kwarg:
        arguments_count += 1

    if arguments_count > max_arity:
        return ERROR_MESSAGES['max_arity'].format(arguments_count, max_arity)


def calculate_max_nesting(node, start_count=1):
    ast_controw_flow_nodes = (ast.If, ast.While, ast.For, ast.With,
                              ast.Try, ast.ExceptHandler)
    for body_node in node.body:
        if isinstance(body_node, ast_controw_flow_nodes):
            count = calculate_max_nesting(body_node, start_count + 1)
            if not count:
                return start_count + 1
            return count
    return start_count, node.lineno + 1

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

.....FF....
======================================================================
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: 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: 3 != 5

----------------------------------------------------------------------
Ran 11 tests in 0.115s

FAILED (failures=2)

История (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
125
126
127
128
129
130
131
132
133
134
import ast
import re
from collections import defaultdict, Counter

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
}

ERROR_MESSAGES = {
    'line_length': 'line too long ({} > {})',
    'forbid_semicolons': 'multiple expressions on the same line',
    'max_nesting': 'nesting too deep ({} > {})',
    'indentation_size': 'indentation is {} instead of {}',
    'methods_per_class': 'too many methods in class({} > {})',
    'max_arity': 'too many arguments({} > {})',
    'forbid_trailing_whitespace': 'trailing whitespace',
    'max_lines_per_function': 'method with too many lines ({} > {})'
}


def critic(code, **rules):
    rules_to_check = DEFAULT_RULES
    rules_to_check.update(rules)

    result = defaultdict(list)

    tree = ast.parse(code)
    lines = code.splitlines()

    for index, line in enumerate(lines):
        long_line_error = check_line_length(line,
                                            rules_to_check['line_length'])
        if long_line_error:
            result[index + 1].append(long_line_error)

        indentation_error = check_line_indent(
            line,
            rules_to_check['indentation_size'])
        if indentation_error:
            result[index + 1].append(indentation_error)

        if rules_to_check['forbid_trailing_whitespace']:
            trailing_whitespace_error = check_trailing_whitespace(
                line.lstrip())
            if trailing_whitespace_error:
                result[index + 1].append(trailing_whitespace_error)

    if rules_to_check['max_arity'] or rules_to_check['max_arity'] == 0:
        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                max_arity_error = check_max_arity(
                    node,
                    rules_to_check['max_arity'])
                if max_arity_error:
                    result[node.lineno].append(max_arity_error)

    if rules_to_check['methods_per_class'] or rules_to_check['methods_per_class'] == 0:
        for node in ast.walk(tree):
            if isinstance(node, ast.ClassDef):
                methods_count = 0
                for n in node.body:
                    if isinstance(n, ast.FunctionDef):
                        methods_count += 1

                if methods_count > rules_to_check['methods_per_class']:
                    result[node.lineno].append(
                        ERROR_MESSAGES['methods_per_class'].format(
                            methods_count,
                            rules_to_check['methods_per_class']))

    if rules_to_check['forbid_semicolons']:
        assignments_line_numbers = [node.lineno for node in ast.walk(tree)
                                    if isinstance(node, ast.Assign)]
        assignments_per_lineno = Counter(assignments_line_numbers)
        for lineno in assignments_per_lineno:
            if assignments_per_lineno[lineno] > 1:
                result[lineno].append(ERROR_MESSAGES['forbid_semicolons'])

    if rules_to_check['max_nesting']:
        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                max_nesting = calculate_max_nesting(node)
                if max_nesting[0] > rules_to_check['max_nesting']:
                    result[max_nesting[1]].append(
                        ERROR_MESSAGES['max_nesting'].format(
                            max_nesting[0], rules_to_check['max_nesting']))
    return result


def check_line_length(line, max_line_length):
    if len(line) > max_line_length:
        return ERROR_MESSAGES['line_length'].format(len(line), max_line_length)


def check_line_indent(line, allowed_indent):
    line_indent = len(line) - len(line.lstrip(' '))
    if line_indent % allowed_indent != 0:
        return ERROR_MESSAGES['indentation_size'].format(
            line_indent, allowed_indent)


def check_trailing_whitespace(line):
    if re.search(r'\s$', line):
        return ERROR_MESSAGES['forbid_trailing_whitespace']


def check_max_arity(node, max_arity):
    arguments_count = len(node.args.args)
    if node.args.vararg:
        arguments_count += 1
    if node.args.kwarg:
        arguments_count += 1

    if arguments_count > max_arity:
        return ERROR_MESSAGES['max_arity'].format(arguments_count, max_arity)


def calculate_max_nesting(node, start_count=1):
    ast_controw_flow_nodes = (ast.If, ast.While, ast.For, ast.With,
                              ast.Try, ast.ExceptHandler)
    for body_node in node.body:
        if isinstance(body_node, ast_controw_flow_nodes):
            count = calculate_max_nesting(body_node, start_count + 1)
            if not count:
                return start_count + 1
            return count
    return start_count, node.lineno + 1