Использование дескрипторов Python со слотами

Я хочу иметь возможность использовать дескрипторы 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

person Radu M.    schedule 06.02.2011    source источник


Ответы (3)


Не объявляйте одно и то же имя как слот и как метод экземпляра. Используйте разные имена и обращайтесь к слоту как к атрибуту, а не через __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
person Glenn Maynard    schedule 06.02.2011
comment
После этого я вижу, что C.__dict__ создан. Разве это не то, чего мы пытаемся избежать? В любом случае, глядя на документацию, __slots__ уже использует интерфейс дескриптора для своих собственных целей, так что это может быть действительно сложно. - person dhill; 23.01.2013
comment
Используя Python 3, C имеет только mappinyproxy, а не dict. Что еще более важно, экземпляры C не имеют __dict__ - person oz123; 09.10.2016

Хотя ценность и сомнительная ценность, следующий трюк сработает, если можно поместить первый __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 )

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

person shaunc    schedule 14.11.2011

Ответ @Glenn Maynard - хороший.

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

Следующий тест вызывает ошибку, если в экземпляре нет переменной __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__)

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

person Yahya Abou Imran    schedule 26.08.2017