首先我要说明一下我理解插槽和元类在Python中是如何工作的。和两个人一起玩,我遇到了一个有趣的问题。这是一个最小的例子:
def decorator(cls):
dct = dict(cls.__dict__)
dct['__slots__'] = ('y',)
return type('NewClass', cls.__bases__, dct)
@decorator
class A(object):
__slots__= ('x',)
def __init__(self):
self.x = 'xx'
A()
这会产生以下异常:
Traceback (most recent call last):
File "p.py", line 12, in <module>
A()
File "p.py", line 10, in __init__
self.x = 'xx'
TypeError: descriptor 'x' for 'A' objects doesn't apply to 'NewClass' object
现在,我知道为什么会发生这种情况:为插槽x
创建的描述符必须能够引用插槽的保留空间。只有A类的实例或A的子类实例具有此保留空间,因此只有那些实例才能使用描述符x
。在上面的例子中,元类创建了一个新类型,它是A的基类的子级,但不是A本身,所以我们得到了异常。很简单。
当然,在这个简单的示例中,decorator
的以下两个定义中的任何一个都可以解决问题:
def decorator(cls):
dct = dict(cls.__dict__)
dct['__slots__'] = ('y',)
return type('NewClass', (cls,) + cls.__bases__, dct)
def decorator(cls):
class NewClass(cls):
__slots__ = ('y',)
return NewClass
但是这些解决方法并不完全与原始相同,因为它们都将A添加为基类。他们可能会在更复杂的设置中失败。例如,如果继承树更复杂,则可能会遇到以下异常:TypeError: multiple bases have instance lay-out conflict
。
所以我非常具体的问题是:
是否有办法通过调用type
创建一个新类,它修改现有类的__slots__
属性,但不会将现有类添加为新课程?
编辑:
我知道严格的元类是我上面的例子的另一种解决方法。有很多方法可以让最小的例子工作,但我的问题是通过new
创建一个基于现有类的类,而不是如何使示例工作。对不起,感到困惑。
编辑2:
评论中的讨论使我得到了比我最初提出的问题更精确的问题:
是否可以通过调用type
创建一个类,该类使用现有类的插槽和描述符而不是该类的后代?
如果答案是“否”,我会很感激为什么不这样做。
答案 0 :(得分:3)
不,遗憾的是,在创建类之后无法对__slots__
执行任何操作(并且在调用它们的装饰器时也是如此)。唯一的方法是使用元类,并在调用__slots__
之前修改/添加type.__new__
。
这种元类的一个例子:
class MetaA(type):
def __new__(mcls, name, bases, dct):
slots = set(dct.get('__slots__', ()))
slots.add('y')
dct['__slots__'] = tuple(slots)
return super().__new__(mcls, name, bases, dct)
class BaseA(metaclass=MetaA):
pass
class A(BaseA):
__slots__ = ('x',)
def __init__(self):
self.x = 1
self.y = 2
print(A().x, A().y)
没有元类,你可以做一些魔法并从定义的类复制所有内容并动态创建一个新的,但该代码闻起来;)
def decorator(cls):
slots = set(cls.__slots__)
slots.add('y')
dct = cls.__dict__.copy()
for name in cls.__slots__:
dct.pop(name)
dct['__slots__'] = tuple(slots)
return type(cls)(cls.__name__, cls.__bases__, dct)
@decorator
class A:
__slots__ = ('x',)
def __init__(self):
self.x = self.y = 42
print(A().x, A().y)
这样的代码的主要缺点是,如果有人在你的之前应用另一个装饰器,并且,让我们说,在某个地方创建对装饰类的引用,那么它们最终会存储对不同的引用类。元类相同 - 它们将执行两次。因此,元类方法更好,因为没有副作用。
在创建类之后,为什么无法真正更改__slots__
的确切答案取决于您正在使用的python解释器的实现细节。例如,在CPython中,对于您定义的每个插槽,类都有一个描述符(请参阅CPython源代码中的PyMemberDescr_Type
&amp; PyMemberDef
结构),其中有一个偏移参数,其中插槽值在哪里对齐内部对象存储。而且你根本就没有在公共Python API中操纵这些东西的工具。交换灵活性以减少内存使用(同样,在CPython中,因为在PyPy中,您可以为所有类自动获得相同的内存效果)。
如果绝对需要修改__slots__
,您可以编写C扩展(或使用ctypes
)并执行此操作,但这不是一个可靠的解决方案。< / p>
答案 1 :(得分:1)
你可以用元类来做到这一点:
class MetaSlot(type):
def __new__(mcs, name, bases, dic):
dic['__slots__'] += ('y',)
return type.__new__(mcs, name, bases, dic)
class C(metaclass=MetaSlot): # Python 3 syntax
__slots__ = ('x',)
现在可以使用x
和y
:
>>> c = C()
>>> c.y = 10
>>> c.x = 10