Вопрос по python – Как мне запомнить экземпляр класса в Python?

18

Хорошо, вот сценарий реального мира: я пишу приложение, и у меня есть класс, который представляет определенный тип файлов (в моем случае это фотографии, но эта деталь не имеет отношения к проблеме). Каждый экземпляр класса Photograph должен быть уникальным для имени файла фотографии.

Проблема в том, что когда пользователь говорит моему приложению загрузить файл, я должен иметь возможность определить, когда файлы уже загружены, и использовать существующий экземпляр для этого имени файла, а не создавать дубликаты экземпляров с тем же именем файла.

Мне кажется, что это хорошая ситуация, чтобы использовать запоминание, и существует множество примеров этого, но в этом случае я не просто запоминаю обычную функцию, мне нужно запоминать__init__(), Это создает проблему, потому что к тому времени__init__() вызывается, это уже слишком поздно, поскольку уже создан новый экземпляр.

В моем исследовании я нашел Python__new__() метод, и я действительно смог написать рабочий тривиальный пример, но он развалился, когда я попытался использовать его на своих объектах реального мира, и я не уверен почему (единственное, что я могу думать, это то, что мой реальный объекты мира были подклассами других объектов, которые я не могу реально контролировать, и поэтому при таком подходе были некоторые несовместимости). Вот что у меня было:

class Flub(object):
    instances = {}

    def __new__(cls, flubid):
        try:
            self = Flub.instances[flubid]
        except KeyError:
            self = Flub.instances[flubid] = super(Flub, cls).__new__(cls)
            print 'making a new one!'
            self.flubid = flubid
        print id(self)
        return self

    @staticmethod
    def destroy_all():
        for flub in Flub.instances.values():
            print 'killing', flub


a = Flub('foo')
b = Flub('foo')
c = Flub('bar')

print a
print b
print c
print a is b, b is c

Flub.destroy_all()

Какой вывод это:

making a new one!
139958663753808
139958663753808
making a new one!
139958663753872
<__main__.Flub object at 0x7f4aaa6fb050>
<__main__.Flub object at 0x7f4aaa6fb050>
<__main__.Flub object at 0x7f4aaa6fb090>
True False
killing <__main__.Flub object at 0x7f4aaa6fb050>
killing <__main__.Flub object at 0x7f4aaa6fb090>

Это прекрасно! Только два экземпляра были сделаны для двух уникальных идентификаторов, а в Flub.instances явно есть только два перечисленных.

Но когда я попытался использовать этот подход с объектами, которые я использовал, я получил всевозможные бессмысленные ошибки о том, как__init__() взял только 0 аргументов, а не 2. Так что я бы изменил некоторые вещи, и тогда он сказал бы мне, что__init__() нужен аргумент. Совершенно странно.

После некоторого времени борьбы с ним, я просто сдался и переместил все__new__() черная магия в статический метод называетсяgetтакой, чтобы я мог позвонитьPhotograph.get(filename) и это будет только позвонитьPhotograph(filename) если имя файла не было уже вPhotograph.instances.

Кто-нибудь знает, где я ошибся здесь? Есть ли лучший способ сделать это?

Другой способ думать об этом состоит в том, что он аналогичен синглтону, за исключением того, что он не глобально синглтон, а только синглтон на имя файла.

Вот мой реальный код с использованием staticmethod get если вы хотите увидеть все это вместе.

Я отредактировал вопрос, чтобы удалить те вещи, которые вы сказали. robru

Ваш Ответ

3   ответа
2

Параметры для__new__ также передается__init__, так:

def __init__(self, flubid):
    ...

Вы должны принятьflubid аргумент там, даже если вы не используете его в__init__

Вот соответствующий комментарий, взятый изtypeobject.c в Python2.7.3

/* You may wonder why object.__new__() only complains about arguments
   when object.__init__() is not overridden, and vice versa.

   Consider the use cases:

   1. When neither is overridden, we want to hear complaints about
      excess (i.e., any) arguments, since their presence could
      indicate there's a bug.

   2. When defining an Immutable type, we are likely to override only
      __new__(), since __init__() is called too late to initialize an
      Immutable object.  Since __new__() defines the signature for the
      type, it would be a pain to have to override __init__() just to
      stop it from complaining about excess arguments.

   3. When defining a Mutable type, we are likely to override only
      __init__().  So here the converse reasoning applies: we don't
      want to have to override __new__() just to stop it from
      complaining.

   4. When __init__() is overridden, and the subclass __init__() calls
      object.__init__(), the latter should complain about excess
      arguments; ditto for __new__().

   Use cases 2 and 3 make it unattractive to unconditionally check for
   excess arguments.  The best solution that addresses all four use
   cases is as follows: __init__() complains about excess arguments
   unless __new__() is overridden and __init__() is not overridden
   (IOW, if __init__() is overridden or __new__() is not overridden);
   symmetrically, __new__() complains about excess arguments unless
   __init__() is overridden and __new__() is not overridden
   (IOW, if __new__() is overridden or __init__() is not overridden).

   However, for backwards compatibility, this breaks too much code.
   Therefore, in 2.6, we'll *warn* about excess arguments when both
   methods are overridden; for all other cases we'll use the above
   rules.

*/
О хорошо Благодарю. robru
@Robru, я обновил свой ответ объяснением, приведенным вtypeobject.c
То, что вы говорите, имеет смысл, но как мой тривиальный пример работает без определения__init__ at all? Shouldn't it also give me errors about incorrect number of arguments passed? – robru Jun 6 '12 at 0:27 robru
15

