__slots__ 到底如何工作以及如何实现自己的?

问题描述 投票:0回答:2

我一直在闲逛

__slots__
并搜索了一下它们,但我仍然对一些细节感到困惑:

我知道

__slots__
会生成某种描述符:

>>> class C:
...     __slots__ = ('x',)
...

>>> C.x
<member 'x' of 'C' objects>

>>> C.x.__get__
<method-wrapper '__get__' of member_descriptor object at 0x7f001de183a8>

>>> C.x.__get__
<method-wrapper '__get__' of member_descriptor object at 0x7f001de183a8>

但我想知道:这些值实际存储在哪里?

因为到目前为止我见过的描述符的常见配方/习惯用法是:

>>> class Descr:
...
...     def __init__(self, attrname):
...         self.attrname = attrname
...
...     def __get__(self, obj, cls):
...         return obj.__dict__[self.attrname]
...
...     def __set__(self, obj, val):
...         obj.__dict__[self.attrname] = val
...
... class C:
...
...     def __init__(self, x):
...         self.x = x

__slots__
另外使用时,有两个问题:

  1. 名称冲突:
>>> class C:
...
...     __slots__ = ('x',)
...
...     def __init__(self, x):
...         self.x = x
...
...     x = Descr('x')
...
Traceback (most recent call last)
 ...
ValueError: 'x' in __slots__ conflicts with class variable

因此,解决方法是将实际属性命名为“_x”。

  1. __dict__
    (除非您明确将其添加到
    __slots__
    ):
>>> class C:
...
...     __slots__ = ('_x',)
...
...     def __init__(self, x):
...         self._x = x
...
...     x = Descr('_x')
...

>>> c = C(0)

>>> c.x
Traceback (most recent call last)
 ...
AttributeError: 'C' object has no attribute '__dict__'

所以你必须使用

getattr()
setattr()
来代替。

您可以以通用描述符结尾,该描述符可以与

__dict__
__slots__
:

一起使用
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)

(如果同时存在

__slots__
__dict__
,则无法正常工作。)

但最近我找到了一种用包装器劫持

__get__
__set__
方法的方法:

def slot_wrapper(cls, slotname, slot, descriptor):
    '''Wrapper replacing a slot descriptor with another one'''

    class InnerDescr(descriptor):

        def __get__(self, obj, cls):
            print("Hijacking __get__ method of a member-descriptor")
            return slot.__get__(obj, cls)

        def __set__(self, obj, val):
            print("Hijacking __set__ method of a member-descriptor")
            slot.__set__(obj, val)

    return InnerDescr(slotname, cls)

(用例是添加类型检查和数据验证,以及强制封装。)

因此,在创建类之后(或在使用元类之前),您可以为插槽和描述符保留相同的名称。

效果很好,但感觉有点脏……我认为最好实现我自己的插槽以继续使用描述符的一个名称。但我不知道怎么办。

所以这里有一些问题:

  1. 实际存储的值在哪里(因为没有字典)?我以为这是用 C 实现的东西,不能直接用 Python 代码访问。

  2. 如何在不损失性能优化的情况下实现纯Python等效项?

  3. 坚持使用我的包装纸更好吗?

python properties getter-setter instance-variables
2个回答
4
投票

这些值实际上存储在哪里(因为没有字典)?我以为这是用 C 实现的东西,不能直接用 Python 代码访问。

内存直接在对象本身中分配给

PyObject *
指针。您可以在
Objects/typeobject.c
中查看处理方式。生成的描述符将访问为其在适当类型的对象中的槽保留的内存。

如何在不损失性能优化的情况下实现纯Python等效项?

你不能。您能得到的最接近的结果是延长

tuple

最好还是用我的包装纸?

不。不要将插槽命名为与您希望由其他描述符处理的属性相同的名称。这样做就像将两个非槽描述符命名为相同的东西;对于如何处理具有该名称的属性,您表达了两种相互矛盾的意图。


0
投票

从您的整体前景来看,我一直致力于成员描述符上的自定义设置器的执行解决方案一段时间,这是迄今为止我想出的最好的解决方案:
(在 wine 上使用 Anaconda 2.3.0 (Python 3.4.3) 进行测试,在 Linux 上使用 Python 3.5.2 进行测试)

