Стат трек

Краен срок
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 неуспешни тест(а)
Теодор Тошков
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 неуспешни тест(а)
Данаил Димитров
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 неуспешни тест(а)
Николай Лазаров
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 неуспешни тест(а)
Десислава Цветкова
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 неуспешни тест(а)
Николай Желязков
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 неуспешни тест(а)
Димитър Керезов
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 неуспешни тест(а)
Георги Данков
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 неуспешни тест(а)
Светомир Стоименов
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 неуспешни тест(а)
Марина Георгиева
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 неуспешни тест(а)
Илиан Стаменов
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)