Using memoize

Вы можете использовать памятку, но вы должны украситьclass, не__init__ метод. Предположим, у нас есть этот мемоизатор:

def get_id_tuple(f, args, kwargs, mark=object()):
    """ 
    Some quick'n'dirty way to generate a unique key for an specific call.
    """
    l = [id(f)]
    for arg in args:
        l.append(id(arg))
    l.append(id(mark))
    for k, v in kwargs:
        l.append(k)
        l.append(id(v))
    return tuple(l)

_memoized = {}
def memoize(f):
    """ 
    Some basic memoizer
    """
    def memoized(*args, **kwargs):
        key = get_id_tuple(f, args, kwargs)
        if key not in _memoized:
            _memoized[key] = f(*args, **kwargs)
        return _memoized[key]
    return memoized

Теперь вам просто нужно украсить класс:

@memoize
class Test(object):
    def __init__(self, somevalue):
        self.somevalue = somevalue

Давайте посмотрим тест?

tests = [Test(1), Test(2), Test(3), Test(2), Test(4)]
for test in tests:
    print test.somevalue, id(test)

Выход ниже. Обратите внимание, что одинаковые параметры дают одинаковый идентификатор возвращаемого объекта:

1 3072319660
2 3072319692
3 3072319724
2 3072319692
4 3072319756

В любом случае, я бы предпочел создать функцию для генерации объектов и их запоминания. Кажется, чище для меня, но это может быть какая-то неуместная любимая мозоль

class Test(object):
    def __init__(self, somevalue):
        self.somevalue = somevalue

@memoize
def get_test_from_value(somevalue):
    return Test(somevalue)
Using __new__:

Или, конечно, вы можете переопределить__new__, Несколько дней назад я отправилответ о плюсах, минусах и лучших практиках переопределения__new__ это может быть полезно. В основном, это говорит, чтобы всегда проходить*args, **kwargs на ваш__new__ метод.

Я, например, предпочел бы запомнить функцию, которая создает объекты, или даже написать конкретную функцию, которая позаботилась бы о том, чтобы объект никогда не воссоздавался с одним и тем же параметром. Конечно, однако, это в основном мое мнение, а не правило.

имейте в виду, что когда входной параметр является строкой или юникодом, идентификатор («строка») не обязательно будет уникальным. Вместо этого вы должны использовать его хэш.
@Robru P.S. Согласно PyInt_FromLongdocs.python.org/2/c-api/int.html#c.PyInt_FromLong единственные значения, которые сохраняют свойство id (a) == id (b), если a == b, составляют от -5 до 256. Я проверил 257, и у него действительно есть разные идентификаторы, если вы создаете его несколько раз.
После нескольких часов возни я сдался__new__ и вернулся к декоратору. Я получил это на работуexactly как я и хотел, в том числе функциональные статические методы! (декораторы нарушают статические методы по умолчанию, потому что оригинальный класс скрывается за объектом декоратора). Решение здесь:github.com/robru/gottengeography/blob/… robru
@Robru, конечно, моя памятка - это просто быстрый код, который я использую в примерах, не обращайте на это большого внимания :)
Благодарю. Я не осознавал, что вы можете поместить декоратор непосредственно в класс, а не в методы. Это была ключевая информация, которую мне не хватало. Ваш декоратор памятки не являетсяquite что мне нужно, потому что строки не являются синглетонами, такими как числа (и, следовательно,idне уникальны из одной идентичной строки в другую), но для своих упрощенных потребностей я смог просто использовать первый аргумент непосредственно в качестве ключа. robru
4

class memoize(object):
    def __init__(self, cls):
        self.cls = cls
        self.__dict__.update(cls.__dict__)

        # This bit allows staticmethods to work as you would expect.
        for attr, val in cls.__dict__.items():
            if type(val) is staticmethod:
                self.__dict__[attr] = val.__func__

    def __call__(self, *args):
        key = '//'.join(map(str, args))
        if key not in self.cls.instances:
            self.cls.instances[key] = self.cls(*args)
        return self.cls.instances[key]

И тогда вы украшаетеclass с этим не__init__, Хотя Brandizzi предоставил мне эту ключевую информацию, его примерный декоратор не работал должным образом.

Я нашел эту концепцию довольно тонкой, но в основном, когда вы используете декораторы в Python, вы должны понимать, что то, что декорируется (будь то метод или класс), на самом делеreplaced by сам декоратор. Например, когда я пытаюсь получить доступPhotograph.instances или жеCamera.generate_id() (статический метод), я не могу получить к ним доступ, потому чтоPhotograph фактически не относится к исходному классу Photograph, оно относится кmemoized function (из примера Брэндиззи).

Чтобы обойти это, мне нужно было создать класс декоратора, который на самом деле брал все атрибуты и статические методы из декорированного класса и выставлял их как свои собственные. Почти как подкласс, за исключением того, что класс декоратора не знает заранее, какие классы он будет декорировать, поэтому он должен скопировать атрибуты по факту.

Конечным результатом является то, что любой экземплярmemoize class становится почти прозрачной оболочкой вокруг фактического класса, который он украсил, за исключением того, что попытка создать его экземпляр (но на самом деле вызывать его) предоставит вам кэшированные копии, когда они станут доступны.

Это было очень полезно для меня. Я просто добавлю, что в моем случае использования также использовались методы классов и, следовательно, потребовалось добавить эти строки после проверки статическим методом:if type(val) is classmethod: self.__dict__[attr] = functools.partial(val.__func__, cls)

Похожие вопросы