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
168
169
170
171
172
173
174
175
176
177
178
179
from ast import NodeVisitor
import ast
import re
from collections import defaultdict


default_rules = {
    'line_length': 79,
    'forbid_semicolon': 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):
    code_critic = CodeCritic(**rules)
    return code_critic.get_errors(code)


class CodeCritic:
    def __init__(self, **rules):
        self.rules = {
            key: (rules[key] if key in rules else default_rules[key])
            for key in default_rules
        }
        self.code_problems = defaultdict(set)
        self.node_error_finder = NodeErrorFinder(self.rules)
        self.line_error_finder = LineErrorFinder(self.rules)

    def get_errors(self, code):
        self.merge_problems(self.line_error_finder.get_errors(code))
        self.merge_problems(self.node_error_finder.get_errors(code))
        return dict(self.code_problems)

    def merge_problems(self, problems):
        for key in problems:
            self.code_problems[key].update(problems[key])


class memoize:
    def __init__(self, function):
        self.function = function
        self.memoized = {}

    def __call__(self, *args):
        try:
            return self.memoized[args]
        except KeyError:
            self.memoized[args] = self.function(*args)
            return self.memoized[args]


NESTED_NODE_TYPES = {ast.ClassDef, ast.FunctionDef, ast.If, ast.While,
                     ast.For, ast.With, ast.Try}


class NodeErrorFinder:
    def __init__(self, rules):
        self.rules = rules
        self.problems = defaultdict(set)

    def get_errors(self, code):
        tree = ast.parse(code)
        self.__iter_nodes(tree, 0)
        return self.problems

    def __iter_nodes(self, node, depth):
        if not hasattr(node, 'body'):
            return
        for child_node in node.body:
            self.__make_checks(child_node, depth)
            inner_depth = (depth + 1
                           if type(child_node)
                           in NESTED_NODE_TYPES else depth)
            self.__iter_nodes(child_node, inner_depth)

    def __make_checks(self, node, depth):
        if not hasattr(node, 'lineno'):
            return

        if self.rules['max_nesting']:
            self.__check_nesting(node, depth)

        self.__check_indent(node, depth)

        if self.rules['methods_per_class'] and isinstance(node, ast.ClassDef):
            self.__check_max_methods(node)

        if isinstance(node, ast.FunctionDef):
            if self.rules['max_arity']:
                self.__check_max_arguments(node)

            if self.rules['max_lines_per_function']:
                self.__check_max_lines(node)

    def __check_nesting(self, node, depth):
        if depth > self.rules['max_nesting']:
            error = 'nesting too deep ({} > {})'.format(
                    depth, self.rules['max_nesting'])
            self.problems[node.lineno].add(error)

    def __check_indent(self, node, depth):
        indent = depth * self.rules['indentation_size']
        if node.col_offset is not indent:
            error = 'indentation is {} instead of {}'.format(
                    node.col_offset, indent)
            self.problems[node.lineno].add(error)

    def __check_max_methods(self, classdef_node):
        count = 0
        for node in ast.iter_child_nodes(classdef_node):
            if type(node) is ast.FunctionDef:
                count = count + 1

        if count > self.rules['methods_per_class']:
            error = 'too many methods in class({} > {})'.format(
                count, self.rules['methods_per_class'])
            self.problems[classdef_node.lineno].add(error)

    def __check_max_arguments(self, funcdef_node):
        args_count = len(funcdef_node.args.args)
        if args_count > self.rules['max_arity']:
            error = 'too many arguments({} > {})'.format(
                args_count, self.rules['max_arity'])
            self.problems[funcdef_node.lineno].add(error)

    def __check_max_lines(self, node):
        logical_lines = len(node.body)
        if logical_lines > self.rules['max_lines_per_function']:
            error = 'method with too many lines ({} > {})'.format(
                logical_lines, self.rules['max_lines_per_function'])
            self.problems[node.lineno].add(error)


class LineErrorFinder:
    def __init__(self, rules):
        self.rules = rules
        self.problems = defaultdict(set)

    def get_errors(self, code):
        self.__iter_lines(code)
        return self.problems

    def __iter_lines(self, code):
        for line_num, line in enumerate(code.splitlines()):
            self.__make_checks(line_num + 1, line)

    def __make_checks(self, line_num, line):
        self.__check_line_length(line_num, line)

        if self.rules['forbid_semicolon']:
            self.__check_semicolons(line_num, line)

        if self.rules['forbid_trailing_whitespace']:
            self.__check_trailing_whitespace(line_num, line)

    def __check_line_length(self, line_num, line):
        length = len(line)
        if length > self.rules['line_length']:
            error = 'line too long ({} > {})'.format(
                length, self.rules['line_length'])
            self.problems[line_num].add(error)

    def __check_semicolons(self, line_num, line):
        pattern = r'(?<!\\)\"[^\"]*(?<!\\)\"|(?<!\\)\'[^\']*(?<!\\)\''
        line = re.sub(pattern, '', line)
        if ';' in line:
            error = 'multiple expressions on the same line'
            self.problems[line_num].add(error)

    def __check_trailing_whitespace(self, line_num, line):
        pattern = r'.*\s($|\n)'
        if re.match(pattern, line):
            error = 'trailing whitespace'
            self.problems[line_num].add(error)

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

