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
from re import search
from ast import parse, ClassDef, FunctionDef, For, While, If
from collections import defaultdict
from functools import reduce
from copy import copy

LINE_LENGTH_KEY = "line_length"
FORBID_SEMICOLONS_KEY = "forbid_semicolons"
MAX_NESTING_KEY = "max_nesting"
IDENTATION_SIZE_KEY = "indentation_size"
METHODS_PER_CLASS_KEY = "methods_per_class"
MAX_ARITY_KEY = "max_arity"
FORBID_TRAILING_SPACE_KEY = "forbid_trailing_whitespace"
MAX_LINES_PER_FUNCTION_KEY = "max_lines_per_function"

KEYS = [LINE_LENGTH_KEY, FORBID_SEMICOLONS_KEY, MAX_NESTING_KEY,
        IDENTATION_SIZE_KEY, METHODS_PER_CLASS_KEY, MAX_ARITY_KEY,
        FORBID_TRAILING_SPACE_KEY, MAX_LINES_PER_FUNCTION_KEY]

LINE_LENGTH_DEFAULT = 79
FORBID_SEMICOLONS_DEFAULT = True
MAX_NESTING_DEFAULT = None
IDENTATION_SIZE_DEFAULT = 4
METHODS_PER_CLASS_DEFAULT = None
MAX_ARITY_DEFAULT = None
FORBID_TRAILING_SPACE_DEFAULT = True
MAX_LINES_PER_FUNCTION_DEFAULT = None

DEFAULTS = [LINE_LENGTH_DEFAULT, FORBID_SEMICOLONS_DEFAULT,
            MAX_NESTING_DEFAULT, IDENTATION_SIZE_DEFAULT,
            METHODS_PER_CLASS_DEFAULT, MAX_ARITY_DEFAULT,
            FORBID_TRAILING_SPACE_DEFAULT, MAX_LINES_PER_FUNCTION_DEFAULT]

LINE_TOO_LONG_FORMAT = "line too long (%d > %d)"
MULTIPLE_EXP_MSG = "multiple expressions on the same line"
NEST_TOO_DEEP_FORMAT = "nesting too deep (%d > %d)"
WRONG_INDENT_FORMAT = "indentation is %d instead of %d"
TOO_MANY_METHODS_FORMAT = "too many methods in class(%d > %d)"
TOO_MANY_ARGS_FORMAT = "too many arguments(%d > %d)"
TRAILING_SPACE_MSG = "trailing whitespace"
TOO_MANY_LINES_FORMAT = "method with too many lines (%d > %d)"


def setup_rules(rules):
    for key, default in zip(KEYS, DEFAULTS):
        rules[key] = rules.get(key, default)


def add_ast_errors_recursive(lineno, depth, statements, errors, rules):
    for expression in statements:
        if lineno == expression.lineno and rules[FORBID_SEMICOLONS_KEY]:
            errors[lineno].add(MULTIPLE_EXP_MSG)
        lineno = expression.lineno
        if (isinstance(expression, ClassDef) and
           rules[METHODS_PER_CLASS_KEY] != METHODS_PER_CLASS_DEFAULT):
            funcs_count = reduce(lambda count, expr:
                                 count + 1
                                 if isinstance(expr, FunctionDef)
                                 else count,
                                 expression.body, 0)
            if funcs_count > rules[METHODS_PER_CLASS_KEY]:
                errors[lineno].add(TOO_MANY_METHODS_FORMAT %
                                   (funcs_count, rules[METHODS_PER_CLASS_KEY]))

        if isinstance(expression, FunctionDef):
            lastBody = expression.body[-1]
            while isinstance(lastBody, (For, While, If)):
                lastBody = lastBody.body[-1]
            lastLine = lastBody.lineno
            func_length = lastLine - lineno
            if (rules[MAX_LINES_PER_FUNCTION_KEY] and
               func_length > rules[MAX_LINES_PER_FUNCTION_KEY]):
                errors[lineno].add(TOO_MANY_LINES_FORMAT %
                                   (func_length,
                                    rules[MAX_LINES_PER_FUNCTION_KEY]))
            args_count = len(expression.args.args)
            if (rules[MAX_ARITY_KEY] != MAX_ARITY_DEFAULT and
               args_count > rules[MAX_ARITY_KEY]):
                errors[lineno].add(TOO_MANY_ARGS_FORMAT %
                                   (args_count, rules[MAX_ARITY_KEY]))
        if hasattr(expression, "body"):
            add_ast_errors_recursive(lineno,
                                     depth+1,
                                     expression.body,
                                     errors,
                                     rules)
        if (rules[MAX_NESTING_KEY] != MAX_NESTING_DEFAULT and
           depth > rules[MAX_NESTING_KEY]):
            errors[lineno].add(NEST_TOO_DEEP_FORMAT %
                               (depth, rules[MAX_NESTING_KEY]))


