python 中是否有等效的工具来模仿 C 中预处理器的行为?

Is there an equivalent tool in python to mimic the behaviour of the pre-processor in C?

提问人:doggo 提问时间:5/4/2020 最后编辑:doggo 更新时间:5/4/2020 访问量:176

问:

想要定义许多不同的函数,这些函数在 C 中具有相同的原型并不少见。

int f(int x, int y, char z, char *w);
int g(int x, int y, char z, char *w);
int h(int x, int y, char z, char *w);
int i(int x, int y, char z, char *w);
int j(int x, int y, char z, char *w);

为了在不更改多行代码的情况下为每个函数添加附加参数的可能性,我可以使用预处理器来保持灵活性:

#define FUNCTION(func) int (func)(int x, int y, char z, char *w)

然后用

FUNCTION(f);
FUNCTION(g);
FUNCTION(h);
FUNCTION(i);
FUNCTION(j);

然后,当我去定义函数时,我会使用如下行:

FUNCTION(f)
{
    //do something with x,y,z,w
}

FUNCTION(g)
{
    //do something else with x,y,z,w
}

有没有办法在python中完成这一点?即是否可以在 Python 中定义许多函数,所有这些函数都采用完全相同的参数,然后通过更改一行来修改它们的参数(例如添加或删除一个参数)?

python dry 函数声明

评论

0赞 Scott Hunter 5/4/2020
这如何为任何内容添加参数?
0赞 doggo 5/4/2020
无。但是,如果我想为每个函数添加一个参数,我只需要更改以“#define”开头的行。
1赞 Samwise 5/4/2020
为什么要向每个函数添加一个参数,而不更改函数体以使用该参数执行某些操作?
1赞 John Coleman 5/4/2020
您可以创建一个修饰器,其行为由在文件顶部定义的参数控制,并使用这个通用修饰器修饰函数。
0赞 doggo 5/4/2020
@Samwise 在运行时,我将调用其中一个函数,但不知道它是哪一个。某些参数仅用于某些功能。可以想象,当我向列表中添加新功能时,其中一个可能需要更多信息。在这种情况下,我可能会添加一个新参数。虽然旧函数不需要它,但我需要它们期待它。我问题中的 MWE 隐藏了这样做的动机。

答:

3赞 Samwise 5/4/2020 #1

你不会像在 C 中那样在 Python 中转发声明函数,所以这个问题的前提没有多大意义。最接近的类比是在类型声明中,您确实可以为其别名:

from typing import Callable

FUNCTION = Callable[[int, int, str, str], int]

您还可以让函数本身返回函数,并可能以这种方式消除大量重复,但您的示例不包括函数的主体,因此不会真正映射。

对于您在编辑中描述的情况,您可以不声明参数类型并改用 *args:

def f(*args):
    x, y, z, w = args

或者对于类型安全的解决方案(因为很难键入 *args 和 **kwargs),您可以让这些函数采用单个(类型化)元组参数:

FUNCTION_ARGS = Tuple[int, int, str, str]

def f(args: FUNCTION_ARGS):
    # next line will throw a mypy error if it doesn't match FUNCTION_ARGS
    x, y, z, w = args  

评论

0赞 doggo 5/4/2020
也许我在例子中关注了错误的事情。在定义函数主体时,也可以使用宏。我已经编辑了我的问题以反映这一点。无论如何,我将看一下 Callable 模块。谢谢你的建议!
1赞 6502 5/4/2020 #2

标准 Python 没有内置的宏工具,除了 / 之外,不支持静态生成元编程。evalexec

排除 Python 仅提供动态(运行时)元编程,显然这是一种设计选择;例如,它确实提供了装饰器,可以涵盖一些用例。此外,Python 源代码的读取(解析)是一成不变的,没有办法扩展语法。eval/exec

在运行时创建函数和类的简单方法是将它们的源代码构建为文本字符串,并在其上调用 或。这就是标准库的作用,例如 .evalexecnamedtuple

此外,Python VM 的几乎所有低级部分都公开了;您可以访问 Python 字节码,例如,您可以以这种方式生成函数,但这是一个非常具有挑战性的操作,因为您需要注意很多细节(例如堆栈效果),而且其中一些内部结构不能保证在 Python 版本之间稳定。从理论上讲,仅使用 Python 可以在 Python 本身中重新实现,但这将是相当困难的,因为您必须自己重写 Python 编译器的相当大一部分(即用 C 编写)。exec

还有人试图使用 Python 提供的内容(或忘记禁止)为 Python 添加宏功能......但是,我没有这方面的经验。

评论

0赞 John Coleman 5/4/2020
我不太相信它,但是 Julien Danjou 的《Serious Python》一书中有一章是关于在 Python 中使用该模块进行元编程的,所以也许说 Python “不支持”太强烈了。除了那个狡辩之外,很好的答案。ast
0赞 Dunes 5/4/2020
说 python 不支持元编程有点奇怪,当它被列在元编程的维基百科页面中作为支持元类的语言时。我想说的是 python 对元编程有非常强大的支持,它只是不直接支持宏。
0赞 6502 5/4/2020
@Dunes:这一切都归结为你对元编程的意思。它有某种形式的(装饰器)和低级的东西(你可以创建类或函数对象),但语言中不支持传递、操作或返回代码;只能传递类或函数,而不能传递代码。它支持运行时内省,这也可以被认为是元编程的某种部分形式。如果你想把元编程称为 python 提供的东西,那么你需要发明一个更广泛、更强大的术语来表示 Lisp 提供的东西。
1赞 Dunes 5/4/2020
您绝对可以操作和返回代码。只是一般不鼓励。使用该模块,像 macropy(仅使用 stdlib 功能)之类的东西可以将其转换为 .您甚至可以让 python 解释为 .Python 提供了对函数和类的完全访问权限,能够对这些对象使用反射、反编译代码对象、转换它们并重新编译它们。只是一般的口头禅是:保持简单并坚持装饰器。astf[_ * _]def f(_0): return _0 * _0range(5) | pow(_, 2) | sumsum(pow(n, 2) for n in range(5)
0赞 6502 5/4/2020
@Dunes:“你甚至可以让 python 来解释 range(5) |pow(_, 2) |sum as sum(pow(n, 2) for n in range(5)“.不,你不能,或者说得更好,这是一个非常困难的操作,因为你需要编写一个编译器,并通过替换代码对象来做到这一点。稍微容易做到的是让它的行为或多或少相同,但在每次执行的运行时对“元程序”进行重新解释。说它是一样的,就像说宏和函数是一样的。