....F.F...F
======================================================================
FAIL: test_long_line_with_several_statements (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:
'indentation is 67 instead of 4'
'indentation is 30 instead of 4'

======================================================================
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:
'indentation is 21 instead of 8'

======================================================================
FAIL: test_two_statements_on_one_line (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:
'indentation is 7 instead of 0'

----------------------------------------------------------------------
Ran 11 tests in 0.107s

FAILED (failures=3)

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

Виктор обнови решението на 17.05.2016 23:57 (преди над 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
from ast import NodeVisitor
import ast
import re
from collections import defaultdict


default_rules = {
    'line_length': 79,
    'forbid_semicolon': 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):
    code_critic = CodeCritic(**rules)
    return code_critic.get_errors(code)


class CodeCritic:
    def __init__(self, **rules):
        self.rules = {
            key: (rules[key] if key in rules else default_rules[key])
            for key in default_rules
        }
        self.code_problems = defaultdict(set)
        self.node_error_finder = NodeErrorFinder(self.rules)
        self.line_error_finder = LineErrorFinder(self.rules)

    def get_errors(self, code):
        self.merge_problems(self.line_error_finder.get_errors(code))
        self.merge_problems(self.node_error_finder.get_errors(code))
        return dict(self.code_problems)

    def merge_problems(self, problems):
        for key in problems:
            self.code_problems[key].update(problems[key])


class memoize:
    def __init__(self, function):
        self.function = function
        self.memoized = {}

    def __call__(self, *args):
        try:
            return self.memoized[args]
        except KeyError:
            self.memoized[args] = self.function(*args)
            return self.memoized[args]


NESTED_NODE_TYPES = {ast.ClassDef, ast.FunctionDef, ast.If, ast.While,
                     ast.For, ast.With, ast.Try}


class NodeErrorFinder:
    def __init__(self, rules):
        self.rules = rules
        self.problems = defaultdict(set)

    def get_errors(self, code):
        tree = ast.parse(code)
        self.__iter_nodes(tree, 0)
        return self.problems

    def __iter_nodes(self, node, depth):
        if not hasattr(node, 'body'):
            return
        for child_node in node.body:
            self.__make_checks(child_node, depth)
            inner_depth = (depth + 1
                           if type(child_node)
                           in NESTED_NODE_TYPES else depth)
            self.__iter_nodes(child_node, inner_depth)

    def __make_checks(self, node, depth):
        if not hasattr(node, 'lineno'):
            return

        if self.rules['max_nesting']:
            self.__check_nesting(node, depth)

        self.__check_indent(node, depth)

        if self.rules['methods_per_class'] and isinstance(node, ast.ClassDef):
            self.__check_max_methods(node)

        if isinstance(node, ast.FunctionDef):
            if self.rules['max_arity']:
                self.__check_max_arguments(node)

            if self.rules['max_lines_per_function']:
                self.__check_max_lines(node)

    def __check_nesting(self, node, depth):
        if depth > self.rules['max_nesting']:
            error = 'nesting too deep ({} > {})'.format(
                    depth, self.rules['max_nesting'])
            self.problems[node.lineno].add(error)

    def __check_indent(self, node, depth):
        indent = depth * self.rules['indentation_size']
        if node.col_offset is not indent:
            error = 'indentation is {} instead of {}'.format(
                    node.col_offset, indent)
            self.problems[node.lineno].add(error)

    def __check_max_methods(self, classdef_node):
        count = 0
        for node in ast.iter_child_nodes(classdef_node):
            if type(node) is ast.FunctionDef:
                count = count + 1

        if count > self.rules['methods_per_class']:
            error = 'too many methods in class({} > {})'.format(
                count, self.rules['methods_per_class'])
            self.problems[classdef_node.lineno].add(error)

    def __check_max_arguments(self, funcdef_node):
        args_count = len(funcdef_node.args.args)
        if args_count > self.rules['max_arity']:
            error = 'too many arguments({} > {})'.format(
                args_count, self.rules['max_arity'])
            self.problems[funcdef_node.lineno].add(error)

    def __check_max_lines(self, node):
        logical_lines = len(node.body)
        if logical_lines > self.rules['max_lines_per_function']:
            error = 'method with too many lines ({} > {})'.format(
                logical_lines, self.rules['max_lines_per_function'])
            self.problems[node.lineno].add(error)


class LineErrorFinder:
    def __init__(self, rules):
        self.rules = rules
        self.problems = defaultdict(set)

    def get_errors(self, code):
        self.__iter_lines(code)
        return self.problems

    def __iter_lines(self, code):
        for line_num, line in enumerate(code.splitlines()):
            self.__make_checks(line_num + 1, line)

    def __make_checks(self, line_num, line):
        self.__check_line_length(line_num, line)

        if self.rules['forbid_semicolon']:
            self.__check_semicolons(line_num, line)

        if self.rules['forbid_trailing_whitespace']:
            self.__check_trailing_whitespace(line_num, line)

    def __check_line_length(self, line_num, line):
        length = len(line)
        if length > self.rules['line_length']:
            error = 'line too long ({} > {})'.format(
                length, self.rules['line_length'])
            self.problems[line_num].add(error)

    def __check_semicolons(self, line_num, line):
        pattern = r'(?<!\\)\"[^\"]*(?<!\\)\"|(?<!\\)\'[^\']*(?<!\\)\''
        line = re.sub(pattern, '', line)
        if ';' in line:
            error = 'multiple expressions on the same line'
            self.problems[line_num].add(error)

    def __check_trailing_whitespace(self, line_num, line):
        pattern = r'.*\s($|\n)'
        if re.match(pattern, line):
            error = 'trailing whitespace'
            self.problems[line_num].add(error)