timeit

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

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

Решение на Статичен анализ на python код от Данаил Димитров

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

Към профила на Данаил Димитров

Резултати

  • 5 точки от тестове
  • 0 бонус точки
  • 5 точки общо
  • 5 успешни тест(а)
  • 6 неуспешни тест(а)

Код

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
import re
import ast
from functools import partial
from itertools import groupby

"""
Dark magic here lacks ...
"""

ALL_NODE = type('NodeAll', (), {})
CLASS_NODE = ast.ClassDef
FUNC_NODE = ast.FunctionDef


class Restriction:
    def __init__(self, name, checker, node_type=None,
                 error='', value=None, dep=None):
        self.name = name
        self.node_type = node_type
        self.error_msg = error
        self.value = value
        self.dep = dep
        self.checker = checker

    def update_value(self, val):
        self.value = val

    def _get_error(self, cur_val=None, lineno=None):
        if cur_val:
            result = self.error_msg % (cur_val, self.value)
        else:
            result = self.error_msg
        if lineno:
            result = {lineno: result}

        return result

    def check_rule(self, item=None, lineno=None):
        not_proper_node = self.node_type and \
                          (self.node_type == ALL_NODE and
                           isinstance(item, self.node_type))
        if not self.value or not_proper_node:
            return

        if self.value is not True:
            check = partial(self.checker, item, self.value)
        else:
            check = partial(self.checker, item)

        if self.dep:
            deps = [RULES[rule].value for rule in self.dep]
            check = partial(check, *deps)

        err_val = check()

        if not lineno and item and hasattr(item, 'lineno'):
            lineno = item.lineno

        if not err_val:
            return
        elif err_val is True:
            return self._get_error(None, lineno)
        else:
            return self._get_error(err_val, lineno)


def regex_matcher(pattern, line):
    return bool(re.match(pattern, line))


def max_nesting(node, nesting, indent):
    # works if
    indentation_size = node.col_offset / indent
    return indentation_size if indentation_size > nesting else False


def methods_per_class(node, val):
    cls_funcs = [func for func in node.body if
                 isinstance(func, ast.FunctionDef)]
    func_count = len(cls_funcs)
    return func_count if func_count > val else False


def max_arity(node, val):
    args_count = len(node.args.args)
    return args_count if args_count > val else False


def indentation(line, val):
    # XXX needs the nesting lvl
    indent = re.match(r'\s*', line)
    if indent:
        indent = len(indent.group()) % val
        indent = indent + val if indent > 0 else False
    else:
        indent = False

    return indent


def func_lines(node, val):
    lines = node.body[-1].col_offset - node.col_offset
    return lines if lines > val else False


RULES = {
    'methods_per_class': Restriction('methods_per_class',
                                     methods_per_class,
                                     CLASS_NODE,
                                     'too many methods in class(%d > %d)',
                                     None),
    'max_nesting': Restriction('max_nesting',
                               max_nesting,
                               ALL_NODE,
                               'nesting too deep (%d > %d)',
                               None,
                               dep=['indentation_size']),
    'max_arity': Restriction('max_arity',
                             max_arity,
                             FUNC_NODE,
                             'too many arguments(%d > %d)',
                             None),
    'indentation_size': Restriction('indentation_size',
                                    indentation,
                                    ALL_NODE,
                                    'indentation is %d instead of %d',
                                    4),
    'max_lines_per_function': Restriction('max_lines_per_function',
                                          func_lines,
                                          FUNC_NODE,
                                          'method with too many lines (%d > '
                                          '%d)',
                                          None),
    'line_length': Restriction(name='line_length',
                               error='line too long (%d > %d)',
                               checker=lambda line, val: len(line) if len(
                                   line) > val else False,
                               value=79),
    'forbid_semicolons': Restriction(name='forbid_semicolons',
                                     error='multiple expressions on the same '
                                           'line',
                                     checker=partial(regex_matcher, r".*;.+"),
                                     value=True),
    'forbid_trailing_whitespace': Restriction(name='forbid_trailing_whitespace',
                                              error='trailing whitespace',
                                              checker=partial(regex_matcher,
                                                              r"\s+\n$"),
                                              value=True)
}

LINE_RULES = ["line_length",
              "forbid_semicolons",
              "indentation_size",
              "forbid_trailing_whitespace"
              ]

