如何实际工作`__slots__`以及如何实现自己的?__slots__`

时间:2017-09-04 00:03:33

标签: python properties getter-setter instance-variables

我一直在和__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”

2)否__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) 方法的方法:

 <ng-template #target></ng-template>

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

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

运作良好,但感觉有点脏......我认为实现自己的插槽以继续使用一个名称作为descritpor可能会更好。但我不知道如何。

所以有一些讯问:

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

  2. 如何在不失去性能优化的情况下实现纯Python等价物?

  3. 坚持使用我的包装器是否可以接受?

2 个答案:

答案 0 :(得分:4)

  

在哪里实际存储值(因为没有字典)?我认为它是用C语言实现的,而不能用Python代码直接访问。

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

  

如何在不失去性能优化的情况下实现纯Python等价物?

你做不到。最接近的就是扩展tuple

  

坚持使用我的包装材料是可以的吗?

没有。不要将您的插槽命名为与您希望由其他描述符处理的属性相同的内容。这样做就像命名两个非槽描述符一样;你是如何表达你想要用这个名称处理属性的两个矛盾的意图。

答案 1 :(得分:0)

看看你的整体前景,我一直在为成员描述符上的自定义设置者提供表演性解决方案已有一段时间了,这是迄今为止我提出的最佳方案:
(在wine上使用Anaconda 2.3.0(Python 3.4.3)进行测试,在linux上使用Python 3.5.2进行测试)

注意:此解决方案不是pythonic,也不是对问题的直接回答,而是对所需结果的替代实现。

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

attrget = A['attr'].__get__
attrset = A['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峰值较多),但它实际上大约快20秒(相对于我的机器而言) )与基本的member_descriptor平均相比 这同样适用于没有自定义设置器的i.attr = 0 (随意测试自己,timeit应该类似地工作(除了它包括for循环的时间)。(注意我的测试没有改变值),并确保运行多个测试)

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

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%


最后,如果您对此答案进行了回答,请解释原因 (如果您的测试与我的测试不相符,请不要投票) ^我的结果只是表现出性能小幅提升的一个例子,不应该按照面值进行。