catalogue: 适用于你的库的超轻量级函数注册表
catalogue
是一个微小的、零依赖的库,它使得在你的代码中添加函数(或对象)注册表变得简单。当你有需要既容易序列化又完全可定制的对象时,函数注册表非常有用。你不需要将函数传入对象,而是传入一个标识符名称,对象可以使用这个名称从注册表中查找函数。这使得对象易于序列化,因为名称只是一个简单的字符串。如果你直接保存函数,你就必须使用Pickle进行序列化,而这有许多缺点。
⏳ 安装
pip install catalogue
conda install -c conda-forge catalogue
⚠️ 重要提示:
catalogue
v2.0+仅兼容Python 3.6+。 对于Python 2.7+兼容性,请使用catalogue
v1.x。
👩💻 使用方法
假设你正在开发一个需要在某处加载数据的Python包。你已经为最常见的数据类型实现了一些加载器函数,但你希望允许用户轻松添加他们自己的加载器。使用catalogue.create
,你可以在命名空间your_package
→ loaders
下创建一个新的注册表。
# 你的包
import catalogue
loaders = catalogue.create("your_package", "loaders")
这会给你一个loaders.register
装饰器,你的用户可以导入并用它装饰他们的自定义加载器函数。
# 用户代码
from your_package import loaders
@loaders.register("custom_loader")
def custom_loader(data):
# 在这里加载一些东西...
return data
装饰的函数将自动注册,在你的包中,你可以通过调用loaders.get_all
来访问所有加载器。
# 你的包
def load_data(data, loader_id):
print("所有加载器:", loaders.get_all()) # {"custom_loader": <custom_loader>}
loader = loaders.get(loader_id)
return loader(data)
现在用户可以只使用字符串名称("custom_loader"
)来引用他们的自定义加载器,你的应用程序将知道该怎么做并使用他们的自定义函数。
# 用户代码
from your_package import load_data
load_data(data, loader_id="custom_loader")
❓ 常见问题
但是用户不能直接传入custom_loader
函数吗?
当然可以,这是更经典的回调方法。load_data
可以接受一个函数而不是字符串ID,在这种情况下你就不需要像这样的包。当你需要生成一个可序列化的记录来记录传入了哪些函数时,catalogue
会有所帮助。例如,你可能想写一条日志消息,或保存一个配置以便稍后加载你的对象。使用catalogue
,你的函数可以通过字符串参数化,因此日志记录和序列化仍然很容易 – 同时还保持了完全的可扩展性。
我如何确保所有注册装饰器都已运行?
装饰器通常在模块导入时运行。依赖这种副作用有时会导致混淆,特别是当没有其他理由导入该模块时。一个解决方案是使用入口点。
例如,在spaCy中,我们开始使用函数注册表来使管道组件更加可定制。假设一个用户Jo使用新的机器学习研究开发了一个更好的标注模型。Jo的包的终端用户应该能够写spacy.load("jo_tagging_model")
。他们不应该需要先记得写import jos_tagged_model
,只是为了运行函数注册表作为副作用。使用入口点,注册会在安装时发生 – 所以你不需要依赖导入的副作用。
🎛 API
函数 catalogue.create
为给定的命名空间创建一个新的注册表。返回一个设置函数,可以用作装饰器或使用名称和func
关键字参数调用。如果设置了entry_points=True
,注册表将在Registry.get
和Registry.get_all
中检查为给定命名空间公布的Python入口点,例如命名空间"spacy", "architectures"
的入口点组spacy_architectures
。这允许其他包自动注册函数。
参数 | 类型 | 描述 |
---|---|---|
*namespace | str | 命名空间,例如"spacy" 或"spacy", "architectures" 。 |
entry_points | bool | 是否检查给定命名空间的入口点并预填充全局注册表。 |
返回 | Registry | 带有注册和检索函数方法的Registry 对象。 |
architectures = catalogue.create("spacy", "architectures")
# 用作装饰器
@architectures.register("custom_architecture")
def custom_architecture():
pass
# 用作普通函数
architectures.register("custom_architecture", func=custom_architecture)
类 Registry
可用于注册和检索函数的注册表对象。它通常在你调用catalogue.create
时内部创建。
方法 Registry.__init__
初始化一个新的注册表。如果设置了entry_points=True
,注册表将在Registry.get
和Registry.get_all
中检查为给定命名空间公布的Python入口点,例如命名空间"spacy", "architectures"
的入口点组spacy_architectures
。
参数 | 类型 | 描述 |
---|---|---|
namespace | Tuple[str] | 命名空间,例如 "spacy" 或 "spacy", "architectures" 。 |
entry_points | bool | 在 get 和 get_all 中是否检查给定命名空间的入口点。 |
返回值 | Registry | 新创建的对象。 |
# 用户接口
architectures = catalogue.create("spacy", "architectures")
# 内部接口
architectures = Registry(("spacy", "architectures"))
方法 Registry.__contains__
检查注册表中是否包含某个名称。
参数 | 类型 | 描述 |
---|---|---|
name | str | 要检查的名称。 |
返回值 | bool | 该名称是否在注册表中。 |
architectures = catalogue.create("spacy", "architectures")
@architectures.register("custom_architecture")
def custom_architecture():
pass
assert "custom_architecture" in architectures
方法 Registry.__call__
在注册表的命名空间中注册一个函数。可以作为装饰器使用,也可以作为函数调用,通过 func
关键字参数提供要注册的函数。委托给 Registry.register
。
方法 Registry.register
在注册表的命名空间中注册一个函数。可以作为装饰器使用,也可以作为函数调用,通过 func
关键字参数提供要注册的函数。
参数 | 类型 | 描述 |
---|---|---|
name | str | 在命名空间下注册的名称。 |
func | Any | 要注册的可选函数(如果不用作装饰器)。 |
返回值 | Callable | 接受一个参数(名称)的装饰器。 |
architectures = catalogue.create("spacy", "architectures")
# 用作装饰器
@architectures.register("custom_architecture")
def custom_architecture():
pass
# 用作普通函数
architectures.register("custom_architecture", func=custom_architecture)
方法 Registry.get
获取在命名空间中注册的函数。
参数 | 类型 | 描述 |
---|---|---|
name | str | 名称。 |
返回值 | Any | 注册的函数。 |
custom_architecture = architectures.get("custom_architecture")
方法 Registry.get_all
获取注册表命名空间中的所有函数。
参数 | 类型 | 描述 |
---|---|---|
返回值 | Dict[str, Any] | 注册的函数,以名称为键。 |
all_architectures = architectures.get_all()
# {"custom_architecture": <custom_architecture>}
方法 Registry.get_entry_points
获取其他包为此命名空间注册的入口点。入口点组的名称是由下划线连接的命名空间。
参数 | 类型 | 描述 |
---|---|---|
返回值 | Dict[str, Any] | 加载的入口点,以名称为键。 |
architectures = catalogue.create("spacy", "architectures", entry_points=True)
# 将获取所有 "spacy_architectures" 组的入口点
all_entry_points = architectures.get_entry_points()
方法 Registry.get_entry_point
检查命名空间中是否有给定名称的已注册入口点并加载它。否则,返回默认值。
参数 | 类型 | 描述 |
---|---|---|
name | str | 要加载的入口点名称。 |
default | Any | 要返回的默认值。默认为 None 。 |
返回值 | Any | 加载的入口点或默认值。 |
architectures = catalogue.create("spacy", "architectures", entry_points=True)
# 将获取 "spacy_architectures" 组中的 "custom_architecture" 入口点
custom_architecture = architectures.get_entry_point("custom_architecture")
方法 Registry.find
查找已注册函数的信息,包括它定义所在的模块和文件路径、行号以及文档字符串(如果有的话)。
参数 | 类型 | 描述 |
---|---|---|
name | str | 已注册函数的名称。 |
返回值 | Dict[str, Union[str, int]] | 函数的信息。 |
import catalogue
architectures = catalogue.create("spacy", "architectures", entry_points=True)
@architectures("my_architecture")
def my_architecture():
"""这是一个架构"""
pass
info = architectures.find("my_architecture")
# {'module': 'your_package.architectures',
# 'file': '/path/to/your_package/architectures.py',
# 'line_no': 5,
# 'docstring': '这是一个架构'}
函数 catalogue.check_exists
检查命名空间是否存在。
参数 | 类型 | 描述 |
---|---|---|
*namespace | str | 命名空间,例如 "spacy" 或 "spacy", "architectures" 。 |
返回值 | bool | 命名空间是否存在。 |