You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

153 lines
5.1 KiB

import os
from functools import wraps
from abc import ABCMeta, abstractmethod
from inspect import getcallargs
class Cachable:
'''Базовый класс для создания классов, кэширующих вывод своих методов.
Декоратор @Cachable.method_cached() предназначен для указания методов
с кэшируемым выводом.'''
def __init__(self):
self.clear_method_cache()
def clear_method_cache(self):
self._method_cache = {}
@staticmethod
def method_cached(key=lambda *args, **kwargs: hash(args)):
def decorator(function):
function_name = function.__name__
@wraps(function)
def wrapper(self, *args, **kwargs):
keyval = key(*args, **kwargs)
assert isinstance(self, Cachable)
if function_name not in self._method_cache:
self._method_cache[function_name] = {}
cache = self._method_cache[function_name]
if keyval not in cache:
cache[keyval] = function(self, *args, **kwargs)
return cache[keyval]
@wraps(function)
def null_wrapper(self):
assert isinstance(self, Cachable)
if function_name not in self._method_cache:
self._method_cache[function_name] = function(self)
return self._method_cache[function_name]
if len(function.__code__.co_varnames) > 1:
return wrapper
else:
return null_wrapper
return decorator
class Singleton(type):
'''Метакласс для создания синглтонов.'''
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class SingletonParam(type):
'''Метакласс для создания синглтонов по параметрам.'''
_instances = {}
_init = {}
def __init__(cls, class_name, base_classes, class_dict):
cls._init[cls] = class_dict.get('__init__', None)
def __call__(cls, *args, **kwargs):
init = cls._init[cls]
if init is not None:
key_value = (cls, frozenset(getcallargs(init, None, *args,
**kwargs).items()))
else:
key_value = cls
if key_value not in cls._instances:
cls._instances[key_value] = super().__call__(*args, **kwargs)
return cls._instances[key_value]
class GenericFS(metaclass=ABCMeta):
'''Абстрактный класс для работы с файловыми системами.'''
@abstractmethod
def exists(self, path):
pass
@abstractmethod
def read(self, path):
pass
@abstractmethod
def glob(self, path):
pass
@abstractmethod
def realpath(self, path):
pass
@abstractmethod
def write(self, path, data):
pass
@abstractmethod
def listdir(self, path, fullpath=False):
pass
def get_traceback_caller(exception_type, exception_object,
exception_traceback):
'''Возвращает имя модуля, в котором было сгенерировано исключение,
и соответствующий номер строки.'''
while exception_traceback.tb_next:
exception_traceback = exception_traceback.tb_next
line_number = exception_traceback.tb_lineno
module_path, module_name = os.path.split(exception_traceback.tb_frame.
f_code.co_filename)
if module_name.endswith('.py'):
module_name = module_name[:-3]
full_module_name = [module_name]
while (module_path and module_path != '/' and not
module_path.endswith('site-packages')):
module_path, package_name = os.path.split(module_path)
full_module_name.insert(0, package_name)
if module_path.endswith('site-packages'):
module_name = '.'.join(full_module_name)
return module_name, line_number
def unique(iterable):
'''Возвращает итерируемый объект, содержащий только уникальные элементы
входного объекта, сохраняя их порядок.
'''
output = []
for element in iterable:
if element not in output:
output.append(element)
return output
def flat_iterable(iterable, types=(list, tuple, map, zip, filter)):
'''Распаковывает все вложенные итерируемые объекты во входном объекте.
Например:
[1, 2, [3, 4, [5, 6], 7], [8, 9], 10] -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
'''
if isinstance(iterable, types):
for it in iterable:
for sub_iterable in flat_iterable(it, types=types):
yield sub_iterable
else:
yield iterable