我有一个用例,我需要创建一个类,它的属性是一个Python列表。看上去很简单,对吗?让它变得复杂的部分是,当任何东西被附加到该属性时,我需要做一些特别的事情。观察属性变化对列表的作用和对字符串的作用不一样。
我尝试了很多不同的解决方案,但大多数要么不起作用,要么使代码非常难以理解。
最后,Real Python Slack 频道中有人提到了对collections.UserList进行子类化,并重写append()方法,以便在列表对象被追加时执行回调。
这里是代码的一个非常简化的版本。
import datetime
from collections import UserList
from dataclasses import dataclass, field
from typing import Callable, Dict, List, Tuple
class ListWithCallback(UserList):
"""
Create a class that emulates a Python list and supports a callback
"""
def __init__(self, callback: Callable, *args: Tuple, **kwargs: Dict
) -> None:
super().__init__(*args, **kwargs)
self.callback = callback
def append(self, item) -> None:
super().append(item)
self.callback()
@dataclass
class Media:
channels: List = field(default_factory=list)
def update(self) -> None:
now = datetime.datetime.today()
print(f"{now:%B %d - %H:%m:%S}")
def __post_init__(self) -> None:
self.channels = ListWithCallback(self.update) # type: ignore
if __name__ == "__main__":
import time
impl = Media()
impl.channels.append("Blah")
time.sleep(2)
impl.channels.append("Blah")
这里感兴趣的主要类是Media,它是一个数据类。它有一个单一的属性,channel,它是一个 Python列表。为了使其工作,你使用_post_init_ ()将channels设置为你的自定义类ListWithCallback 的一个实例。这个类接收一个函数或方法,当一个项目被追加到你的特殊列表中时调用。
在这种情况下,每当一个项目被追加时,你就会调用Media 的update()方法。为了测试这个功能是否有效,你在代码的底部导入了时间模块,并在列表中追加了两个字符串,在它们之间有一个sleep()。
收尾工作
如果你发现自己需要对一个 Python 内置类进行子类化,请查看 Python 的集合模块。它有几个类,通常推荐使用,而不是直接从内置模块本身进行子类化。在这个例子中,你使用了collections.UserList。
从集合模块子类化是很简单的。你会学到很多东西,你的代码甚至可能因为你这样做而变得更好。