注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

python解释器如何调用模块注册的方法  

2017-11-14 00:16:55|  分类: python |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、问题
对于python来说,它提供了用户可以注册的模块的加载和调用机制,从而可以让用户随意的扩充python的具体功能模块。但是,这个地方就有一个函数调用规则的问题:我们举个简单的例子,假设说一个程序要加载一个动态库so文件,并从中找出某个函数的并进行调用,通常的做法是下面这个样子(这个例子拷贝子linux下dlopen自带的例子):
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

int
main(int argc, char **argv)
{
   void *handle;
   double (*cosine)(double);
   char *error;

   handle = dlopen("libm.so", RTLD_LAZY);
   if (!handle) {
       fprintf(stderr, "%s\n", dlerror());
       exit(EXIT_FAILURE);
   }

   dlerror();    /* Clear any existing error */

   /* Writing: cosine = (double (*)(double)) dlsym(handle, "cos");
      would seem more natural, but the C99 standard leaves
      casting from "void *" to a function pointer undefined.
      The assignment used below is the POSIX.1-2003 (Technical
      Corrigendum 1) workaround; see the Rationale for the
      POSIX specification of dlsym(). */

   *(void **) (&cosine) = dlsym(handle, "cos");

   if ((error = dlerror()) != NULL)  {
       fprintf(stderr, "%s\n", error);
       exit(EXIT_FAILURE);
   }

   printf("%f\n", (*cosine)(2.0));
   dlclose(handle);
   exit(EXIT_SUCCESS);
}

拷贝了这么多代码,主要是为了让这篇日志看起来内容更加充实,另一方面也是为了保证代码版权的完整。但是这些都并不重要,重要的是大家注意下这里对consine函数的调用方法,它是要先声明函数原型,之后通过dlsym函数读取获得函数地址,之后再用对应的函数原型指针来进行函数调用。这意味着
printf("%f\n", (*cosine)(2.0));
调用中,此时编译器知道如何对consine函数调用时参数压栈出栈。那么,问题来了,作为一个框架,python怎么知道不同模块注册的接口方法中真正需要的参数个数是多少呢?如果不知道的话,如何在框架层调用具体的函数呢?
二、看一个简单的例子
下面的例子中,fileno函数不需要参数,所以在第三行调用的时候出错。
tsecer@harry: cat -n cfuncall.py 
     1 file = open("./cfuncall.py");
     2 num = file.fileno()
     3 num = file.fileno(1)
tsecer@harry: python ./cfuncall.py 
Traceback (most recent call last):
  File "./cfuncall.py", line 3, in <module>
    num = file.fileno(1)
TypeError: fileno() takes no arguments (1 given)
tsecer@harry: 

对于fileno函数


static PyObject *
stdprinter_fileno(PyStdPrinter_Object *self)
{
    return PyLong_FromLong((long) self->fd);
}

三、python对于注册方法的调用
也就是在调用注册的方法时,python解释器会判断函数注册时声明的参数个数,当前对于C类函数调用,必须是typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
类型的函数定义,也就是接受两个函数参数。但是会根据函数注册时的声明进行检测。

这个地方其实还是很有意义的,它相当于从模块的虚函数表中找到对应的函数指针,然后转到具体模块方法的执行流程。
Python-3.6.1\Objects\methodobject.c
PyObjet *
PyCFunction_Call(PyObject *func, PyObject *args, PyObject *kwds)
{
    PyCFunctionObject* f = (PyCFunctionObject*)func;
    PyCFunction meth = PyCFunction_GET_FUNCTION(func);
    PyObject *self = PyCFunction_GET_SELF(func);
    PyObject *arg, *res;
    Py_ssize_t size;
    int flags;

    /* PyCFunction_Call() must not be called with an exception set,
       because it may clear it (directly or indirectly) and so the
       caller loses its exception */
    assert(!PyErr_Occurred());

    flags = PyCFunction_GET_FLAGS(func) & ~(METH_CLASS | METH_STATIC | METH_COEXIST);

    if (flags == (METH_VARARGS | METH_KEYWORDS)) {
        res = (*(PyCFunctionWithKeywords)meth)(self, args, kwds);
    }
    else if (flags == METH_FASTCALL) {
        PyObject **stack = &PyTuple_GET_ITEM(args, 0);
        Py_ssize_t nargs = PyTuple_GET_SIZE(args);
        res = _PyCFunction_FastCallDict(func, stack, nargs, kwds);
    }
    else {
        if (kwds != NULL && PyDict_Size(kwds) != 0) {
            PyErr_Format(PyExc_TypeError, "%.200s() takes no keyword arguments",
                         f->m_ml->ml_name);
            return NULL;
        }

        switch (flags) {
        case METH_VARARGS:
            res = (*meth)(self, args);
            break;

        case METH_NOARGS:
            size = PyTuple_GET_SIZE(args);
            if (size != 0) {
                PyErr_Format(PyExc_TypeError,
                    "%.200s() takes no arguments (%zd given)",
                    f->m_ml->ml_name, size);
                return NULL;
            }

            res = (*meth)(self, NULL);
            break;

        case METH_O:
            size = PyTuple_GET_SIZE(args);
            if (size != 1) {
                PyErr_Format(PyExc_TypeError,
                    "%.200s() takes exactly one argument (%zd given)",
                    f->m_ml->ml_name, size);
                return NULL;
            }

            arg = PyTuple_GET_ITEM(args, 0);
            res = (*meth)(self, arg);
            break;

        default:
            PyErr_SetString(PyExc_SystemError,
                            "Bad call flags in PyCFunction_Call. "
                            "METH_OLDARGS is no longer supported!");
            return NULL;
        }
    }

    return _Py_CheckFunctionResult(func, res, NULL);
}

四、开始例子中fileno函数的注册
所以在函数注册的时候就通过METH_NOARGS标志声明函数不接收任何参数
Python-3.6.1\Modules\_io\bufferedio.c
static PyMethodDef bufferedrandom_methods[] = {
    /* BufferedIOMixin methods */
    {"close", (PyCFunction)buffered_close, METH_NOARGS},
    {"detach", (PyCFunction)buffered_detach, METH_NOARGS},
    {"seekable", (PyCFunction)buffered_seekable, METH_NOARGS},
    {"readable", (PyCFunction)buffered_readable, METH_NOARGS},
    {"writable", (PyCFunction)buffered_writable, METH_NOARGS},
    {"fileno", (PyCFunction)buffered_fileno, METH_NOARGS},
……
但是在函数真正实现的时候,依然使用的是两个参数的函数定义,第二个参数args没有使用。
static PyObject *
buffered_fileno(buffered *self, PyObject *args)
{
    CHECK_INITIALIZED(self)
    return PyObject_CallMethodObjArgs(self->raw, _PyIO_str_fileno, NULL);
}
  评论这张
 
阅读(7)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017