簡體   English   中英

是否可以在已定義__slots__的類的裝飾器中添加__slots__?

[英]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_TypePyMemberDef結構),它具有一個偏移參數,其中插槽值在內部對象存儲中對齊。 而且你根本就沒有在公共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',)

現在可以使用xy

>>> c = C()
>>> c.y = 10
>>> c.x = 10

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM