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


class CodeError(ast.NodeVisitor):
    GATES = [ast.If, ast.For, ast.While, ast.Break,
             ast.Try, ast.ExceptHandler, ast.With, ast.withitem]

    def __init__(self, code):
        self.error_list = defaultdict(list)
        self.code = code
        self.ast_tree = ast.parse(code)

    def line_length(self, line_length=79):
        for i, line in enumerate(self.code.splitlines()):
            if len(line) > line_length:
                self.error_list[i + 1].append(
                    'line too long ({} > {})'.format(len(line), line_length))

    def trailing_whitespace(self, forbid_trailing_whitespace=True):
        if forbid_trailing_whitespace is False:
            return
        for i, line in enumerate(self.code.splitlines()):
            if re.search(r'[ \t]+$', line):
                self.error_list[i + 1].append('trailing whitespace')

    def forbid_semicolons(self, forbid=True):
        if not forbid:
            return
        for i, line in enumerate(self.code.splitlines()):
            line = re.sub(r'([\'\"]).*?\1', '', line)
            if ';' in line:
                self.error_list[i + 1].append(
                    'multiple expressions on the same line')

    def visit_FunctionDef(self, node):
        self.generic_visit(node)

    def visit_ClassDef(self, node):
        self.generic_visit(node)

    def lines_in_function_help(self, node, length=0):
        parsed = ast.parse(node)
        length = len(node.body)
        for element in ast.iter_child_nodes(parsed):
            if type(element) in self.GATES:
                length += self.lines_in_function_help(element)

        return length

    def lines_in_function(self, code, max_lines=None):
        if max_lines is None:
            return
        for node in ast.walk(code):
            if isinstance(node, ast.FunctionDef):
                length = self.lines_in_function_help(node)
                if length > max_lines:
                    self.error_list[node.lineno].append(
                        'method with too many lines ({} > {})'.format(
                            length, max_lines))

    def methods_in_class(self, code, max_methods=None):
        if max_methods is None:
            return
        for node in ast.walk(code):
            if isinstance(node, ast.ClassDef):
                methods = 0
                for method in node.body:
                    if isinstance(method, ast.FunctionDef):
                        methods += 1
                if methods > max_methods:
                    self.error_list[node.lineno].append(
                        'too many methods in class({} > {})'.format(
                            methods, max_methods))

    def arguments_in_function(self, code, max_arity=None):
        if max_arity is None:
            return
        parsed = ast.parse(code)
        for node in ast.walk(parsed):
            if isinstance(node, (ast.FunctionDef)):
                args = len(node.args.args)
                if args > max_arity:
                    self.error_list[node.lineno].append(
                        'too many arguments({} > {})'.format(args, max_arity))

    def nesting_help(self, node, depths, depth=0):
        depth += 1
        for element in node.body:
            if hasattr(element, 'lineno'):
                depths[element.lineno] = depth
            if type(element) in self.GATES:
                self.nesting_help(element, depths, depth)

        return depths

    def nesting(self, code, max_nesting=None):
        if max_nesting is None:
            return
        for node in ast.walk(code):
            if isinstance(node, ast.FunctionDef):
                depths = {}
                self.nesting_help(node, depths)
                for key, value in depths.items():
                    if value > max_nesting:
                        self.error_list[key].append(
                            'nesting too deep ({} > {})'.format(
                                value, max_nesting))

    def indentation(self, node, depths, depth=0, indentation_size=4):
        if not hasattr(node, 'body'):
            return depth
        for element in node.body:
            if hasattr(element, 'lineno') and hasattr(element, 'col_offset'):
                if element.lineno in depths:
                    if element.col_offset < depths[element.lineno]:
                        depths[element.lineno] = element.col_offset
                if element.lineno not in depths:
                    depths[element.lineno] = element.col_offset
                if depth * indentation_size != element.col_offset and \
                        element.col_offset in depths.values():
                    self.error_list[element.lineno].append(
                        'indentation is {} instead of {}'.format(
                            element.col_offset, depth * indentation_size))
            if type(element) in self.GATES or \
                    isinstance(element, ast.FunctionDef) or \
                    isinstance(element, ast.ClassDef):
                self.indentation(element, depths, depth + 1)


def critic(code, **rules):
    code_err = CodeError(code)
    code_err.line_length(rules.get('line_length', 79))
    code_err.forbid_semicolons(rules.get('forbid_semicolons', True))
    code_err.nesting(code_err.ast_tree, rules.get('max_nesting', None))
    code_err.indentation(code_err.ast_tree, dict(), 0,
                         rules.get('indentation_size', 4))
    code_err.methods_in_class(
        code_err.ast_tree, rules.get('methods_per_class', None))
    code_err.arguments_in_function(
        code_err.ast_tree, rules.get('max_arity', None))
    code_err.trailing_whitespace(rules.get('forbid_trailing_whitespace', True))
    code_err.lines_in_function(
        code_err.ast_tree, rules.get('max_lines_per_function', None))

    error_list = code_err.error_list
    return error_list

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

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

----------------------------------------------------------------------
Ran 11 tests in 0.104s

FAILED (failures=1)

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

Борис обнови решението на 17.05.2016 13:00 (преди над 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
from collections import defaultdict
import re
import ast


class CodeError(ast.NodeVisitor):
    GATES = [ast.If, ast.For, ast.While, ast.Break,
             ast.Try, ast.ExceptHandler, ast.With, ast.withitem]

    def __init__(self, code):
        self.error_list = defaultdict(list)
        self.code = code
        self.ast_tree = ast.parse(code)

    def line_length(self, line_length=79):
        for i, line in enumerate(self.code.splitlines()):
            if len(line) > line_length:
                self.error_list[i + 1].append(
                    'line too long ({} > {})'.format(len(line), line_length))

    def trailing_whitespace(self, forbid_trailing_whitespace=True):
        if forbid_trailing_whitespace is False:
            return
        for i, line in enumerate(self.code.splitlines()):
            if re.search(r'[ \t]+$', line):
                self.error_list[i + 1].append('trailing whitespace')

    def forbid_semicolons(self, forbid=True):
        if not forbid:
            return
        for i, line in enumerate(self.code.splitlines()):
            line = re.sub(r'([\'\"]).*?\1', '', line)
            if ';' in line:
                self.error_list[i + 1].append(
                    'multiple expressions on the same line')

    def visit_FunctionDef(self, node):
        self.generic_visit(node)

    def visit_ClassDef(self, node):
        self.generic_visit(node)

    def lines_in_function_help(self, node, length=0):
        parsed = ast.parse(node)
        length = len(node.body)
        for element in ast.iter_child_nodes(parsed):
            if type(element) in self.GATES:
                length += self.lines_in_function_help(element)

        return length

    def lines_in_function(self, code, max_lines=None):
        if max_lines is None:
            return
        for node in ast.walk(code):
            if isinstance(node, ast.FunctionDef):
                length = self.lines_in_function_help(node)
                if length > max_lines:
                    self.error_list[node.lineno].append(
                        'method with too many lines ({} > {})'.format(
                            length, max_lines))

    def methods_in_class(self, code, max_methods=None):
        if max_methods is None:
            return
        for node in ast.walk(code):
            if isinstance(node, ast.ClassDef):
                methods = 0
                for method in node.body:
                    if isinstance(method, ast.FunctionDef):
                        methods += 1
                if methods > max_methods:
                    self.error_list[node.lineno].append(
                        'too many methods in class({} > {})'.format(
                            methods, max_methods))

    def arguments_in_function(self, code, max_arity=None):
        if max_arity is None:
            return
        parsed = ast.parse(code)
        for node in ast.walk(parsed):
            if isinstance(node, (ast.FunctionDef)):
                args = len(node.args.args)
                if args > max_arity:
                    self.error_list[node.lineno].append(
                        'too many arguments({} > {})'.format(args, max_arity))

    def nesting_help(self, node, depths, depth=0):
        depth += 1
        for element in node.body:
            if hasattr(element, 'lineno'):
                depths[element.lineno] = depth
            if type(element) in self.GATES:
                self.nesting_help(element, depths, depth)

        return depths

    def nesting(self, code, max_nesting=None):
        if max_nesting is None:
            return
        for node in ast.walk(code):
            if isinstance(node, ast.FunctionDef):
                depths = {}
                self.nesting_help(node, depths)
                for key, value in depths.items():
                    if value > max_nesting:
                        self.error_list[key].append(
                            'nesting too deep ({} > {})'.format(
                                value, max_nesting))

    def indentation(self, node, depths, depth=0, indentation_size=4):
        if not hasattr(node, 'body'):
            return depth
        for element in node.body:
            if hasattr(element, 'lineno') and hasattr(element, 'col_offset'):
                if element.lineno in depths:
                    if element.col_offset < depths[element.lineno]:
                        depths[element.lineno] = element.col_offset
                if element.lineno not in depths:
                    depths[element.lineno] = element.col_offset
                if depth * indentation_size != element.col_offset and \
                        element.col_offset in depths.values():
                    self.error_list[element.lineno].append(
                        'indentation is {} instead of {}'.format(
                            element.col_offset, depth * indentation_size))
            if type(element) in self.GATES or \
                    isinstance(element, ast.FunctionDef) or \
                    isinstance(element, ast.ClassDef):
                self.indentation(element, depths, depth + 1)


def critic(code, **rules):
    code_err = CodeError(code)
    code_err.line_length(rules.get('line_length', 79))
    code_err.forbid_semicolons(rules.get('forbid_semicolons', True))
    code_err.nesting(code_err.ast_tree, rules.get('max_nesting', None))
    code_err.indentation(code_err.ast_tree, dict(), 0,
                         rules.get('indentation_size', 4))
    code_err.methods_in_class(
        code_err.ast_tree, rules.get('methods_per_class', None))
    code_err.arguments_in_function(
        code_err.ast_tree, rules.get('max_arity', None))
    code_err.trailing_whitespace(rules.get('forbid_trailing_whitespace', True))
    code_err.lines_in_function(
        code_err.ast_tree, rules.get('max_lines_per_function', None))

    error_list = code_err.error_list
    return error_list