【问题标题】:Is it possible to use an Abstract Base Class as a mixin with a dict subclass?是否可以将抽象基类用作带有 dict 子类的 mixin?
【发布时间】:2021-08-30 19:37:18
【问题描述】:

TL;DR:想知道是否可以按照我的意愿使用抽象基类作为 mixin,或者我的方法是否从根本上被误导了。

我有一个我一直在从事的 Flask 项目。作为我项目的一部分,我实现了一个RememberingDict 类。它是dict 的一个简单子类,添加了一些额外的功能:它会记住它的创建时间,它知道如何将自己腌制/保存到磁盘,并且它知道如何从磁盘打开/取消腌制自己:

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

代码在本地开发服务器上非常适合我的目的,所以一切都很好,但是(由于这里没有必要进入的原因),它在 Google 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() 方法,可以将实例保存到文件并返回None;他们都有一个from_disk() 类方法,它返回一个已保存到某个磁盘的类的实例。

理想情况下,我想让它们都继承自一个基类,它传递给它们各种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 作为 mixin(而不是作为唯一的基类)似乎会与 @abstractmethod 装饰器混淆,因此如果继承的类无法实现所需的抽象方法,则不会再引发异常.

理想情况下,我希望我的基类继承标准 Python dict 的所有功能,但还指定必须在继承的类中实现某些方法才能实例化实例。

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

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

【问题讨论】:

  • 这是 abc 和 C 类的一个已知问题 - 如果一个类继承自用 C 编写的非 object 类,则抽象强制不起作用。
  • 确实应该记录在案,但仍然没有。
  • 谢谢!有没有其他方法可以让我做类似的事情,你知道吗?或者我可以在任何地方阅读更多相关信息?对 ABC 来说还是相当新的!
  • 英雄,谢谢——一直试图在其他地方找到有关该问题的参考,但什么也看不到。编辑:哇,那个错误已经打开了很长时间。

标签: python inheritance abc abstract-base-class


【解决方案1】:

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

模拟字典的类。实例的内容保存在常规字典中,可通过 UserDict 实例的 data 属性访问。如果提供了initialdata,则使用其内容初始化数据;请注意,不会保留对 initialdata 的引用,允许将其用于其他目的。

本质上,它是一个围绕dict 的薄常规类包装器。您应该能够将它与多重继承一起用作抽象基类,就像使用 AbstractRememberingDict 一样。

【讨论】:

  • 很好——比错误报告中建议的解决方案优雅得多。
  • __dict____slots__ 与此无关。
  • @user2357112supportsMonica 你说得对,我错了。我已经相应地更新了答案。
猜你喜欢
  • 1970-01-01
  • 2020-07-18
  • 2016-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-07
相关资源
最近更新 更多