简体   繁体   English

是否可以使用 Abstract Base Class 作为带有 dict 子类的 mixin?

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

TL;DR: Interested in knowing if it's possible to use Abstract Base Classes as a mixin in the way I'd like to, or if my approach is fundamentally misguided. TL;DR:有兴趣了解是否可以按照我的意愿使用抽象基类作为混合,或者我的方法是否从根本上被误导。

I have a Flask project I've been working on.我有一个我一直在从事的 Flask 项目。 As part of my project, I've implemented a RememberingDict class.作为我项目的一部分,我实现了一个RememberingDict class。 It's a simple subclass of dict , with a handful of extra features tacked on: it remembers its creation time, it knows how to pickle/save itself to a disk, and it knows how to open/unpickle itself from a disk:它是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

The code works really nicely for my purposes while on a local development server, so all is good, but (for reasons it is unnecessary here to go into), it doesn't work when deploying it on Google App Engine, so for those purposes, I designed this alternative implementation:该代码在本地开发服务器上非常适合我的目的,所以一切都很好,但是(由于此处不需要 go 的原因),在 Google App Engine 上部署它时它不起作用,所以出于这些目的,我设计了这个替代实现:

from __future__ import annotations

import pickle
from datetime import datetime
from typing import Optional, TypeVar
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

Now, both of these implementations work fine.现在,这两种实现都可以正常工作。 However, I want to keep them both -- the first one is useful for development -- but the annoying thing is that there's obviously a fair amount of repetition between the two.然而,我想保留它们——第一个对开发很有用——但令人讨厌的是,两者之间显然有相当多的重复。 Their __init__() functions are identical;它们的__init__()函数是相同的; they both have a to_disk() method that saves the instance to a file and returns None ;它们都有一个to_disk()方法,可以将实例保存到文件并返回None and they both have a from_disk() classmethod that returns an instance of the class that's been saved to a disk somewhere.并且它们都有一个from_disk()类方法,该方法返回一个 class 的实例,该实例已保存到某处的磁盘中。

Ideally, I would like to have them both inherit from a base class, which passes them a variety of dict -like abilities, and also specifies that the to_disk() and from_disk() methods must be overridden in order to provide a complete implementation.理想情况下,我希望它们都从基础 class 继承,这会传递给它们各种类似dict的能力,并且还指定必须重写to_disk()from_disk()方法以提供完整的实现。

This feels like a problem that ABC s should be able to solve.这感觉像是ABC应该能够解决的问题。 I tried the following:我尝试了以下方法:

from __future__ import annotations

from datetime import datetime
from typing import Final, Optional, TypeVar, Hashable
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 

However, using the ABC as a mixin (rather than as the sole base class) appears to mess with the @abstractmethod decorator, such that inherited classes no longer raise an exception if they fail to implement the required abstract methods.但是,使用ABC作为 mixin(而不是作为唯一的基类)似乎会与@abstractmethod装饰器混淆,因此如果继承的类无法实现所需的抽象方法,它们将不再引发异常。

Ideally, I'd like my base class to inherit all the features of a standard Python dict , but also specify that certain methods must be implemented in inherited classes for instances to be instantiated.理想情况下,我希望我的基础 class 继承标准 Python dict的所有功能,但还指定必须在继承的类中实现某些方法才能实例化实例。

Is what I'm trying to do possible, or is my approach fundamentally misguided?我正在尝试做的事情是可能的,还是我的方法从根本上被误导了?

(As an aside: I'm more interested in the way ABC s work than about the best way to cache data structures for a web app, etc. -- I'm sure there may be better ways of caching data, but this is my first Flask project and my way's working great for me at the moment.) (顺便说一句:我对ABC的工作方式更感兴趣,而不是为 web 应用程序缓存数据结构的最佳方法等。我确信可能有更好的缓存数据方法,但这是我的第一个 Flask 项目,我的方式目前对我来说很好。)

You can get around the problems of subclassing dict by subclassing collections.UserDict instead.您可以通过子类collections.UserDict来解决子类化dict的问题。 As the docs say:正如文档所说:

Class that simulates a dictionary. Class 模拟字典。 The instance's contents are kept in a regular dictionary, which is accessible via the data attribute of UserDict instances.实例的内容保存在常规字典中,可通过 UserDict 实例的 data 属性访问。 If initialdata is provided, data is initialized with its contents;如果提供了initialdata,则使用其内容初始化数据; note that a reference to initialdata will not be kept, allowing it be used for other purposes.请注意,不会保留对 initialdata 的引用,允许将其用于其他目的。

Essentially, it's a thin regular-class wrapper around a dict .本质上,它是一个围绕dict的薄常规类包装器。 You should be able to use it with multiple inheritance as an abstract base class, as you do with AbstractRememberingDict .您应该能够将它与多个 inheritance 一起使用作为抽象基础 class,就像使用AbstractRememberingDict一样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM