使用带有插槽的Python描述符

时间:2011-02-06 09:15:13

标签: python descriptor slots

我希望能够在具有插槽优化的类中使用python描述符:

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

我遇到的问题是如何实现描述符类,以便能够在调用描述符对象的类实例中存储值。通常的解决方案看起来如下所示,但由于在C类中调用“slots”时不再定义“dict”,因此无法正常工作:

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 个答案:

答案 0 :(得分:10)

不要声明与插槽相同的名称和实例方法。使用不同的名称,并将该插槽作为属性访问,而不是通过__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

答案 1 :(得分:2)

虽然值得怀疑,但如果可以将第一个__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 )

如果你坚持不可摧毁,你可以在元类或类装饰器中进行赋值。

答案 2 :(得分:1)

@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__

,则无法按预期工作

希望这有帮助。