NODE_RULES = ["max_nesting",
              "methods_per_class",
              "max_arity",
              "max_lines_per_function"]


def find_line_errors(code, rules):
    line_errs = []
    for lineno, line in enumerate(code.split('\n')):
        line_errs.extend(
            [RULES[rule].check_rule(item=line, lineno=lineno + 1) for rule in
             rules])
    return line_errs


def find_node_errors(tree, node_rules):
    # walk the whole tree
    errs = []
    node_iterator = ast.walk(tree)
    next(node_iterator)  # skip the module node
    for node in node_iterator:
        if not hasattr(node, 'lineno'):
            continue
        errs.extend([RULES[rule].check_rule(node) for rule in node_rules])
    return errs


def critic(code, **rules):
    # apply the new values
    for key, value in rules.items():
        RULES[key].update_value(value)

    def set_rules(rule):
        return RULES[rule].value

    node_rules = list(filter(set_rules, NODE_RULES))
    line_rules = list(filter(set_rules, LINE_RULES))
    node_errs = (find_node_errors(ast.parse(code), node_rules))
    line_errs = (find_line_errors(code, line_rules))

    # sort by line number
    def get_lineno(err):
        return list(err.keys())[0]

    errors = list(filter(None, node_errs + line_errs))

    errors.sort(key=get_lineno)

    errors = groupby(errors, key=get_lineno)

    result = {}
    for lineno, errs in errors:
        # extract results from groupby
        result[lineno] = [err[lineno] for err in errs]

    return result

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

FF...EE.FE.
======================================================================
ERROR: 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
AttributeError: 'Assign' object has no attribute 'body'

======================================================================
ERROR: 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
AttributeError: 'If' object has no attribute 'args'

