Использование дескрипторов Python со слотами - PullRequest
9 голосов
/ 06 февраля 2011

Я хочу иметь возможность использовать дескрипторы Python в классе, который имеет оптимизацию слотов:

class C(object):    
    __slots__ = ['a']
    a = MyDescriptor('a')
    def __init__(self, val):
        self.a = val

Проблема, которую я имею, состоит в том, как реализовать класс дескриптора, чтобы иметь возможность хранить значения в экземпляре класса, который вызывает объект дескриптора. Обычное решение будет выглядеть так, как показано ниже, но не будет работать, поскольку «dict» больше не определяется, когда «slots» вызывается в классе C:

class MyDescriptor(object):
    __slots__ = ['name']    
    def __init__(self, name_):
        self.name = name_
    def __get__(self, instance, owner):
        if self.name not in instance.__dict__:
            raise AttributeError, self.name
        return instance.__dict__[self.name]     
    def __set__(self, instance, value):
        instance.__dict__[self.name] = value

Ответы [ 3 ]

10 голосов
/ 06 февраля 2011

Не объявляйте то же имя, что и слот, и как метод экземпляра.Используйте разные имена и получите доступ к слоту как к атрибуту, а не через __dict__.

class MyDescriptor(object):
    __slots__ = ['name']
    def __init__(self, name_):
        self.name = name_
    def __get__(self, instance, owner):
        return getattr(instance, self.name)
    def __set__(self, instance, value):
        setattr(instance, self.name, value)

class C(object):
    __slots__ = ['_a']
    a = MyDescriptor('_a')
    def __init__(self, val):
        self.a = val

foo = C(1)
print foo.a
foo.a = 2
print foo.a
2 голосов
/ 14 ноября 2011

Несмотря на сомнительную ценность, следующий трюк сработает, если будет нормально поставить первый __slots__ в подкласс.

class A( object ):
    __slots__ = ( 'a', )

class B( A ):
    __slots__ = ()

    @property
    def a( self ):
        try:
            return A.a.__get__( self )
        except AttributeError:
            return 'no a set'

    @a.setter
    def a( self, val ):
        A.a.__set__( self, val )

(Вы можете использовать свой собственный дескриптор, а не свойство.) С этими определениями:

>>> b = B()
>>> b.a
'no a set'
>>> b.a = 'foo'
>>> b.a
'foo'

Насколько я понимаю, __slots__ реализован со своим собственным дескриптором, поэтому другой дескриптор после __slots__ в том же классе просто перезапишет. Если вы хотите разработать эту технику, вы можете найти дескриптор кандидата в self.__class__.__mro__ (или начинать с instance в вашем собственном __get__).

Постскриптум

Хорошо ... хорошо, если вы действительно хотите использовать один класс, вы можете использовать следующую адаптацию:

class C( object ):
    __slots__ = ( 'c', )

class MyDescriptor( object ):

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

    def __get__( self, inst, owner = None ):
        try:
            return self.slots_descriptor.__get__( inst, owner )
        except AttributeError:
            return 'no c'

    def __set__( self, inst, val ):
        self.slots_descriptor.__set__( inst, val )

C.c = MyDescriptor( C.c )

Если вы настаиваете на непостижимости, вы можете выполнить назначение в метаклассе или в декораторе класса.

1 голос
/ 26 августа 2017

Хороший ответ @Glenn Maynard.

Но я хотел бы указать на проблему в вопросе ОП (я не могу добавить комментарий к его вопросу, поскольку у меня недостаточно репутациипока):

Следующий тест вызывает ошибку, когда экземпляр не имеет переменной __dict__:

        if self.name not in instance.__dict__:

Итак, вот универсальное решение, которое пытается получить доступ ксначала переменную __dict__ (которая по умолчанию используется в любом случае) и, в случае ее сбоя, используйте getattr и setattr:

class WorksWithDictAndSlotsDescriptor:

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

    def __get__(self, instance, owner):
        try:
            return instance.__dict__[self.attr_name]
        except AttributeError:
            return getattr(instance, self.attr_name)

    def __set__(self, instance, value):
        try:
            instance.__dict__[self.attr_name] = value
        except AttributeError:
            setattr(instance, self.attr_name, value)

(работает, только если attr_name не совпадает симя переменной реального экземпляра, или у вас будет RecursionError, как указано в принятом ответе)

(не будет работать должным образом, если есть оба __slots__ И __dict__)

Надеюсь, это поможет.

...