嵌入时python函数的动态arg类型

时间:2013-05-21 18:45:35

标签: python-c-api exim python-embedding

我向Exim添加了一个嵌入式python解释器。我已经复制了嵌入式perl接口,并期望python与长编码的嵌入式perl解释器一样工作。目标是允许sysadmin以强大的脚本语言(即python)执行复杂的功能,而不是尝试使用exim的标准ACL命令,因为使用exim ACL语言执行相对简单的操作会变得非常复杂。

截至撰写本文时,我目前的代码位于http://git.exim.org/users/tlyons/exim.git/blob/9b2c5e1427d3861a2154bba04ac9b1f2420908f7:/src/src/python.c。它工作正常,因为它可以导入sysadmin的自定义python代码,调用其中的函数,并处理返回的值(仅简单的返回类型:int,float或string)。但是,它还没有处理传递给python函数的值,这是我的问题开始的地方。

Python似乎要求我使用c api将传递给嵌入式python函数的任何args显式地转换为int,long,double,float或string之一。问题是sysadmin可以在嵌入式python代码中放置任何东西,在exim中的c方面,我不知道那些变量类型是什么。我知道python是动态类型的,所以我希望在将值传递给嵌入代码时保持合规性。但是在我的测试中它并没有这样做。

使用以下基本的超简单python代码:

def dumb_add(a,b):
        return a+b

...我的exim ACL语言的调用代码是:

${python {dumb_add}{800}{100}}

在下面的c代码中,为简洁起见,省略了引用计数。 count 是我传递的args的数量:

pArgs = PyTuple_New(count);
for (i=0; i<count; ++i)
{
  pValue = PyString_FromString((const char *)arg[i]);
  PyTuple_SetItem(pArgs, i, pValue);
}
pReturn = PyObject_CallObject(pFunc, pArgs);

是的,** arg是一个指向字符串数组的指针(在这个简单的例子中是两个字符串)。问题是这两个值在python代码中被视为字符串,因此执行嵌入式python的c代码的结果是:

${python {dumb_add}{800}{100}}

800100

如果我将python更改为:

def dumb_add(a,b):
        return int(a)+int(b)

然后执行python代码的c代码的结果是预期的:

${python {dumb_add}{800}{100}}

900

我的目标是我不想强迫python用户手动转换它们传递给嵌入式python函数的所有数字参数。而不是PyString_FromString(),如果有一个PyDynamicType_FromString(),我会欣喜若狂。 Exim的嵌入式perl会解析args并自动进行转换,我希望嵌入式python能够实现相同的效果。任何人都可以建议如果python可以执行此arg解析以提供我期望的动态类型吗?

或者,如果我想保持这种动态类型,我唯一的选择是解析每个arg并猜测将其投射到的类型?我真的非常希望避免这种做法。如果是这样,我可能只记录“传递的所有参数都是字符串,所以如果你实际上想要传递数字,你必须使用int(),float(),double()或long()来强制转换所有参数” 。然而,之后总会有一个逗号,我觉得这种方法会让我的实现变得非常糟糕。我也想避免这样做。

除了“将您的应用程序变成python模块”之外,还有任何建议值得赞赏。

1 个答案:

答案 0 :(得分:0)

我最终解决这个问题的方法是找出函数预期的args数,如果传递给函数的args数不匹配则退出并返回错误。我觉得最好强制执行匹配的arg计数,而不是尝试合成缺失的args或简单地省略额外的args。

args作为unsigned char ** arg

传递给此函数
  int count = 0;
  /* Identify and call appropriate function */
  pFunc = PyObject_GetAttrString(pModule, (const char *) name);
  if (pFunc && PyCallable_Check(pFunc))
  {
    PyCodeObject *pFuncCode = (PyCodeObject *)PyFunction_GET_CODE(pFunc);
    /* Should not fail if pFunc succeeded, but check to be thorough */
    if (!pFuncCode)
    {
      *errstrp = string_sprintf("Can't check function arg count for %s",
                                name);
      return NULL;
    }
    while(arg[count])
      count++;
    /* Sanity checking: Calling a python object requires to state number of
       vars being passed, bail if it doesn't match function declaration. */
    if (count != pFuncCode->co_argcount)
    {
      *errstrp = string_sprintf("Expected %d args to %s, was passed %d",
                                pFuncCode->co_argcount, name, count);
      return NULL;
    }

string_sprintf是Exim源代码中的一个函数,它也处理内存分配,让我的生活变得轻松。