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.

154 lines
5.1 KiB

  1. import os
  2. from functools import wraps
  3. from abc import ABCMeta, abstractmethod
  4. from inspect import getcallargs
  5. class Cachable:
  6. '''Базовый класс для создания классов, кэширующих вывод своих методов.
  7. Декоратор @Cachable.method_cached() предназначен для указания методов
  8. с кэшируемым выводом.'''
  9. def __init__(self):
  10. self.clear_method_cache()
  11. def clear_method_cache(self):
  12. self._method_cache = {}
  13. @staticmethod
  14. def method_cached(key=lambda *args, **kwargs: hash(args)):
  15. def decorator(function):
  16. function_name = function.__name__
  17. @wraps(function)
  18. def wrapper(self, *args, **kwargs):
  19. keyval = key(*args, **kwargs)
  20. assert isinstance(self, Cachable)
  21. if function_name not in self._method_cache:
  22. self._method_cache[function_name] = {}
  23. cache = self._method_cache[function_name]
  24. if keyval not in cache:
  25. cache[keyval] = function(self, *args, **kwargs)
  26. return cache[keyval]
  27. @wraps(function)
  28. def null_wrapper(self):
  29. assert isinstance(self, Cachable)
  30. if function_name not in self._method_cache:
  31. self._method_cache[function_name] = function(self)
  32. return self._method_cache[function_name]
  33. if len(function.__code__.co_varnames) > 1:
  34. return wrapper
  35. else:
  36. return null_wrapper
  37. return decorator
  38. class Singleton(type):
  39. '''Метакласс для создания синглтонов.'''
  40. _instances = {}
  41. def __call__(cls, *args, **kwargs):
  42. if cls not in cls._instances:
  43. cls._instances[cls] = super().__call__(*args, **kwargs)
  44. return cls._instances[cls]
  45. class SingletonParam(type):
  46. '''Метакласс для создания синглтонов по параметрам.'''
  47. _instances = {}
  48. _init = {}
  49. def __init__(cls, class_name, base_classes, class_dict):
  50. cls._init[cls] = class_dict.get('__init__', None)
  51. def __call__(cls, *args, **kwargs):
  52. init = cls._init[cls]
  53. if init is not None:
  54. key_value = (cls, frozenset(getcallargs(init, None, *args,
  55. **kwargs).items()))
  56. else:
  57. key_value = cls
  58. if key_value not in cls._instances:
  59. cls._instances[key_value] = super().__call__(*args, **kwargs)
  60. return cls._instances[key_value]
  61. class GenericFS(metaclass=ABCMeta):
  62. '''Абстрактный класс для работы с файловыми системами.'''
  63. @abstractmethod
  64. def exists(self, path):
  65. pass
  66. @abstractmethod
  67. def read(self, path):
  68. pass
  69. @abstractmethod
  70. def glob(self, path):
  71. pass
  72. @abstractmethod
  73. def realpath(self, path):
  74. pass
  75. @abstractmethod
  76. def write(self, path, data):
  77. pass
  78. @abstractmethod
  79. def listdir(self, path, fullpath=False):
  80. pass
  81. def get_traceback_caller(exception_type, exception_object,
  82. exception_traceback):
  83. '''Возвращает имя модуля, в котором было сгенерировано исключение,
  84. и соответствующий номер строки.'''
  85. while exception_traceback.tb_next:
  86. exception_traceback = exception_traceback.tb_next
  87. line_number = exception_traceback.tb_lineno
  88. module_path, module_name = os.path.split(exception_traceback.tb_frame.
  89. f_code.co_filename)
  90. if module_name.endswith('.py'):
  91. module_name = module_name[:-3]
  92. full_module_name = [module_name]
  93. while (module_path and module_path != '/' and not
  94. module_path.endswith('site-packages')):
  95. module_path, package_name = os.path.split(module_path)
  96. full_module_name.insert(0, package_name)
  97. if module_path.endswith('site-packages'):
  98. module_name = '.'.join(full_module_name)
  99. return module_name, line_number
  100. def unique(iterable):
  101. '''Возвращает итерируемый объект, содержащий только уникальные элементы
  102. входного объекта, сохраняя их порядок.
  103. '''
  104. output = []
  105. for element in iterable:
  106. if element not in output:
  107. output.append(element)
  108. return output
  109. def flat_iterable(iterable, types=(list, tuple, map, zip, filter)):
  110. '''Распаковывает все вложенные итерируемые объекты во входном объекте.
  111. Например:
  112. [1, 2, [3, 4, [5, 6], 7], [8, 9], 10] -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  113. '''
  114. if isinstance(iterable, types):
  115. for it in iterable:
  116. for sub_iterable in flat_iterable(it, types=types):
  117. yield sub_iterable
  118. else:
  119. yield iterable