Объекты-одиночки
Часто по тем или иным причинам необходимо обеспечить уникальность какого-либо объекта. В этой заметке приводятся различные варианты реализации "одиночек" на языке Python.
Введение
Часто возникает необходимость в таком виде объектов, чтобы все запрошенные объекты этого вида были идентичны, имели одинаковое поведение и находились в одинаковом состоянии. Именно такие объекты мы и будем называть одиночками. Идентичность объектов можно рассматривать как с точки зрения оператора is (то есть, это должен быть один и тот же объект), так и с точки зрения оператора == (для равенства достаточно правильно определить метод сравнения — __eq__ или __cmp__). Каким же образом можно обеспечить все эти требования на языке Python?
Модули в языке Python являются одиночками!
Действительно, каждый модуль инициализирует только один раз — когда вы его импортируете (не будем брать в расчет возможность перезагрузить модуль с помощью функции reload). Кроме того, механизм импорта модулей обеспечивает "одиночество" всех глобальных объектов модуля, атрибутов классов модуля и так далее. Однако не всегда удобно выделять несколько строк кода в отдельный модуль с одной этой целью, а инициализация объектов модуля произойдет, уже когда потребуется модуль, но еще не сам объект-одиночка из него. Поэтому использование механизма импорта модулей для обеспечения "одиночества" подойдет только в случае, если инициализация "одиночки" не требует значительных затрат.
Преведем простой пример. Часто требуется объект, чтобы обозначить что-то особенное, например поведение по умолчанию. В большинстве случаев для этих целей вполне подходит значение None. Но как быть, если необходимо рассматривать None на равне с любыми другими значениями? Нужен уникальный объект, предназначенный только для данной задачи. Самый простой способ — создать экземпляр типа object:
default = object() # уникальный объект
def func(arg=default):
if arg is _default:
# действия по умолчанию
else:
# обычная обработка аргумента, в том числе None
Представление такого объекта не слишком удобно при отладке. Более подходящим будет пустой класс:
class default: pass
Если вы хотите защититься от случайного использования не по назначению, можно запретить создание его экземпляров:
class default:
def __init__(self):
raise ValueError
Ну и, наконец, чтобы получить действительно красивое представление "одиночки" или наделить его какой-то другой дополнительной функциональностью — придется создавать экземпляр специально написанного класса:
class _Default:
def __repr__(self):
return 'default'
default = _Default()
del _Default
Уникальный объект или разделяемое состояние?
А так ли нам необходима уникальность объекта? Может вполне достаточно того, что вполне достаточно иметь одинаковое состояние и поведение? Тогда вам должен понравиться простой класс, предложенный Алексом Мартелли:
class Borg:
__shared_state = {}
def __init__(self):
self.__dict__ = self.__shared_state
При необходимости можно и равенство всех экземпляров класса Borg обеспечить, переопределив метод __eq__. Имея доступ к текущему состоянию, можно обеспечить выполнение инициализации только один раз. Однако, для классов нового типа со слотами такое решение работать не будет: экземпляры таких классов не используют __dict__ для хранения атрибутов. Конечно, новые классы также могут иметь разделяемое состояние. Более того, с помощью дескрипторов можно красиво обеспечить разделение только части состояния объектов, но класс уже не будет таким простым.
Классический подход
Пожалуй самый распространенный способ гарантировать единственность создаваемого экземпляра — создавать его не напрямую, а через функцию или статический метод класса. Это классическая модель одиночки (Singleton), описанная "бандой четырех" (E. Gamma, R. Helm, R. Johnson, J. Vlissides, "Design Patterns, Elements of Reusable Object-Oriented Software"). Именно такой подход следует использрвать в тех случаях, когда создание и инциализация экземпляра требует значительных ресурсов, а результат не всегда востребован. Для единственного объекта функция-конструктор может выглядеть так:
class _Class:
# ...
_instance = None
def createInstance():
global _instance
if _instance is None:
_instance = _Class()
return _instance
Если же объект должен быть единственным для каждого набора параметров, то можно держать словарь инициализированных объектов.
Однако в языке Python нет необходимости определять отдельную функцию конструктор или статический метод, достаточно переопределить метод и уникальность объектов будет поддерживаться незаметно для пользователей:
class Singleton(object):
__instance = None
def __new__(cls):
if cls.__instance is None:
cls.__instance = self = object.__new__(cls)
# необходимая инициализация
return cls.__instance
Следует помнить, что код, помещенный в метод __init__ этого класса будет выполняться при каждой попытке создания экзепляра (в этом редко возникает необходимость), в то время как код внутри инструкции if в методе __new__ выполняется, только когда экземпляр действительно создается.
Последний вариант можно модифицировать таким образом, чтобы созданный экземпляр "жил" только тогда, когда он где-то используется, то есть на него есть ссылки:
import weakref
class Singleton(object):
__instance = staticmethod(lambda: None)
def __new__(cls):
self = cls.__instance()
if self is None:
self = object.__new__(cls)
cls.__instance = weakref.ref(self)
# необходимая инициализация
return self
Источники:
www.python.ru
автор: Д.С.Откидач
|
ПОМОЩЬ САЙТУ :
|
|
|