gpt4 book ai didi

python - 是否可以在已经定义了 __slots__ 的类的装饰器中添加 __slots__?

转载 作者:太空宇宙 更新时间:2023-11-04 10:41:55 26 4
gpt4 key购买 nike

首先让我说我了解槽和元类在 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 创建一个类,它使用现有类的槽和描述符而不是该类的后代?

如果答案是“否”,我将不胜感激有关为什么不这样做的消息来源。

最佳答案

不,不幸的是,在创建类之后无法对 __slots__ 做任何事情(那是调用它们的装饰器的时候)。唯一的方法是使用元类,并在调用 type.__new__ 之前修改/添加 __slots__

此类元类的示例:

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_TypePyMemberDef 结构),它有一个偏移参数,其中插槽值在内部对象存储中对齐。而且您根本没有在公共(public) Python API 中操作这些东西的工具。您以灵 active 换取更少的内存使用(同样,在 CPython 中,就像在 PyPy 中一样,您会自动为所有类获得相同的内存效果)。

如果绝对需要修改 __slots__,您可能可以编写一个 C 扩展(或使用 ctypes)并执行它,但这不是一个可靠的解决方案.

关于python - 是否可以在已经定义了 __slots__ 的类的装饰器中添加 __slots__?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20086908/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com