在槽中使用Python描述符
我想在一个使用了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 个回答
@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__
,这个方法就不会按预期工作)
希望这能帮到你。
虽然这个方法的价值有点可疑,但如果你愿意在子类中放置第一个 __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 )
如果你坚持要让事情变得复杂,你可以在元类或类装饰器中进行赋值。
不要把槽(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