让 Python 模块可调用:介绍 Cadule

发布: (2025年12月26日 GMT+8 23:02)
4 min read
原文: Dev.to

Source: Dev.to

Cover image for Making Python Modules Callable: Introducing Cadule

在编写 Python 代码时,我常常想念 Node.js 的一个特性:能够直接 require 一个模块并让它可调用。在 Node.js 中,你可以从模块导出一个函数并立即调用它:

const messUpThings = require('./mess-up-things');
messUpThings(); // Works!

我知道有些开发者不喜欢这种模式,但它确实有合理的使用场景。

问题

设想一种情况,你需要编写一个自解释名称的帮助函数,例如 mess_up_things。函数名本身就说明了它的作用。你有几种选择:

  • 创建一个 util 或 helper 模块:把它放在 util.pyhelper.py 中。许多开发者不喜欢这些通用名称,尤其是更倾向于具描述性包名的 Go 开发者。
  • 创建一个专用文件:把它放在 mess_up_things.py 中。然后你需要写冗长的导入语句,例如:
from mess_up_things import mess_up_things

这种重复显得多余。如果能够直接这样做就好了:

import mess_up_things
mess_up_things()

探索解决方案

如果你熟悉 Python 的魔法方法,你可能知道任何拥有 __call__ 方法的对象都可以被调用。由于模块是 types.ModuleType 的实例,并且它们支持像 __getattr__ 这样的魔法方法,难道我们不能直接在模块上定义 __call__ 方法吗?

遗憾的是,Python 并不原生支持这一点。实际上曾有 PEP 713 提议为模块层面添加 __call__ 支持,但该提案被否决了。

不过,有一种技巧可以实现这种行为:

import sys

class MyModule(sys.modules[__name__].__class__):
    def __call__(self):
        print("Messing up things...")

sys.modules[__name__].__class__ = MyModule

它通过在运行时动态替换模块的 __class__ 来工作。但谁愿意每次都复制粘贴这段样板代码呢?

介绍 Cadule

为了避免重复这段样板代码,我把机制封装进了一个名为 CaduleCallable [Mo]dule Less 的缩写)的包中。

安装

pip install cadule

使用

创建一个名为 mess_up_things.py 的文件:

import cadule

@cadule
def __call__():
    print("Messing up things...")

然后在 Python REPL 或其他脚本中:

>>> import mess_up_things
>>> mess_up_things()
Messing up things...
>>> callable(mess_up_things)
True

你也可以传递参数并返回值:

import cadule

@cadule
def __call__(target):
    return f"Messing up {target}!"
>>> import mess_up_things
>>> mess_up_things("the database")
'Messing up the database!'

何时使用

Cadule 在以下场景特别有用:

  • 单一用途模块:当模块的主要目的是暴露一个函数时。
  • DSL 与流式接口:创建更自然的 API。
  • 脚本与工具:让命令行工具的使用更直观。

GitHub:

Back to Blog

相关文章

阅读更多 »

我的简易井字游戏

作为 Codecademy 项目作品系列的一部分,我构建了一个交互式的 Tic‑Tac‑Toe 游戏,完全在终端中运行。这是我的第一个完整的 Python…