timeit

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

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

Стат трек

Краен срок
14.04.2016 23:30

Срокът за предаване на решения е отминал

Въпреки, че имате домашно, се опасяваме, че скучаете. Затова ви даваме предизвикателство.

Задачата е да напишете функция def stats(path_to_directory), която връща следния речник:

{
    'classes': брой класове,  # int (default 0)
    'functions': брой функции,  # int (default 0)
    'unpleasant_functions': ['[path/to/file.py]#[function_name]', ...]  # list (default [])
}

Представляващ статистика, касаеща всички .py файлове намиращи се в зададената директория.

Неприятна функция наричаме функция с 3 или повече нива на влагане вътре в нея. За ниво на влагане се броят:

  • if / else
  • while
  • for
  • with
  • try/except

Например функцията:

def unpleasant_one():
    for x in ["smiling", "girl"]:
        for y in ["cool", "long beard", "boy"]:
            if x == 'girl' and y == 'long beard':
                print("А {} {}!".format(y, x))

Е "неприятна" понеже има 3 нива на влагане в себе си.

NOTE:

  • Няма да имате функции, дефинирани в тялото на функции
  • Методите на класовете ги разглеждаме като функции
  • Ако path_to_directory не е валиден, хвърляйте NotADirectoryError
  • Ако имаме:
    • path_to_directory=/baba
    • вътре в baba директория baba
    • във вътрешната baba файл baba.py
    • в baba.py, неприятна фунцкия с име "baba"

то стринга "/baba/baba/baba.py#baba", трябва да бъде в unpleasant_functions.

Решения

Теодор Тошков
  • Некоректно
  • 4 успешни тест(а)
  • 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
import os
import re


small_pattern = r'\bdef\b.*:\n\s{12}'
if_pattern = small_pattern + r'if'
else_pattern = small_pattern + r'else'
while_pattern = small_pattern + r'while'
for_pattern = small_pattern + r'for'
with_pattern = small_pattern + r'with'
except_pattern = small_pattern + r'except'
try_pattern = small_pattern + r'try'
big_pattern = while_pattern + r'|' + else_pattern + r'|' + \
              with_pattern + r'|' + try_pattern + r'|' + \
              for_pattern + r'|' + if_pattern + r'|' + except_pattern


def stats(path_to_directory):
    if not os.path.exists(path_to_directory):
        raise NotADirectoryError
    curr_dir = os.getcwd()
    os.chdir(path_to_directory)
    result = {'classes': 0, 'functions': 0, 'unpleasant_functions': []}
    for file in os.listdir():
        if file.endswith(".py"):
            current = open(file)
            source = current.read()
            result['classes'] += len(re.findall(r'\bclass\b', source))
            result['functions'] += len(re.findall(r'\bdef\b', source))
            while source.rfind("def") >= 0:
                matching = re.findall(big_pattern, source, re.DOTALL)
                if matching == []:
                    break
                source = matching[0]
                index = source.rfind("def")
                remove = source[index:]
                source = source[:index]
                add_to_dict = f_name(remove)
                result['unpleasant_functions'].append(
                    os.getcwd() + '/' + file + "#" + add_to_dict)
            current.close()
    os.chdir(curr_dir)
    return result


def f_name(unpleasant_function):
    i = 4
    name = ""
    while unpleasant_function[i] != '(':
        name += unpleasant_function[i]
        i += 1
    return name
..F..FF
======================================================================
FAIL: test_catches_unpleasant_functions (test.TestAst)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
AssertionError: False is not true

======================================================================
FAIL: test_dont_read_code_from_docstring (test.TestAst)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
AssertionError: 3 != 2

======================================================================
FAIL: test_unpleasant_functions_count_correctness (test.TestAst)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
AssertionError: 6 != 0

----------------------------------------------------------------------
Ran 7 tests in 0.087s

FAILED (failures=3)
Данаил Димитров
  • Некоректно
  • 0 успешни тест(а)
  • 0 неуспешни тест(а)
Данаил Димитров
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
import re
import ast
from functools import partial

BAD_LEVEL = 3
ast_type = (ast.For, ast.If, ast.While, ast.With, ast.Try, ast.Else, ast.Except)

def is_bad_function(node):
    def is_bad_r(node, level):
        if level == BAD_LEVEL:
            return True
        if not hasattr(node, 'body'):
            return False
        level_items = (item for item in node.body if type(item) in ast_type)
        return any(map(partial(is_bad_r, level=level+1), level_items))
    return is_bad_r(node, 0)

