使用带槽的 Python 描述符

发布于 2024-10-15 20:31:46 字数 678 浏览 10 评论 0原文

我希望能够在具有插槽优化的类中使用 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

I want to be able use python descriptors in a class which has the slots optimization:

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

The problem I have is how to implement the descriptor class in order to be able to store values in the class instance which invokes the descriptor object. The usual solution would look like the one below but will not work since "dict" is no longer defined when "slots" is invoked in the C class:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

九厘米的零° 2024-10-22 20:31:46

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

Don't declare the same name as a slot and as an instance method. Use different names, and access the slot as an attribute, not via __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
俯瞰星空 2024-10-22 20:31:46

尽管价值可疑,但如果可以将第一个 __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 )

如果您坚持难以理解,您可以在元类或类装饰器中进行分配。

Though of dubious value, the following trick will work, if it is ok to put the first __slots__ in a subclass.

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 )

(You can use your own descriptor rather than property.) With these definitions:

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

As far as I understand, __slots__ is implemented with its own descriptor, so another descriptor after __slots__ in the same class would just overwrite. If you want to elaborate this technique, you could search for a candidate descriptor in self.__class__.__mro__ (or starting with instance in your own __get__).

Postscript

Ok ... well if you really want to use one class, you can use the following adaptation:

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 )

If you insist on inscrutability, you can make the assignment in a metaclass or a class decorator.

撑一把青伞 2024-10-22 20:31:46

@Glenn Maynard 的答案很好。

但我想指出OP问题中的一个问题(我无法对他的问题添加评论,因为我还没有足够的声誉):

当实例没有 __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__ ,则不会按预期工作)

希望这会有所帮助。

The @Glenn Maynard's answer is the good one.

But I would like to point at a problem in the OP's question (I can't add a comment to his question since I havn't enough reputation yet):

The following test is raising an error when the instance hasn't a __dict__ variable:

        if self.name not in instance.__dict__:

So, here is an a generic solution that tries to acces to the __dict__ variable first (which is the default anyway) and, if it fails, use getattr and 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)

(Works only if the attr_name is not the same as the real instance variable's name, or you will have a RecursionError as pointed to in the accepted answer)

(Won't work as expected if there is both __slots__ AND __dict__)

Hope this helps.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文