Повторное использование существующих объектов для неизменяемых объектов?

В Python, как можно повторно использовать существующие одинаковые неизменяемые объекты (например, для str)? Можно ли это сделать только путем определения метода __hash__ или требуется более сложные меры?

Ответ 1

Если вы хотите создать через конструктор класса и вернуть его ранее созданному объекту, вам необходимо предоставить метод __new__ (потому что к тому времени, когда вы дойдете до __init__, объект уже создан).

Вот простой пример: если значение, используемое для инициализации, было замечено до этого, возвращается ранее созданный объект, а не новый:

class Cached(object):
    """Simple example of immutable object reuse."""

    def __init__(self, i):
        self.i = i

    def __new__(cls, i, _cache={}):
        try:
            return _cache[i]
        except KeyError:
            # you must call __new__ on the base class
            x = super(Cached, cls).__new__(cls)
            x.__init__(i)
            _cache[i] = x
            return x

Обратите внимание, что для этого примера вы можете использовать что-либо для инициализации, пока оно hashable. И просто чтобы показать, что объекты действительно используются повторно:

>>> a = Cached(100)
>>> b = Cached(200)
>>> c = Cached(100)
>>> a is b
False
>>> a is c
True

Ответ 2

Я считаю, что вам нужно будет сохранить dict {args: object} из уже созданных экземпляров, затем переопределить метод класса __new__ для проверки этого словаря и вернуть соответствующий объект, если он уже существует. Обратите внимание, что я не реализовал или не тестировал эту идею. Конечно, строки обрабатываются на уровне C.

Ответ 3

Существует два решения для "разработки программного обеспечения", которые не требуют каких-либо низкоуровневых знаний о Python. Они применяются в следующих сценариях:

Первый сценарий:. Объекты вашего класса "равны", если они построены с одинаковыми параметрами конструктора, а равенство не изменится со временем после построения. Решение. Используйте factory, который использует параметры конструктора:

class MyClass:
  def __init__(self, someint, someotherint):
    self.a = someint
    self.b = someotherint

cachedict = { }
def construct_myobject(someint, someotherint):
  if (someint, someotherint) not in cachedict:
    cachedict[(someint, someotherint)] = MyClass(someint, someotherint)
  return cachedict[(someint, someotherint)]

Этот подход существенно ограничивает экземпляры вашего класса одним уникальным объектом для каждой входной пары. Есть и очевидные недостатки: не все типы легко хешируются и т.д.

Второй сценарий: Объекты вашего класса изменяемы, а их "равенство" может меняться со временем. Решение. Определите реестр уровня равным образом:

class MyClass:
  registry = { }

  def __init__(self, someint, someotherint, third):
    MyClass.registry[id(self)] = (someint, someotherint)
    self.someint = someint
    self.someotherint = someotherint
    self.third = third

  def __eq__(self, other):
    return MyClass.registry[id(self)] == MyClass.registry[id(other)]

  def update(self, someint, someotherint):
    MyClass.registry[id(self)] = (someint, someotherint)

В этом примере объекты с одинаковой парой someint, someotherint равны, а третий параметр не учитывается. Трюк заключается в том, чтобы синхронизировать параметры в registry. В качестве альтернативы update вы можете переопределить getattr и setattr для своего класса; это обеспечило бы синхронизацию любого присваивания foo.someint = y с вашим словарем уровня. См. Пример здесь.