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