[英]Is it possible to add __slots__ in decorator of class that already defines __slots__?
首先讓我說我了解插槽和元類在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_Type
和PyMemberDef
結構),它具有一個偏移參數,其中插槽值在內部對象存儲中對齊。 而且你根本就沒有在公共Python API中操縱這些東西的工具。 交換靈活性以減少內存使用(同樣,在CPython中,因為在PyPy中,您可以為所有類自動獲得相同的內存效果)。
如果絕對需要修改__slots__
,你可以編寫C擴展(或使用ctypes
)並執行它,但這不是一個可靠的解決方案。
您可以使用元類執行此操作:
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.