# Mutates the result
def process_module(module_node, path, result):
     for sub_node in module_node.body:
        if sub_node == ast.ClassDef:
            result['classes'] += 1
        elif sub_node == ast.FunctionDef:
            result['functions'] += 1
            if is_bad_function(sub_node):
                function_entry = '[{}#{}]'.format(path, sub_node.name)
                result['unpleasant_functions'].append(function_entry)

def stats(path):
    result = {
            'classes': 0,
            'functions': 0,
            'unpleasant_functions': []
            }
    process_module(ast.parse(module), path, result)
    fnames = os.listdir(path)
    for file in (fname for fname in fnames if re.match('.*\.py$', fname)):
        with open(file, 'r') as f:
            module = f.read()
            func_path = path + '/' + file
            process_module(module, func_path, result)
    return result
module 'ast' has no attribute 'Else'
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 114, in main
    loaded_test = imp.load_source('test', test_module)
  File "/usr/local/lib/python3.5/imp.py", line 172, in load_source
    module = _load(spec)
  File "<frozen importlib._bootstrap>", line 693, in _load
  File "<frozen importlib._bootstrap>", line 673, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 662, in exec_module
  File "<frozen importlib._bootstrap>", line 222, in _call_with_frames_removed
  File "/tmp/d20160415-29791-1peh9t5/test.py", line 3, in <module>
    import solution
  File "/tmp/d20160415-29791-1peh9t5/solution.py", line 6, in <module>
    ast_type = (ast.For, ast.If, ast.While, ast.With, ast.Try, ast.Else, ast.Except)
Николай Лазаров
  • Коректно
  • 7 успешни тест(а)
  • 0 неуспешни тест(а)
Николай Лазаров
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
from os import listdir
from os.path import isfile, isdir, join, splitext
import ast
import codecs


class DepthCounter(ast.NodeVisitor):
    def __init__(self, depth=0):
        self.depth = depth

    def _count_depth(self, node):
        child_depth = self.depth+1
        for child in ast.iter_child_nodes(node):
            depth_counter = DepthCounter(child_depth)
            depth_counter.visit(child)
            self.depth = max(self.depth, depth_counter.depth)

    visit_For = _count_depth
    visit_While = _count_depth
    visit_With = _count_depth
    visit_Try = _count_depth
    visit_If = _count_depth


class StatsVisitor(ast.NodeVisitor):
    def __init__(self, filepath):
        self.filepath = filepath
        self.functions = 0
        self.classes = 0
        self.unpleasant_functions = []

    def visit_FunctionDef(self, node):
        self.functions += 1
        depth_counter = DepthCounter()
        depth_counter.visit(node)
        if depth_counter.depth >= 3:
            self.unpleasant_functions.append(
                "{}#{}".format(self.filepath, node.name)
            )

        self.generic_visit(node)

    def visit_ClassDef(self, node):
        self.classes += 1
        self.generic_visit(node)


def stats(path_to_directory):
    if not isdir(path_to_directory):
        raise NotADirectoryError(path_to_directory)

    dir_stats = {
        'classes': 0,
        'functions': 0,
        'unpleasant_functions': []
    }

    for file_or_dir in listdir(path_to_directory):
        path = join(path_to_directory, file_or_dir)
        if isfile(path):
            _, ext = splitext(path)
            if ext != '.py':
                continue

            with codecs.open(path, 'r', 'utf-8') as f:
                file = f.read()

            node = ast.parse(file)
            stats_visitor = StatsVisitor(path)
            stats_visitor.visit(node)

            dir_stats['functions'] += stats_visitor.functions
            dir_stats['classes'] += stats_visitor.classes
            dir_stats['unpleasant_functions'] += \
                stats_visitor.unpleasant_functions
        elif isdir(path):
            subdir_stats = stats(path)
            dir_stats['classes'] += subdir_stats['classes']
            dir_stats['functions'] += subdir_stats['functions']
            dir_stats['unpleasant_functions'] += \
                subdir_stats['unpleasant_functions']

    return dir_stats
.......
----------------------------------------------------------------------
Ran 7 tests in 0.117s

OK
Десислава Цветкова
  • Коректно
  • 7 успешни тест(а)
  • 0 неуспешни тест(а)
Десислава Цветкова
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
import ast
import inspect
import textwrap
import os
from importlib.machinery import SourceFileLoader
import os.path
import re


def stats(path_to_directory):
    data = {
        'classes': 0,
        'functions': 0,
        'unpleasant_functions': []
    }
    if not os.path.exists(path_to_directory):
        raise NotADirectoryError

    modules = find_modules(path_to_directory)
    for mod in modules:
        name = re.match('.*/(.*)\.py$', mod).groups()[0]
        foo = SourceFileLoader(name, mod).load_module()
        Crawler.make_all(foo, data, mod)
    return data