def critic(code, **rules):
    setup_rules(rules)
    errors = defaultdict(set)
    try:
        module = parse(code)
        statements = module.body
        add_ast_errors_recursive(0, 0, statements, errors, rules)

        lineno = 0
        for line in code.split("\n"):
            lineno += 1
            line_length = len(line)
            if line_length > rules[LINE_LENGTH_KEY]:
                errors[lineno].add(
                    LINE_TOO_LONG_FORMAT %
                    (line_length, rules[LINE_LENGTH_KEY])
                )
            ident_match = search("^\s+", line)
            ident = len(ident_match.group(0)) if ident_match else 0
            if ident % rules[IDENTATION_SIZE_KEY]:
                errors[lineno].add(WRONG_INDENT_FORMAT %
                                   (ident, rules[IDENTATION_SIZE_KEY]))
            trail_space_match = search("\s$", line)
            if trail_space_match and rules[FORBID_TRAILING_SPACE_KEY]:
                errors[lineno].add(TRAILING_SPACE_MSG)
    except IndentationError as ex:
        match = search('(.*) \(<unknown>, line (\d+)\)', str(ex))
        if match:
            errors[match.group(2)] = match.group(1)
    except SyntaxError:
        errors['0'] = {"Syntax error"}
    finally:
        return errors

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

......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 (14 > 5)'

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

FAILED (failures=1)

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

Димитър обнови решението на 15.05.2016 15: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
from re import search
from ast import parse, ClassDef, FunctionDef, For, While, If
from collections import defaultdict
from functools import reduce
from copy import copy

LINE_LENGTH_KEY = "line_length"
FORBID_SEMICOLONS_KEY = "forbid_semicolons"
MAX_NESTING_KEY = "max_nesting"
IDENTATION_SIZE_KEY = "indentation_size"
METHODS_PER_CLASS_KEY = "methods_per_class"
MAX_ARITY_KEY = "max_arity"
FORBID_TRAILING_SPACE_KEY = "forbid_trailing_whitespace"
MAX_LINES_PER_FUNCTION_KEY = "max_lines_per_function"

KEYS = [LINE_LENGTH_KEY, FORBID_SEMICOLONS_KEY, MAX_NESTING_KEY,
        IDENTATION_SIZE_KEY, METHODS_PER_CLASS_KEY, MAX_ARITY_KEY,
        FORBID_TRAILING_SPACE_KEY, MAX_LINES_PER_FUNCTION_KEY]

LINE_LENGTH_DEFAULT = 79
FORBID_SEMICOLONS_DEFAULT = True
MAX_NESTING_DEFAULT = None
IDENTATION_SIZE_DEFAULT = 4
METHODS_PER_CLASS_DEFAULT = None
MAX_ARITY_DEFAULT = None
FORBID_TRAILING_SPACE_DEFAULT = True
MAX_LINES_PER_FUNCTION_DEFAULT = None

DEFAULTS = [LINE_LENGTH_DEFAULT, FORBID_SEMICOLONS_DEFAULT,
            MAX_NESTING_DEFAULT, IDENTATION_SIZE_DEFAULT,
            METHODS_PER_CLASS_DEFAULT, MAX_ARITY_DEFAULT,
            FORBID_TRAILING_SPACE_DEFAULT, MAX_LINES_PER_FUNCTION_DEFAULT]

LINE_TOO_LONG_FORMAT = "line too long (%d > %d)"
MULTIPLE_EXP_MSG = "multiple expressions on the same line"
NEST_TOO_DEEP_FORMAT = "nesting too deep (%d > %d)"
WRONG_INDENT_FORMAT = "indentation is %d instead of %d"
TOO_MANY_METHODS_FORMAT = "too many methods in class(%d > %d)"
TOO_MANY_ARGS_FORMAT = "too many arguments(%d > %d)"
TRAILING_SPACE_MSG = "trailing whitespace"
TOO_MANY_LINES_FORMAT = "method with too many lines (%d > %d)"


def setup_rules(rules):
    for key, default in zip(KEYS, DEFAULTS):
        rules[key] = rules.get(key, default)


def add_ast_errors_recursive(lineno, depth, statements, errors, rules):
    for expression in statements:
        if lineno == expression.lineno and rules[FORBID_SEMICOLONS_KEY]:
            errors[lineno].add(MULTIPLE_EXP_MSG)
        lineno = expression.lineno
        if (isinstance(expression, ClassDef) and
           rules[METHODS_PER_CLASS_KEY] != METHODS_PER_CLASS_DEFAULT):
            funcs_count = reduce(lambda count, expr:
                                 count + 1
                                 if isinstance(expr, FunctionDef)
                                 else count,
                                 expression.body, 0)
            if funcs_count > rules[METHODS_PER_CLASS_KEY]:
                errors[lineno].add(TOO_MANY_METHODS_FORMAT %
                                   (funcs_count, rules[METHODS_PER_CLASS_KEY]))

        if isinstance(expression, FunctionDef):
            lastBody = expression.body[-1]
            while isinstance(lastBody, (For, While, If)):
                lastBody = lastBody.body[-1]
            lastLine = lastBody.lineno
            func_length = lastLine - lineno
            if (rules[MAX_LINES_PER_FUNCTION_KEY] and
               func_length > rules[MAX_LINES_PER_FUNCTION_KEY]):
                errors[lineno].add(TOO_MANY_LINES_FORMAT %
                                   (func_length,
                                    rules[MAX_LINES_PER_FUNCTION_KEY]))
            args_count = len(expression.args.args)
            if (rules[MAX_ARITY_KEY] != MAX_ARITY_DEFAULT and
               args_count > rules[MAX_ARITY_KEY]):
                errors[lineno].add(TOO_MANY_ARGS_FORMAT %
                                   (args_count, rules[MAX_ARITY_KEY]))
        if hasattr(expression, "body"):
            add_ast_errors_recursive(lineno,
                                     depth+1,
                                     expression.body,
                                     errors,
                                     rules)
        if (rules[MAX_NESTING_KEY] != MAX_NESTING_DEFAULT and
           depth > rules[MAX_NESTING_KEY]):
            errors[lineno].add(NEST_TOO_DEEP_FORMAT %
                               (depth, rules[MAX_NESTING_KEY]))


def critic(code, **rules):
    setup_rules(rules)
    errors = defaultdict(set)
    try:
        module = parse(code)
        statements = module.body
        add_ast_errors_recursive(0, 0, statements, errors, rules)

        lineno = 0
        for line in code.split("\n"):
            lineno += 1
            line_length = len(line)
            if line_length > rules[LINE_LENGTH_KEY]:
                errors[lineno].add(
                    LINE_TOO_LONG_FORMAT %
                    (line_length, rules[LINE_LENGTH_KEY])
                )
            ident_match = search("^\s+", line)
            ident = len(ident_match.group(0)) if ident_match else 0
            if ident % rules[IDENTATION_SIZE_KEY]:
                errors[lineno].add(WRONG_INDENT_FORMAT %
                                   (ident, rules[IDENTATION_SIZE_KEY]))
            trail_space_match = search("\s$", line)
            if trail_space_match and rules[FORBID_TRAILING_SPACE_KEY]:
                errors[lineno].add(TRAILING_SPACE_MSG)
    except IndentationError as ex:
        match = search('(.*) \(<unknown>, line (\d+)\)', str(ex))
        if match:
            errors[match.group(2)] = match.group(1)
    except SyntaxError:
        errors['0'] = {"Syntax error"}
    finally:
        return errors