在槽中使用Python描述符

9 投票
3 回答
3789 浏览
提问于 2025-04-16 11:19

我想在一个使用了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 个回答

1

@Glenn Maynard的回答是个不错的选择。

不过我想指出一个问题,就是提问者的问题中有个小错误(我还没有足够的声望,无法在他的问题下评论):

下面这个测试在实例没有__dict__变量时会报错:

        if self.name not in instance.__dict__:

所以,这里有一个通用的解决方案,它首先尝试访问__dict__变量(这也是默认的方式),如果失败了,就使用getattrsetattr

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__,这个方法就不会按预期工作)

希望这能帮到你。

4

虽然这个方法的价值有点可疑,但如果你愿意在子类中放置第一个 __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__ 中寻找合适的描述符(或者从你的 __get__ 中的 instance 开始)。

附言

好吧……如果你真的想使用一个类,你可以使用以下的改编方法:

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 )

如果你坚持要让事情变得复杂,你可以在元类或类装饰器中进行赋值。

13

不要把槽(slot)和实例方法(instance method)用同样的名字。要用不同的名字,并且通过属性来访问槽,而不是通过 __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

撰写回答