Python: кешировать значения дескрипторов

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

class Pro(object):
    """My descriptor class"""

    def __init__(self):
        self.value = None

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        self.value = value


class Something(object):
    """My simple class"""
    pro = Pro()

a = Something()
a.pro = 1

b = Something()
b.pro = 2

print(a.pro, b.pro) # At first, I've expected them to be 1 and 2

Я почему-то думал, что атрибут pro будет уникальным экземпляром Pro для каждого экземпляра Something, очевидно, я ошибался. Похоже, я должен использовать что-то вроде instance._value вместо self.value внутри __set__ и __get__, но я действительно надеялся скрыть все внутри класса Pro. Это вообще возможно? Спасибо!


person Anton Melnikov    schedule 29.01.2015    source источник
comment
Вы видели это?   -  person Lemming    schedule 29.01.2015
comment
@Lemming, выглядит довольно аккуратно, спасибо, но я надеялся решить это только с помощью стандартной библиотеки :)   -  person Anton Melnikov    schedule 29.01.2015
comment
Справедливо. Кстати, как и большинство пакетов Python, этот имеет открытый исходный код. фактический код довольно мал, а версия без потоков не требует дополнительных пакетов.   -  person Lemming    schedule 29.01.2015
comment
Источник был интересный, спасибо, не видел его до сих пор.   -  person Anton Melnikov    schedule 29.01.2015


Ответы (1)


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

class Meta(type):
    def __new__(cls, name, bases, dct):
        for k, v in dct.items():
            if isinstance(v, Pro):
                # add an _ in front of the name
                v.name = '_' + k
        return super(Meta, cls).__new__(cls, name, bases, dct)


class Pro(object):

    def __get__(self, ins, typ):
        return getattr(ins, self.name)

    def __set__(self, ins, val):
            setattr(ins, self.name, val)

class Something(object):
    """My simple class"""
    __metaclass__ = Meta
    pro = Pro()

a = Something()
a.pro = 1

b = Something()
b.pro = 2

Демонстрация:

>>> a.pro, b.pro
(1, 2)
>>> a.__dict__
{'_pro': 1}
>>> b.__dict__
{'_pro': 2}
>>> a.pro = 100
>>> a.__dict__
{'_pro': 100}

Таким образом, нет никакого способа сделать скрытые атрибуты в экземплярах Something, верно?

Нет, есть. Вы можете хранить словарь в экземпляре Pro, в котором хранятся все значения, относящиеся к каждому экземпляру Something. Например, если экземпляры Something можно хешировать, вы можете сделать что-то подобное, используя weakref.WeakKeyDictionary< /а>. WeakKeyDictionary позаботится о том, чтобы после того, как у экземпляра Something не осталось ссылок, он был немедленно собран мусором, что невозможно с обычным dict:

from weakref import WeakKeyDictionary

class Pro(object):

    def __init__(self):
        self.instances = WeakKeyDictionary()

    def __get__(self, ins, typ):
        return self.instances[ins]

    def __set__(self, ins, val):
        self.instances[ins] = val

p = Pro()

class Something(object):
    """My simple class"""
    pro = p

a = Something()
a.pro = 1

b = Something()
b.pro = 2

print a.pro, b.pro

print p.instances.items()
del a
print p.instances.items()

Вывод:

1 2
[(<__main__.Something object at 0x7fb80d0d5310>, 1), (<__main__.Something object at 0x7fb80d0d5350>, 2)]
[(<__main__.Something object at 0x7fb80d0d5350>, 2)]
person Ashwini Chaudhary    schedule 29.01.2015
comment
Значит, нет способа сделать скрытые атрибуты в экземплярах Something, верно? - person Anton Melnikov; 29.01.2015
comment
Если вы хотите «кэшировать» значение для экземпляра, где-то это значение должно быть связано с экземпляром. Атрибуты Python в основном делают это, используя словарь dict объекта. Вы можете изменить это, указав словарь в описании с экземпляром в качестве ключа, но основная проблема остается в том, что вам нужно сопоставление экземпляра со значением или наоборот. - person Geoff Genz; 29.01.2015
comment
@AntonMelnikov Определенно, это тоже возможно, смотрите обновление. - person Ashwini Chaudhary; 29.01.2015
comment
@AshwiniChaudhary Хорошо, теперь, кажется, я понял. Спасибо за хорошее объяснение. - person Anton Melnikov; 29.01.2015