注意:这个解决方案并不试图成为Pythonic,也不作为问题的直接答案,而是期望结果的替代实现。

class A(object):
    __slots__ = ( 'attr', )

attrget = A.__dict__['attr'].__get__
attrset = A.__dict__['attr'].__set__

def setter( obj, val ):
    if type( val ) is int:
        attrset( obj, val )
    else:
        raise TypeError( 'int type expected, got %s'%type( val ) )

setattr( A, 'attr', property( attrget, setter ) )
# ^ this is safer than A.__dict__['attr'] as a mapping_proxy is read-only

有趣的事实: 对于

i = A()
,虽然
i.attr
效率较低(更多 CPU 峰值),但与基本的 member_descriptor 相比,它实际上平均快了约 20ns(相对于我的机器)。
这同样适用于没有自定义设置器的
i.attr = 0

(请随意自己测试,timeit 应该类似地工作(除了它包括 for 循环的时间)。(请注意,我的测试没有更改该值),并确保运行多个测试)

这是 Python 3.5.2 在 Linux 上的测试结果:

10000 iterations; threshold of min + 250ns:
________code___|_______min______|_______max______|_______avg______|_efficiency
⡇⢠⠀⠀⠀⠀⠀⠀⠀⠀⡄⠀⢰⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀
⣿⣾⣴⣄⣤⣄⣄⣰⣦⣤⣧⣰⣼⣤⣤⣆⡀⡀⢀⣄⣠⣀⣴⣶⣦⣤⣤⣦⣶⣄⣄⣠⣄⣴⣤⣶⣸⣦⣤⣤⣴⣴⣴⣷⣶⣴⣦⣤⣶⣆⣤⣤⣦⣶⣤⣴⣠⣷⣤⣶⣾⣷⣤⣆
    i.mdsc = 1 |      564.964ns |    17341.983ns |      638.568ns |  88.473%
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⢠⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣷⣶⣶⣴⣤⣤⣦⣶⣶⣦⣧⣼⣼⣴⣄⣦⡄⣄⣀⣄⣴⡄⣼⣾⣶⣦⣴⣧⣶⣄⣄⣴⣦⣾⣴⣴⣤⣦⣆⣶⣴⣤⣴⣷⣿⣼⣾⣦⣷⣦⣧⣾⣦⣿⣤⣴⣤⣿⣤⣧⣾⣷⣶⣧
    i.prop = 1 |      538.013ns |     8267.001ns |      624.045ns |  86.214%
10000 iterations; threshold of min + 175ns:
____code___|_______min______|_______max______|_______avg______|_efficiency
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡆
⣇⣴⣠⣤⣠⣄⣤⣄⣀⣀⣀⡀⣀⣀⣀⣄⣠⣠⣄⣦⣤⣤⣄⣤⣤⣠⣤⣧⣤⣤⣠⣤⣤⣤⣤⣤⣤⣼⣤⣤⣤⣶⣤⣶⣦⣤⣀⣄⣤⣤⣤⣤⣤⣤⣤⣤⣤⣶⣦⣷⣤⣶⣄⣧
    i.mdsc |      364.962ns |    27579.023ns |      411.621ns |  88.665%
⡇⢠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀
⣷⣾⣦⣦⣴⣠⣿⣦⣠⣠⣄⣀⣄⡄⣠⣴⣠⣤⣴⣴⣦⣼⣤⣦⣤⣤⣤⣧⣴⣶⣦⣶⣶⣶⣶⣶⣦⣶⣶⣶⣷⣿⣷⣿⣷⣾⣶⣶⣶⣾⣾⣾⣶⣶⣴⣶⣴⣾⣷⣿⣿⣷⣶⣶
    i.prop |      341.039ns |     2000.015ns |      400.054ns |  85.248%

最后,如果您反对这个答案,请解释原因。
(如果您的测试与我的测试不符,请不要投反对票)
^ 我的结果只是一个示例,用于显示性能的小幅提升,不应只看表面价值。
© www.soinside.com 2019 - 2024. All rights reserved.