def find_modules(path_to_directory):
    modules = []
    for dirpath, dirnames, filenames in os.walk(path_to_directory):
        for filename in [f for f in filenames if f.endswith(".py")]:
            modules.append(os.path.join(dirpath, filename))
    return modules


class Crawler:
    GATES = [ast.If, ast.While, ast.For, ast.With,
             ast.Try, ast.Module, ast.FunctionDef]
    FORBIDDEN_LEVEL = 3
    DIFF = 2

    @classmethod
    def make_all(cls, module, data, directory):
        classes_names = cls.get_classes(module)
        funcs = cls.find_all_functions_module(module)
        data['classes'] += len(classes_names)
        data['functions'] += len(funcs)
        unpls = [directory + "#" + n for n in cls.find_unpleasant_funcs(funcs)]
        data['unpleasant_functions'].extend(unpls)

    @classmethod
    def find_all_functions_module(cls, module):
        classes_names = cls.get_classes(module)
        functions = cls.get_functions(module)
        functions = list(map(lambda x: getattr(module, x), functions))
        for c in classes_names:
            clss = getattr(module, c)
            funcs = cls.get_functions(clss)
            functions.extend(list(map(lambda x: getattr(clss, x), funcs)))
        return functions

    @classmethod
    def get_classes(cls, module):
        classes = list(map(lambda x: x[0],
                           inspect.getmembers(module,
                                              predicate=inspect.isclass)))
        return classes

    @classmethod
    def get_functions(cls, module):
        functions = list(map(lambda x: x[0],
                             inspect.getmembers(module,
                                                predicate=inspect.isfunction)))
        return functions

    @classmethod
    def find_unpleasant_funcs(cls, functions):
        funcs = []
        for func in functions:
            if not cls.is_pleasant(func):
                funcs.append(func.__name__)
        return funcs

    @classmethod
    def is_pleasant(cls, function_name):
        cls.DIFF = -1
        function_ast = ast.parse(
            textwrap.dedent(inspect.getsource(function_name)))
        queue = [(function_ast, cls.DIFF)]
        return cls.go_through_function_util(queue)

    @classmethod
    def go_through_function_util(cls, queue):
        while queue:
            element, level = queue.pop()
            if level >= cls.FORBIDDEN_LEVEL and type(element) in cls.GATES:
                return False

            if hasattr(element, 'body') and type(element) in cls.GATES:
                for inner in element.body:
                    queue.append((inner, level + 1))
        return True
.......
----------------------------------------------------------------------
Ran 7 tests in 0.370s

OK
Николай Желязков
  • Некоректно
  • 6 успешни тест(а)
  • 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
import os
import re


def make_stats(file_name):
    if not re.search(r'\.py\b', file_name):
        return 0, 0, []
    cls_count, func_count, spaces = 0, 0, 12
    unpleasant_funcs = set()
    last_func_name = ''
    with open(file_name) as data_file:
        for line in data_file:
            pat = '\s{' + str(spaces) + ',}(if|while|for|with|try)'
            if re.match(r'class', line):
                cls_count += 1
            match_obj = re.search(r'\bdef\b', line)
            if match_obj:
                func_count += 1
                args_pos = re.search(r'\(', line).start()
                last_func_name = line[match_obj.end() + 1:args_pos]
                spaces = match_obj.start() + 12
            elif re.match(pat, line):
                unpleasant_funcs.add(file_name + '#' + last_func_name)
    return cls_count, func_count, list(unpleasant_funcs)


def stats(path_to_directory):
    dir_stats = {}
    dir_stats['classes'] = 0
    dir_stats['functions'] = 0
    dir_stats['unpleasant_functions'] = []
    if not os.path.isdir(path_to_directory):
        raise NotADirectoryError
    for root, dirs, files in os.walk(path_to_directory):
        for file in files:
            result = make_stats(os.path.join(root, file))
            dir_stats['classes'] += result[0]
            dir_stats['functions'] += result[1]
            dir_stats['unpleasant_functions'].extend(result[2])
    return dir_stats
.....F.
======================================================================
FAIL: test_dont_read_code_from_docstring (test.TestAst)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
AssertionError: 3 != 2

----------------------------------------------------------------------
Ran 7 tests in 0.098s

FAILED (failures=1)
Димитър Керезов
  • Коректно
  • 7 успешни тест(а)
  • 0 неуспешни тест(а)
Димитър Керезов
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
from os import walk, path
import ast