======================================================================
ERROR: test_too_many_arguments (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
AttributeError: 'Pass' object has no attribute 'args'

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

======================================================================
FAIL: test_forbid_trailing_whitespace (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_too_deep_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 != 1

----------------------------------------------------------------------
Ran 11 tests in 0.110s

FAILED (failures=3, errors=3)

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

Данаил обнови решението на 18.05.2016 09:23 (преди над 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
import re
import ast
from functools import partial
from itertools import groupby

"""
Dark magic here lacks ...
"""


class Restriction:

    def __init__(self, name, checker, node_type=None,
                 error='', value=None, dep=None):
        self.name = name
        self.node_type = node_type
        self.error_msg = error
        self.value = value
        self.dep = dep
        self.checker = checker

    def update_value(self, val):
        self.value = val

    def _get_error(self, cur_val=None, lineno=None):
        if cur_val:
            result = self.error_msg % (cur_val, self.value)
        else:
            result = self.error_msg
        if lineno:
            result = {lineno: result}

        return result

    def check_rule(self, item=None, line=None):
        print(self.node_type)
        not_proper_node = self.node_type and \
                          (isinstance(item, self.node_type) and
                          self.node_type == ALL_NODE)
        if not self.value or not_proper_node:
            return

        print(self.value)

        if self.value is not True:
            check = partial(self.checker, item, self.value)
        else:
            check = partial(self.checker, item)
        
        if self.dep:
            deps = [RULES[rule] for rule in self.dep]
            check = partial(check, *deps)

        err_val = check()

        if not line and item and item.lineno:
            line = item.lineno

        if not err_val:
            return
        elif err_val is True:
            return self._get_error(None, line)
        else:
            return self._get_error(err_val, line)


def regex_matcher(pattern, line):
    return re.match(pattern, line)


def max_nesting(node, nesting, indent):
    # works if
    print(node, nesting, indent)
    return node.col_offset // indent > nesting


def methods_per_class(node, val):
    cls_funcs = [func for func in node.body if isinstance(func, ast.Function)]
    func_count = len(cls_funcs)
    return func_count if func_count > val else False


def max_arity(node, val):
    args_count = len(node.args.args)
    return args_count if args_count > val else False


def indentation(node, val):
    # XXX needs the nesting lvl
    indent = node.col_offset % val
    return indent if indent > 0 else False


def func_lines(node, val):
    lines = node.body[-1].col_offset - node.col_offset
    return lines if lines > val else False


ALL_NODE = 'all'
CLASS_NODE = ast.ClassDef
FUNC_NODE = ast.FunctionDef


RULES = {
    'methods_per_class': Restriction('methods_per_class',
                                     methods_per_class,
                                     CLASS_NODE,
                                     'too many methods in class(%d > %d)',
                                     None),
    'max_nesting': Restriction('max_nesting',
                               max_nesting,
                               ALL_NODE,
                               'nesting too deep (%d > %d)',
                               None,
                               dep=['indentation_size']),
    'max_arity': Restriction('max_arity',
                             max_arity,
                             FUNC_NODE,
                             'too many arguments(%d > %d)',
                             None),
    'indentation_size': Restriction('indentation_size',
                                    indentation,
                                    ALL_NODE,
                                    'indentation is %d instead of %d',
                                    4),
    'max_lines_per_function': Restriction('max_lines_per_function',
                                          func_lines,
                                          FUNC_NODE,
                                          'method with too many lines (%d > %d)',
                                          None),
    'line_length': Restriction(name='line_length',
                               error='line too long (%d > %d)',
                               checker=lambda line, val: len(line) if len(line) < val else False,
                               value=79),
    'forbid_semicolons': Restriction(name='forbid_semicolons',
                                     error='multiple expressions on the same line',
                                     checker=partial(regex_matcher, r".*;.+"),
                                     value=True),
    'forbid_trailing_whitespace': Restriction(name='forbid_trailing_whitespace',
                                              error='trailing whitespace',
                                              checker=partial(regex_matcher, r"\s+\n$"),
                                              value=True)
}

LINE_RULES = ["line_length",
              "forbid_semicolons",
              "indentation_size"]

NODE_RULES = ["max_nesting",
              "methods_per_class",
              "max_arity",
              "forbid_trailing_whitespace",
              "max_lines_per_function"]


def find_line_errors(code, rules):
    errs = {}
    for lineno, line in enumerate(code.split('\n')):
        line_errs = [RULES[rule].check_rule(line=line) for rule in rules]
        if line_errs:
            errs[lineno] = line_errs
    return errs


def find_node_errors(tree, node_rules):
    # walk the whole tree
    errs = []
    node_iterator = ast.walk(tree)
    next(node_iterator)  # skip the module node
    for node in node_iterator:
        errs.extend([RULES[rule].check_rule(node) for rule in node_rules])
    return errs


def critic(code, **rules):
    # apply the new values
    for key, value in rules.items():
        RULES[key].update_value(value)

    errors = []

    def set_rules(rule):
        return RULES[rule].value

    node_rules = list(filter(set_rules, NODE_RULES))
    line_rules = list(filter(set_rules, LINE_RULES))
    errors.extend(find_node_errors(ast.parse(code), node_rules))
    errors.extend(find_line_errors(code, line_rules))

    # sort by line number
    def get_lineno(err):
        return next(iter(err))

    errors = list(filter(None, errors))
    errors.sort(key=get_lineno)
    errors = groupby(errors, key=get_lineno)
    # TODO clear Nones

    result = {}
    for lineno, err in errors:
        print(errors)
        # extract results from groupby
        result[lineno] = list(err)[1]

    return result

Данаил обнови решението на 18.05.2016 15:50 (преди над 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
import re
import ast
from functools import partial
from itertools import groupby

"""
Dark magic here lacks ...
"""

ALL_NODE = type('NodeAll', (), {})
CLASS_NODE = ast.ClassDef
FUNC_NODE = ast.FunctionDef


class Restriction:
    def __init__(self, name, checker, node_type=None,
                 error='', value=None, dep=None):
        self.name = name
        self.node_type = node_type
        self.error_msg = error
        self.value = value
        self.dep = dep
        self.checker = checker

    def update_value(self, val):
        self.value = val

    def _get_error(self, cur_val=None, lineno=None):
        if cur_val:
            result = self.error_msg % (cur_val, self.value)
        else:
            result = self.error_msg
        if lineno:
            result = {lineno: result}

        return result

    def check_rule(self, item=None, lineno=None):
        not_proper_node = self.node_type and \
                          (self.node_type == ALL_NODE and
                           isinstance(item, self.node_type))
        if not self.value or not_proper_node:
            return

        if self.value is not True:
            check = partial(self.checker, item, self.value)
        else:
            check = partial(self.checker, item)

        if self.dep:
            deps = [RULES[rule].value for rule in self.dep]
            check = partial(check, *deps)

        err_val = check()

        if not lineno and item and hasattr(item, 'lineno'):
            lineno = item.lineno

        if not err_val:
            return
        elif err_val is True:
            return self._get_error(None, lineno)
        else:
            return self._get_error(err_val, lineno)


def regex_matcher(pattern, line):
    return bool(re.match(pattern, line))


def max_nesting(node, nesting, indent):
    # works if
    indentation_size = node.col_offset / indent
    return indentation_size if indentation_size > nesting else False


def methods_per_class(node, val):
    cls_funcs = [func for func in node.body if
                 isinstance(func, ast.FunctionDef)]
    func_count = len(cls_funcs)
    return func_count if func_count > val else False


def max_arity(node, val):
    args_count = len(node.args.args)
    return args_count if args_count > val else False


def indentation(line, val):
    # XXX needs the nesting lvl
    indent = re.match(r'\s*', line)
    if indent:
        indent = len(indent.group()) % val
        indent = indent + val if indent > 0 else False
    else:
        indent = False

    return indent


def func_lines(node, val):
    lines = node.body[-1].col_offset - node.col_offset
    return lines if lines > val else False


RULES = {
    'methods_per_class': Restriction('methods_per_class',
                                     methods_per_class,
                                     CLASS_NODE,
                                     'too many methods in class(%d > %d)',
                                     None),
    'max_nesting': Restriction('max_nesting',
                               max_nesting,
                               ALL_NODE,
                               'nesting too deep (%d > %d)',
                               None,
                               dep=['indentation_size']),
    'max_arity': Restriction('max_arity',
                             max_arity,
                             FUNC_NODE,
                             'too many arguments(%d > %d)',
                             None),
    'indentation_size': Restriction('indentation_size',
                                    indentation,
                                    ALL_NODE,
                                    'indentation is %d instead of %d',
                                    4),
    'max_lines_per_function': Restriction('max_lines_per_function',
                                          func_lines,
                                          FUNC_NODE,
                                          'method with too many lines (%d > '
                                          '%d)',
                                          None),
    'line_length': Restriction(name='line_length',
                               error='line too long (%d > %d)',
                               checker=lambda line, val: len(line) if len(
                                   line) > val else False,
                               value=79),
    'forbid_semicolons': Restriction(name='forbid_semicolons',
                                     error='multiple expressions on the same '
                                           'line',
                                     checker=partial(regex_matcher, r".*;.+"),
                                     value=True),
    'forbid_trailing_whitespace': Restriction(name='forbid_trailing_whitespace',
                                              error='trailing whitespace',
                                              checker=partial(regex_matcher,
                                                              r"\s+\n$"),
                                              value=True)
}

LINE_RULES = ["line_length",
              "forbid_semicolons",
              "indentation_size",
              "forbid_trailing_whitespace"
              ]

NODE_RULES = ["max_nesting",
              "methods_per_class",
              "max_arity",
              "max_lines_per_function"]


def find_line_errors(code, rules):
    line_errs = []
    for lineno, line in enumerate(code.split('\n')):
        line_errs.extend(
            [RULES[rule].check_rule(item=line, lineno=lineno + 1) for rule in
             rules])
    return line_errs


def find_node_errors(tree, node_rules):
    # walk the whole tree
    errs = []
    node_iterator = ast.walk(tree)
    next(node_iterator)  # skip the module node
    for node in node_iterator:
        if not hasattr(node, 'lineno'):
            continue
        errs.extend([RULES[rule].check_rule(node) for rule in node_rules])
    return errs


def critic(code, **rules):
    # apply the new values
    for key, value in rules.items():
        RULES[key].update_value(value)

    def set_rules(rule):
        return RULES[rule].value

    node_rules = list(filter(set_rules, NODE_RULES))
    line_rules = list(filter(set_rules, LINE_RULES))
    node_errs = (find_node_errors(ast.parse(code), node_rules))
    line_errs = (find_line_errors(code, line_rules))

    # sort by line number
    def get_lineno(err):
        return list(err.keys())[0]

    errors = list(filter(None, node_errs + line_errs))

    errors.sort(key=get_lineno)

    errors = groupby(errors, key=get_lineno)

    result = {}
    for lineno, errs in errors:
        # extract results from groupby
        result[lineno] = [err[lineno] for err in errs]

    return result