是否可以使用 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()
方法必须被覆盖才能提供完整的实现。
感觉这个问题ABC
s应该可以解决。我尝试了以下方法:
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
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()
方法必须被覆盖才能提供完整的实现。
感觉这个问题ABC
s应该可以解决。我尝试了以下方法:
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
.