def stats(path_to_directory):
    if not path.isdir(path_to_directory):
        raise NotADirectoryError("Path does not exist or is not a directory")

    result = {
        'classes': 0,
        'functions': 0,
        'unpleasant_functions': []
    }

    def is_unpleasant(ast_obj, depth):
        if depth > 3:
            return True

        if hasattr(ast_obj, "body"):
            for inner_ast_obj in ast_obj.body:
                if is_unpleasant(inner_ast_obj, depth + 1):
                    return True

    for root, subFolders, files in walk(path_to_directory):
        for file in files:
            if file.endswith(".py"):
                with open(path.join(root, file), 'r') as f:
                    content = f.read()
                    parsed = ast.parse(content)
                    for element in ast.walk(parsed):
                        if isinstance(element, ast.ClassDef):
                            result["classes"] += 1
                        if isinstance(element, ast.FunctionDef):
                            result["functions"] += 1
                            if is_unpleasant(element, 0):
                                full_file_path = path.join(root, file)
                                func = full_file_path + "#" + element.name
                                result["unpleasant_functions"].append(func)

    return result
.......
----------------------------------------------------------------------
Ran 7 tests in 0.103s

OK
Георги Данков
  • Коректно
  • 7 успешни тест(а)
  • 0 неуспешни тест(а)
Георги Данков
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
import ast
import os


class ClassFuncLister(ast.NodeVisitor):
    def __init__(self):
        self.func_count = 0
        self.class_count = 0
        self.unpleasant = []

    def visit_FunctionDef(self, node):
        self.func_count += 1
        if ClassFuncLister.is_unpleasant(node, node.col_offset):
                self.unpleasant.append(node.name)
        self.generic_visit(node)

    @staticmethod
    def is_unpleasant(node, def_col):
        if not hasattr(node, 'body'):
            return node.col_offset - def_col >= 13

        body = ast.parse(node).body
        for expr in body:
            if ClassFuncLister.is_unpleasant(expr, def_col):
                return True
        return False

    def visit_ClassDef(self, node):
        self.class_count += 1
        self.generic_visit(node)


def stats(path_to_directory):
    if not os.path.isdir(path_to_directory):
        raise NotADirectoryError(
            "Directory {} doesn't exist!".format(path_to_directory))

    class_count = 0
    func_count = 0
    unpleasant = []

    for root, dirs, files in os.walk(path_to_directory):
        for file in filter(lambda f: os.path.splitext(f)[1] == '.py', files):
                absolute_path = os.path.join(
                    os.sep, os.path.abspath(root), file)
                with open(absolute_path, 'r') as f:
                    parsed_module = ast.parse(f.read())

                cfl = ClassFuncLister()
                cfl.visit(parsed_module)

                class_count += cfl.class_count
                func_count += cfl.func_count

                relative_path = os.path.join(root, file)
                unpleasant.extend(["{}#{}". format(relative_path, unpl)
                                  for unpl in cfl.unpleasant])
    return {
        'classes': class_count,
        'functions': func_count,
        'unpleasant_functions': unpleasant
    }
.......
----------------------------------------------------------------------
Ran 7 tests in 0.107s

OK
Светомир Стоименов
  • Коректно
  • 7 успешни тест(а)
  • 0 неуспешни тест(а)
Светомир Стоименов
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
import os
import ast


class Block(ast.If, ast.For, ast.While, ast.Try, ast.With):
    pass


def is_block(object):
    return issubclass(Block, object.__class__)


class StatsNodeVisitor(ast.NodeVisitor):
    def __init__(self, path):
        self.path = path + '#'
        self.stats = {
            'classes': 0,
            'functions': 0,
            'unpleasant_functions': []
        }

    def indent_level(self, block):
        inner_blocks = [child for child in block if is_block(child)]
        if not inner_blocks:
            return 0
        indents = {self.indent_level(block.body) for block in inner_blocks}
        return max(indents) + 1

    def visit_ClassDef(self, node):
        self.stats['classes'] += 1
        self.generic_visit(node)

    def visit_FunctionDef(self, node):
        self.stats['functions'] += 1
        self.generic_visit(node)
        if self.indent_level(node.body) > 2:
            self.stats['unpleasant_functions'].append(self.path + node.name)


def merge_stats(stats1, stats2):
    result = {}
    for key in stats1:
        result[key] = stats1[key] + stats2[key]
    return result


def stats(path_to_directory):
    if not os.path.isdir(path_to_directory):
        raise NotADirectoryError
    stats = {
        'classes': 0,
        'functions': 0,
        'unpleasant_functions': []
    }
    for path, dirs, files in os.walk(path_to_directory):
        for file in files:
            if file.endswith('.py'):
                fstats = file_stats(os.path.join(path, file))
                stats = merge_stats(stats, fstats)
    return stats


