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
import ast
import tokenize
from io import BytesIO
from collections import defaultdict


error = ['line too long ({} > {})', 'multiple expressions on the same line',
         'nesting too deep ({} > {})', 'indentation is {} instead of {}',
         'too many methods in class({} > {})', 'too many arguments({} > {})',
         'trailing whitespace', 'method with too many lines ({} > {})']


def max_length(code, line_length, result):
    for num, line in enumerate(code):
        if len(line) > line_length:
            result[num+1].add(error[0].format(len(line), line_length))


def multiple_expressions(code, result):
    for _, string, (line, _), _, _ in code:
        if string == ';':
            result[line].add(error[1])


def nesting(code, max_nesting, result):
    indent = 0
    for type, _, (line, _), _, _ in code:
        if type == tokenize.INDENT:
            indent = indent + 1
            continue
        if type == tokenize.DEDENT:
            indent = indent - 1
            if indent <= 0:
                return
        if indent > max_nesting:
            result[line].add(error[2].format(indent, max_nesting))


def indentation(code, indent, result):
    size = 0
    stack = []
    last = 0
    for type, value, (line, start), (_, end), _ in code:
        if value in {'(', '[', '{'}:
            stack.append(value)
        if value == ')' and stack[-1] == '(':
            stack.pop()
        if value == ']' and stack[-1] == '[':
            stack.pop()
        if value == '}' and stack[-1] == '{':
            stack.pop()
        if type == tokenize.INDENT:
            size = end - start
        elif type == tokenize.DEDENT:
            size = start
        elif all((size % indent, last in {4, 5, 6}, size == start,
                  not len(stack))):
            result[line].add(error[3].format(size, size // indent * indent))
        last = type


def class_methods(ast_object, max_methods, result):
    methods = 0
    for expression in ast_object.body:
        if isinstance(expression, ast.ClassDef):
            for class_element in expression.body:
                if isinstance(class_element, ast.FunctionDef):
                    methods = methods + 1
            if methods > max_methods:
                result[expression.lineno].add(error[4].format(methods,
                                                              max_methods))


def function_arguments(ast_object, arity, result):
    if isinstance(ast_object, (ast.FunctionDef, ast.Lambda)):
        f_args = ast_object.args
        arguments = len(f_args.args) + len(f_args.kwonlyargs)
        if f_args.kwarg is not None:
            arguments = arguments + 1
        if f_args.vararg is not None:
            arguments = arguments + 1
        if arguments > arity:
            result[ast_object.lineno].add(error[5].format(arguments, arity))
    for expression in ast_object.body:
        if isinstance(expression, (ast.ClassDef, ast.FunctionDef, ast.Lambda)):
            function_arguments(expression, arity, result)


def trailing_whitespace(code, result):
    for num, line in enumerate(code):
        if line[-1] in {' ', '\t'}:
            result[num+1].add(error[6])


def method_lines(code, start_line, max_lines, result):
    stack = []
    empty_lines = 0
    continuation_line = 0
    last_line = 0
    indent = 0
    for type, value, (line, _), _, string in code:
        if type == tokenize.INDENT:
            indent = indent + 1
            continue
        if type == tokenize.DEDENT:
            indent = indent - 1
            if indent <= 0:
                lines = line - start_line - 1 - empty_lines - continuation_line
                if lines > max_lines:
                    result[start_line].add(error[7].format(lines, max_lines))
        if string.isspace():
            empty_lines = empty_lines + 1
        if last_line != line and len(stack):
            continuation_line = continuation_line + 1
        if value in {'(', '[', '{'}:
            stack.append(value)
        if value == ')' and stack[-1] == '(':
            stack.pop()
        if value == ']' and stack[-1] == '[':
            stack.pop()
        if value == '}' and stack[-1] == '{':
            stack.pop()
        last_line = line


def critic(code, 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):
    code_lines = code.splitlines()
    tokenized = list(tokenize.tokenize(BytesIO(code.encode('utf-8')).readline))
    parsed_code = ast.parse(code)
    result = defaultdict(set)

    max_length(code_lines, line_length, result)
    if forbid_semicolons:
        multiple_expressions(tokenized, result)
    if max_nesting is not None:
        for num, (_, value, _, _, _) in enumerate(tokenized):
            if value == 'def':
                nesting(tokenized[num:], max_nesting, result)
    indentation(tokenized, indentation_size, result)
    if methods_per_class is not None:
        class_methods(parsed_code, methods_per_class, result)
    if max_arity is not None:
        function_arguments(parsed_code, max_arity, result)
    if forbid_trailing_whitespace:
        trailing_whitespace(code_lines, result)
    if max_lines_per_function is not None:
        for num, (_, value, (line, _), _, _) in enumerate(tokenized):
            if value == 'def':
                method_lines(tokenized[num:], line, max_lines_per_function,
                             result)

    return result

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

......EE...
======================================================================
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
IndexError: string index out of range

======================================================================
ERROR: test_no_issues (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
IndexError: string index out of range

----------------------------------------------------------------------
Ran 11 tests in 0.236s

FAILED (errors=2)

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

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


errors = ['line too long ({} > {})', 'multiple expressions on the same line',
          'nesting too deep ({} > {})', 'indentation is {} instead of {}',
          'too many methods in class({} > {})', 'too many arguments({} > {})',
          'trailing whitespace', 'method with too many lines ({} > {})']


def max_length(code, line_length, result):
    for num, line in enumerate(code):
        if len(line) > line_length:
            result[num+1].append(errors[0].format(len(line), line_length))


def multiple_expressions(code, result):         # TODO tyrsi i w komentari i nizowe
    for num, line in enumerate(code):
        if ';' in line:
            result[num+1].append(errors[1])


def nesting():                                  # TODO
    pass


def indentation(code, indent, result):          # TODO shitty
    count = 0
    for num, line in enumerate(code):
        for char in line:
            if char == ' ':
                count = count + 1
        if count % indent:
            pass


def class_methods(code, max_methods, result):
    methods = 0
    parsed_code = ast.parse(code)
    for expression in parsed_code.body:
        if isinstance(expression, ast.ClassDef):
            for class_element in expression.body:
                if isinstance(class_element, ast.FunctionDef):
                    methods = methods + 1
            if methods > max_methods:
                result[expression.lineno].append(errors[4].format(methods,
                                                                  max_methods))


def function_arguments(ast_object, arity, result):
    if isinstance(ast_object, (ast.FunctionDef, ast.Lambda)):
        f_args = ast_object.args
        arguments = len(f_args.args) + len(f_args.kwonlyargs)
        if f_args.kwarg is not None:
            arguments = arguments + 1
        if f_args.vararg is not None:
            arguments = arguments + 1
        if arguments > arity:
            result[ast_object.lineno].append(errors[5].format(arguments, arity))
    for expression in ast_object.body:
        if isinstance(expression, (ast.ClassDef, ast.FunctionDef, ast.Lambda)):
            function_arguments(expression, arity, result)


def trailing_whitespace(code, result):
    for num, line in enumerate(code):
        if line[-1] == ' ':
            result[num+1].append(errors[6])


def method_lines():
    pass


def clear_comments_and_strings(code):
    pass
    # re.sub(r'(?=[^\\])\'.*?\'', 'WTF', "a; b; \''c;' 'd'", 0, re.DOTALL)
    # ne raboti


def matcher(regex, string):
    match = re.search(regex, string)
    if match is None:
        return string
    start, end = match.span()
    return string[:start] + '(' + string[start:end] + ')' + string[end:]


def critic(code, 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):
    code_lines = code.splitlines()
    result = defaultdict(list)
    max_length(code_lines, line_length, result)
    if forbid_semicolons:
        multiple_expressions(code_lines, result)
    if max_nesting is not None:
        nesting()
    indentation(code_lines, indentation_size, result)
    if methods_per_class is not None:
        class_methods(code, methods_per_class, result)
    if max_arity is not None:
        function_arguments(ast.parse(code), max_arity, result)
    if forbid_trailing_whitespace:
        trailing_whitespace(code_lines, result)
    if max_lines_per_function is not None:
        method_lines()
    return result

Тодор обнови решението на 14.05.2016 23:33 (преди над 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
import ast
import tokenize
from io import BytesIO
from collections import defaultdict


error = ['line too long ({} > {})', 'multiple expressions on the same line',
         'nesting too deep ({} > {})', 'indentation is {} instead of {}',
         'too many methods in class({} > {})', 'too many arguments({} > {})',
         'trailing whitespace', 'method with too many lines ({} > {})']


def token(code):
    g = tokenize.tokenize(BytesIO(code.encode('utf-8')).readline)
    for toknum, tokval, start, end, line in g:
        if toknum == tokenize.INDENT:
            print ("Found indent")
        if tokval == ';':
            print ("Found semicolon")
        print (toknum, tokval, start, end, line)


def max_length(code, line_length, result):
    for num, line in enumerate(code):
        if len(line) > line_length:
            result[num+1].add(error[0].format(len(line), line_length))


def multiple_expressions(code, result):
    for _, string, (line, _), _, _ in code:
        if string == ';':
            result[line].add(error[1])


def nesting(code, max_nesting, result):
    indents = 0
    for type, _, (line, _), _, _ in code:
        if type == tokenize.INDENT:
            indents = indents + 1
        if type == tokenize.DEDENT:
            indents = indents - 1
        if indents > max_nesting:
            result[line].add(error[2].format(indents, max_nesting))


def indentation(code, indent, result):
    size = 0
    for (type, _, (line, start), (_, end), _) in code:
        if type == tokenize.INDENT:
            size = end - start
        elif type == tokenize.DEDENT:
            size = start
        elif size % indent and last_type in {4, 5, 6} and size == start:
            result[line].add(error[3].format(size, size // indent * indent))
        last_type = type


def class_methods(ast_object, max_methods, result):
    methods = 0
    for expression in ast_object.body:
        if isinstance(expression, ast.ClassDef):
            for class_element in expression.body:
                if isinstance(class_element, ast.FunctionDef):
                    methods = methods + 1
            if methods > max_methods:
                result[expression.lineno].add(error[4].format(methods,
                                                              max_methods))


def function_arguments(ast_object, arity, result):
    if isinstance(ast_object, (ast.FunctionDef, ast.Lambda)):
        f_args = ast_object.args
        arguments = len(f_args.args) + len(f_args.kwonlyargs)
        if f_args.kwarg is not None:
            arguments = arguments + 1
        if f_args.vararg is not None:
            arguments = arguments + 1
        if arguments > arity:
            result[ast_object.lineno].add(error[5].format(arguments, arity))
    for expression in ast_object.body:
        if isinstance(expression, (ast.ClassDef, ast.FunctionDef, ast.Lambda)):
            function_arguments(expression, arity, result)


def trailing_whitespace(code, result):
    for num, line in enumerate(code):
        if line[-1] in {' ', '\t'}:
            result[num+1].add(error[6])


def method_lines(code, max_lines, result):      # TODO
    pass


def tokenized(code):
    return tokenize.tokenize(BytesIO(code.encode('utf-8')).readline)


def critic(code, 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):
    code_lines = code.splitlines()
    parsed_code = ast.parse(code)
    result = defaultdict(set)

    max_length(code_lines, line_length, result)
    if forbid_semicolons:
        multiple_expressions(tokenized(code), result)
    if max_nesting is not None:
        nesting(tokenized(code), max_nesting, result)
    indentation(tokenized(code), indentation_size, result)
    if methods_per_class is not None:
        class_methods(parsed_code, methods_per_class, result)
    if max_arity is not None:
        function_arguments(parsed_code, max_arity, result)
    if forbid_trailing_whitespace:
        trailing_whitespace(code_lines, result)
    # if max_lines_per_function is not None:
    #     method_lines(tokenized(code), max_lines_per_function, result)

    return result

Тодор обнови решението на 17.05.2016 22:49 (преди над 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
import ast
import tokenize
from io import BytesIO
from collections import defaultdict


error = ['line too long ({} > {})', 'multiple expressions on the same line',
         'nesting too deep ({} > {})', 'indentation is {} instead of {}',
         'too many methods in class({} > {})', 'too many arguments({} > {})',
         'trailing whitespace', 'method with too many lines ({} > {})']


def max_length(code, line_length, result):
    for num, line in enumerate(code):
        if len(line) > line_length:
            result[num+1].add(error[0].format(len(line), line_length))


def multiple_expressions(code, result):
    for _, string, (line, _), _, _ in code:
        if string == ';':
            result[line].add(error[1])


def nesting(code, max_nesting, result):
    indent = 0
    for type, _, (line, _), _, _ in code:
        if type == tokenize.INDENT:
            indent = indent + 1
            continue
        if type == tokenize.DEDENT:
            indent = indent - 1
            if indent <= 0:
                return
        if indent > max_nesting:
            result[line].add(error[2].format(indent, max_nesting))


def indentation(code, indent, result):
    size = 0
    stack = []
    last = 0
    for type, value, (line, start), (_, end), _ in code:
        if value in {'(', '[', '{'}:
            stack.append(value)
        if value == ')' and stack[-1] == '(':
            stack.pop()
        if value == ']' and stack[-1] == '[':
            stack.pop()
        if value == '}' and stack[-1] == '{':
            stack.pop()
        if type == tokenize.INDENT:
            size = end - start
        elif type == tokenize.DEDENT:
            size = start
        elif all((size % indent, last in {4, 5, 6}, size == start,
                  not len(stack))):
            result[line].add(error[3].format(size, size // indent * indent))
        last = type


def class_methods(ast_object, max_methods, result):
    methods = 0
    for expression in ast_object.body:
        if isinstance(expression, ast.ClassDef):
            for class_element in expression.body:
                if isinstance(class_element, ast.FunctionDef):
                    methods = methods + 1
            if methods > max_methods:
                result[expression.lineno].add(error[4].format(methods,
                                                              max_methods))


def function_arguments(ast_object, arity, result):
    if isinstance(ast_object, (ast.FunctionDef, ast.Lambda)):
        f_args = ast_object.args
        arguments = len(f_args.args) + len(f_args.kwonlyargs)
        if f_args.kwarg is not None:
            arguments = arguments + 1
        if f_args.vararg is not None:
            arguments = arguments + 1
        if arguments > arity:
            result[ast_object.lineno].add(error[5].format(arguments, arity))
    for expression in ast_object.body:
        if isinstance(expression, (ast.ClassDef, ast.FunctionDef, ast.Lambda)):
            function_arguments(expression, arity, result)


def trailing_whitespace(code, result):
    for num, line in enumerate(code):
        if line[-1] in {' ', '\t'}:
            result[num+1].add(error[6])


def method_lines(code, start_line, max_lines, result):
    stack = []
    empty_lines = 0
    continuation_line = 0
    last_line = 0
    indent = 0
    for type, value, (line, _), _, string in code:
        if type == tokenize.INDENT:
            indent = indent + 1
            continue
        if type == tokenize.DEDENT:
            indent = indent - 1
            if indent <= 0:
                lines = line - start_line - 1 - empty_lines - continuation_line
                if lines > max_lines:
                    result[start_line].add(error[7].format(lines, max_lines))
        if string.isspace():
            empty_lines = empty_lines + 1
        if last_line != line and len(stack):
            continuation_line = continuation_line + 1
        if value in {'(', '[', '{'}:
            stack.append(value)
        if value == ')' and stack[-1] == '(':
            stack.pop()
        if value == ']' and stack[-1] == '[':
            stack.pop()
        if value == '}' and stack[-1] == '{':
            stack.pop()
        last_line = line


def critic(code, 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):
    code_lines = code.splitlines()
    tokenized = list(tokenize.tokenize(BytesIO(code.encode('utf-8')).readline))
    parsed_code = ast.parse(code)
    result = defaultdict(set)

    max_length(code_lines, line_length, result)
    if forbid_semicolons:
        multiple_expressions(tokenized, result)
    if max_nesting is not None:
        for num, (_, value, _, _, _) in enumerate(tokenized):
            if value == 'def':
                nesting(tokenized[num:], max_nesting, result)
    indentation(tokenized, indentation_size, result)
    if methods_per_class is not None:
        class_methods(parsed_code, methods_per_class, result)
    if max_arity is not None:
        function_arguments(parsed_code, max_arity, result)
    if forbid_trailing_whitespace:
        trailing_whitespace(code_lines, result)
    if max_lines_per_function is not None:
        for num, (_, value, (line, _), _, _) in enumerate(tokenized):
            if value == 'def':
                method_lines(tokenized[num:], line, max_lines_per_function,
                             result)

    return result