是否可以使用 Abstract Base Class 作为 dict 子类的混合?

Is it possible to use an Abstract Base Class as a mixin with a dict subclass?

TL;DR:有兴趣知道是否可以按照我喜欢的方式使用 Abstract Base 类 作为混合,或者我的方法是否从根本上被误导了。

我有一个我一直在做的 Flask 项目。作为我项目的一部分,我实现了 RememberingDict class。它是 dict 的简单子 class,附加了一些额外的功能:它会记住它的创建时间,它知道如何 pickle/save 自己到磁盘,它知道如何open/unpickle 本身来自磁盘:

from __future__ import annotations

import pickle
from datetime import datetime
from typing import Final, Optional, TypeVar, Any, Hashable


FILE_PATH: Final = 'data.pickle'
T = TypeVar('T', bound='RememberingDict')


class RememberingDict(dict):
    def __init__(self, data: Optional[dict[Hashable, Any]] = None) -> None:
        super().__init__(data if data is not None else {})
        self.creation_time: datetime = datetime.now()

    def to_disk(self) -> None:
        """I save a copy of the data to a file"""

        with open(FILE_PATH, 'wb') as f:
            pickle.dump(self, f)

    @classmethod
    def from_disk(cls: type[T]) -> T:
        """I extract a copy of the data from a file"""

        with open(FILE_PATH, 'rb') as f:
            latest_dataset: T = pickle.load(f)

        return latest_dataset

代码在本地开发服务器上对我的目的来说工作得很好,所以一切都很好,但是(由于这里没有必要进入的原因),它在 [=56 上部署时不起作用=] App Engine,因此出于这些目的,我设计了这个替代实现:

from __future__ import annotations

import pickle
from datetime import datetime
from typing import Optional, TypeVar, Hashable, Any
from google.cloud.storage.blob import Blob


def get_google_blob() -> Blob:
"""
Actual implementation unnecessary to go into, 
but rest assured that the real version of this function returns a Blob object,
linked to Google Storage account credentials, 
from which files can be uploaded to, and downloaded from, 
Google's Cloud Storage platform.
"""
    pass


T = TypeVar('T', bound='RememberingDict')


class RememberingDict(dict):
    def __init__(self, data: Optional[dict[Hashable, Any]] = None) -> None:
        super().__init__(data if data is not None else {})
        self.creation_time: datetime = datetime.now()

    def to_disk(self) -> None:
        """I upload a copy of the data to Google's Cloud Storage"""

        get_google_blob().upload_from_string(pickle.dumps(self))

    @classmethod
    def from_disk(cls: type[T]) -> T:
        """I extract a copy of the data from Google's Cloud Storage"""

        latest dataset: T = pickle.loads(get_google_blob().download_as_bytes())
        return latest_dataset

现在,这两种实现都可以正常工作。但是,我想同时保留它们——第一个对开发很有用——但令人讨厌的是,两者之间显然有相当多的重复。它们的 __init__() 功能相同;他们都有一个 to_disk() 将实例保存到文件的方法和 returns None;他们都有一个 from_disk() class 方法,returns 一个 class 的实例被保存到磁盘的某个地方。

理想情况下,我想让它们都继承自一个基础 class,它传递给它们各种类似 dict 的能力,并且还指定 to_disk()from_disk() 方法必须被覆盖才能提供完整的实现。

感觉这个问题ABCs应该可以解决。我尝试了以下方法:

from __future__ import annotations

from datetime import datetime
from typing import Final, Optional, TypeVar, Hashable,  Any
from abc import ABC, abstractmethod
from google.cloud.storage.blob import Blob


T = TypeVar('T', bound='AbstractRememberingDict')


class AbstractRememberingDict(ABC, dict):
    def __init__(self, data: Optional[dict[Hashable, Any]] = None) -> None:
        super().__init__(data if data is not None else {})
        self.creation_time: datetime = datetime.now()
    
    @abstractmethod
    def to_disk(self) -> None: ...

    @classmethod
    @abstractmethod
    def from_disk(cls: type[T]) -> T: ...


FILE_PATH: Final = 'data.pickle'


class LocalRememberingDict(AbstractRememberingDict):
    def to_disk(self) -> None:
        """I save a copy of the data to a file"""

        with open(FILE_PATH, 'wb') as f:
            pickle.dump(self, f)

    @classmethod
    def from_disk(cls: type[T]) -> T:
        """I extract a copy of the data from a file"""

        with open(FILE_PATH, 'rb') as f:
            latest_dataset: T = pickle.load(f)

        return latest_dataset


def get_google_blob() -> Blob:
"""
Actual implementation unnecessary to go into, 
but rest assured that the real version of this function returns a Blob object,
linked to Google Storage account credentials, 
from which files can be uploaded to, and downloaded from, 
Google's Cloud Storage platform.
"""
    pass


class RemoteRememberingDict(AbstractRememberingDict):
    def to_disk(self) -> None:
        """I upload a copy of the data to Google's Cloud Storage"""

        get_google_blob().upload_from_string(pickle.dumps(self))

    @classmethod
    def from_disk(cls: type[T]) -> T:
        """I extract a copy of the data from Google's Cloud Storage"""

         latest_dataset: T = pickle.loads(get_google_blob().download_as_bytes())
         return latest_dataset 

然而,使用 ABC 作为混合(而不是作为唯一的基础 class)似乎与 @abstractmethod 装饰器混淆,这样继承的 class如果 es 未能实现所需的抽象方法,则不再引发异常。

理想情况下,我希望我的基础 class 继承标准 Python dict 的所有功能,但也指定某些方法必须在继承 classes 表示要实例化的实例。

我正在尝试做的事情是可行的,还是我的方法从根本上被误导了?

(顺便说一句:我更感兴趣的是 ABC 的工作方式,而不是缓存 Web 应用程序数据结构的最佳方式等——我相信可能会有缓存数据的更好方法,但这是我的第一个 Flask 项目,目前我的方法对我来说效果很好。)

您可以通过 subclassing collections.UserDict 来解决 subclassing dict 的问题。正如文档所说:

Class that simulates a dictionary. The instance’s contents are kept in a regular dictionary, which is accessible via the data attribute of UserDict instances. If initialdata is provided, data is initialized with its contents; note that a reference to initialdata will not be kept, allowing it be used for other purposes.

本质上,它是 dict 周围的薄常规 class 包装器。您应该能够像使用 AbstractRememberingDict.

一样将其与多重继承一起用作抽象基础 class