def file_stats(path_to_file):
    with open(path_to_file, 'r') as file:
        module = file.read()
    visitor = StatsNodeVisitor(path_to_file)
    visitor.visit(ast.parse(module))
    return visitor.stats
.......
----------------------------------------------------------------------
Ran 7 tests in 1.085s

OK
Марина Георгиева
  • Коректно
  • 7 успешни тест(а)
  • 0 неуспешни тест(а)
Марина Георгиева
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
import os
import ast
from fnmatch import fnmatch


def stats(path_to_directory):
    stats = {'classes': 0, 'functions': 0, 'unpleasant_functions': []}

    if not os.path.isdir(path_to_directory):
        raise NotADirectoryError

    py_files_names = get_py_files_names(path_to_directory)
    modules_names = {file_name: build_ast(file_name)
                     for file_name in py_files_names}
    for module_name in modules_names:
        process_module_ast(modules_names[module_name], module_name, stats)
    return stats


def get_py_files_names(path_to_directory):
    return [os.path.join(path, file)
            for path, subdirs, files in os.walk(path_to_directory)
            for file in files if fnmatch(file, '*py')]


def build_ast(file_name):
    with open(file_name, 'r') as file:
        return ast.parse(file.read())


def process_module_ast(module, file_name, stats):
    for node in module.body:
            if isinstance(node, ast.ClassDef):
                stats['classes'] += 1
                for body_node in node.body:
                    if isinstance(body_node, ast.FunctionDef):
                        process_function(body_node, file_name, stats)
            elif isinstance(node, ast.FunctionDef):
                process_function(node, file_name, stats)


def process_function(node, file_name, stats):
    stats['functions'] += 1
    if check_if_unpleasant_function(node):
        stats['unpleasant_functions'].append(file_name + '#' + node.name)


def check_if_unpleasant_function(node, nesting_count=0):
    ast_controw_flow_nodes = (ast.If, ast.While, ast.For, ast.With,
                              ast.Try, ast.ExceptHandler)
    for body_node in node.body:
        if isinstance(body_node, ast_controw_flow_nodes):
            return check_if_unpleasant_function(body_node, nesting_count + 1)
    return nesting_count >= 3
.......
----------------------------------------------------------------------
Ran 7 tests in 0.095s

OK
Илиан Стаменов
  • Некоректно
  • 5 успешни тест(а)
  • 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
import os
import re
import ast


class Visitor(ast.NodeVisitor):
    def __init__(self):
        ast.NodeVisitor.__init__(self)
        self.func_count = 0
        self.class_count = 0
        self.unpleasant_functions = []
        self.depth = 0

    def set_file(self, file):
        self.file = file

    def visit_FunctionDef(self, node):
        self.func_count += 1
        self.depth = 0
        self.generic_visit(node)
        if(3 <= self.depth):
            self.unpleasant_functions.append(self.file + node.name)

    def visit_ClassDef(self, node):
        self.depth += 1
        self.class_count += 1
        self.generic_visit(node)

    def visit_For(self, node):
        self.depth += 1
        self.generic_visit(node)

    def visit_If(self, node):
        self.depth += 1
        self.generic_visit(node)

    def visit_While(self, node):
        self.depth += 1
        self.generic_visit(node)

    def visit_With(self, node):
        self.depth += 1
        self.generic_visit(node)

    def visit_Try(self, node):
        self.depth += 1
        self.generic_visit(node)


def stats(path_to_directory):
    regex = re.compile(".*\.py$")
    visitor = Visitor()
    try:
        for root, dirs, files in os.walk(path_to_directory):
            for file in files:
                if(regex.match(file)):
                    with open(os.path.join(root, file)) as f:
                        res = re.sub('\\\\', '/', root + "/" + file)
                        visitor.set_file(res + "#")
                        string = ""
                        for line in f:
                            string += line
                        visitor.visit(ast.parse(string))
    except OSError:
        raise NotADirectoryError()
    return {'classes': visitor.class_count,
            'functions': visitor.func_count,
            'unpleasant_functions': visitor.unpleasant_functions}
.F....F
======================================================================
FAIL: test_assertion_raised_on_wrong_directory (test.TestAst)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
AssertionError: NotADirectoryError not raised

======================================================================
FAIL: test_unpleasant_functions_count_correctness (test.TestAst)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/data/rails/pyfmi-2016/releases/20160307095126/lib/language/python/runner.py", line 67, in thread
    raise result
AssertionError: 6 != 7

----------------------------------------------------------------------
Ran 7 tests in 0.111s

FAILED (failures=2)