Archive for December 19th, 2007

用Erlang实现的斐波拉契数列计算

Wednesday, December 19th, 2007

用Erlang实现的斐波拉契数列计算

作者: gashero

前几日有好友发来帖子告诉我一个Ruby比Python快的实验,见了以后立马写了一个程序,不过优化效果很有限。最后倒是想起来erlang,一直吵吵着想学,但是一直学的都不紧不慢的,没效果。于是准备拿这个例子来尝试一下。

经过了好久的学习,最终写出了一个比较龌龊的例子,如下:

-module(fibmod).
-export([fib/1,calc/1,print/2]).

fib(0) ->
    0;
fib(1) ->
    1;
fib(X) ->
    fib(X-1)+fib(X-2).

calc(35) ->
    print(35,fib(35));
calc(N) ->
    print(N,fib(N)),
    calc(N+1).

print(Number,Fibnum) ->
    io:format("n=~w => ~w~n",[Number,Fibnum]).

也就是这个例子了,要在erlang控制台中运行,需要按照如下步骤:

10> c(fibmod).
{ok,fibmod}
11> fibmod:calc(1).
n=1 => 1
n=2 => 1
n=3 => 2
n=4 => 3
n=5 => 5
n=6 => 8
n=7 => 13
n=8 => 21
n=9 => 34
n=10 => 55
n=11 => 89
n=12 => 144
n=13 => 233
n=14 => 377
n=15 => 610
n=16 => 987
n=17 => 1597
n=18 => 2584
n=19 => 4181
n=20 => 6765
n=21 => 10946
n=22 => 17711
n=23 => 28657
n=24 => 46368
n=25 => 75025
n=26 => 121393
n=27 => 196418
n=28 => 317811
n=29 => 514229
n=30 => 832040
n=31 => 1346269
n=32 => 2178309
n=33 => 3524578
n=34 => 5702887
n=35 => 9227465
ok
12>

当时按照例子,Ruby1.9要十几秒,Python的要三十几秒。不过我在这台机器上没有测试。我在这台笔记本(T43 1.86GHz)上运行如上erlang程序,估算时间是4秒以内(注:不知道如何用erlang计时)。

其实例子超级简单,不过多少算是第一个erlang程序,希望是一个好的开始。现在越来越看好erlang了,也祝愿erlang发展的越来越好吧。

在应用中嵌入Python

Wednesday, December 19th, 2007

在应用中嵌入Python

翻译: gashero

前面的章节讨论如何扩展Python,如何生成适合的C库等。不过还有另一种情况:通过将Python嵌入C/C++应用以扩展程序的功能。Python嵌入实现了一些使用Python更合适的功能。这可以有很多用途,一个例子是允许用户裁减需要的Python功能。也可以用于默写使用Python编写更加方便的功能。

嵌入Python与扩展很像。扩展Python时,主程序是Python解释器,但是嵌入Python则主程序并不是Python的-是程序的其他部分调用Python来实现一些功能。

所以,如果要嵌入Python,你可以提供自己的主程序,这个主程序需要初始化Python解释器。至少需要调用函数 Py_Initialize() (对于MacOS,调用 PyMac_Initialize())。可以选择是否传入命令行参数到Python。然后你就可以在应用的任何地方调用Python解释器了。

有几种方法调用解释器:可以传递一个包含Python语句的字符串到 PyRun_SimpleString() ,也可以传递一个stdio文件指针和一个文件名(用于识别错误信息)到 PyRun_SimpleFile() 。你也可以调用前几章介绍的底层操作直接控制Python对象。

可以在目录 Demo/embed/ 中找到嵌入Python的例子。

目录

1   高层次嵌入

嵌入Python最简单的形式是使用高层次的接口。这个接口专门用于执行Python脚本,而不需要与应用程序直接交互。例子可以在一个文件中展示:

#include <Python.h>
int
main(int argc, char* argv[]) {
    Py_Initialize();
    PyRun_SimpleString("from time import time,ctime\n"
            "print 'Today is',ctime(time())\n");
    Py_Finalize();
    return 0;
}

如上代码首先使用 Py_Initialize() 初始化Python解释器,随后执行硬编码中的Python脚本来打印日期和时间。最后 Py_Finalize() 关闭了解释器。在真实应用中,你可能希望从其他方式获取Python脚本,文件、编辑器、数据库等。从文件获取的方式更适合使用 PyRun_SimpleFile() 函数,可以省去分配内存空间和载入文件的麻烦。

2   超越高层嵌入:预览

高层次的接口可以方便的执行Python代码,但是交换数据就很麻烦。如果需要,你可以使用低层次的接口调用。虽然多写一些C代码,但是却可以完成很多功能。

仍然要提醒的是,Python的扩展与嵌入其实很像,尽管目的不同。前几章讨论的大多数问题在这里也同样适用。可以参考用C扩展Python时一些步骤:

  1. 转换Python类型到C类型
  2. 传递参数并调用C函数
  3. 转换返回值到Python

当嵌入Python时,接口需要做:

  1. 转换C数据到Python
  2. 调用Python接口程序来调用Python函数
  3. 转化返回值到C

有如你所见,数据转换的步骤用于跨语言的数据交换。唯一的不同是两次数据转换之间调用的函数。当扩展时,你调用C函数,当嵌入时,调用Python函数。

这一章不会讨论Python和C之间的数据转换。并且假设你会使用手册来处理错误,自此只会讨论与扩展解释器不同的部分,你可以到前面的章节找到需要的信息。

3   纯扩展

第一个程序是执行一段Python脚本中的函数。有如高层接口一节,Python解释器并不会自动与程序结合。

运行一段Python脚本中的函数的代码如下:

#include <Python.h>

int
main(int argc, char *argv[])
{
    PyObject *pName, *pModule, *pDict, *pFunc;
    PyObject *pArgs, *pValue;
    int i;

    if (argc < 3) {
        fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
        return 1;
    }

    Py_Initialize();
    pName = PyString_FromString(argv[1]);
    /* Error checking of pName left out */

    pModule = PyImport_Import(pName);
    Py_DECREF(pName);

    if (pModule != NULL) {
        pFunc = PyObject_GetAttrString(pModule, argv[2]);
        /* pFunc is a new reference */

        if (pFunc && PyCallable_Check(pFunc)) {
            pArgs = PyTuple_New(argc - 3);
            for (i = 0; i < argc - 3; ++i) {
                pValue = PyInt_FromLong(atoi(argv[i + 3]));
                if (!pValue) {
                    Py_DECREF(pArgs);
                    Py_DECREF(pModule);
                    fprintf(stderr, "Cannot convert argument\n");
                    return 1;
                }
                /* pValue reference stolen here: */
                PyTuple_SetItem(pArgs, i, pValue);
            }
            pValue = PyObject_CallObject(pFunc, pArgs);
            Py_DECREF(pArgs);
            if (pValue != NULL) {
                printf("Result of call: %ld\n", PyInt_AsLong(pValue));
                Py_DECREF(pValue);
            }
            else {
                Py_DECREF(pFunc);
                Py_DECREF(pModule);
                PyErr_Print();
                fprintf(stderr,"Call failed\n");
                return 1;
            }
        }
        else {
            if (PyErr_Occurred())
                PyErr_Print();
            fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
        }
        Py_XDECREF(pFunc);
        Py_DECREF(pModule);
    }
    else {
        PyErr_Print();
        fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
        return 1;
    }
    Py_Finalize();
    return 0;
}

这段代码从argv[1]中载入Python脚本,并且调用argv[2]中的函数,整数型的参数则是从argv数组后面得来的。如果编译和链接这个程序,执行如下脚本:

def multiply(a,b):
    print "Will compute",a,"times",b
    c=0
    for i in range(0,a)
        c=c+b
    return c

结果将是:

$ call multiply multiply 3 2
Will compute 3 times 2
Result of call: 6

虽然这个程序的代码挺多的,但是大部分其实都是做数据转换和错误报告。主要关于嵌入Python的开始于:

Py_Initialize();
pName=PyString_FromString(argv[1]);
/* Error checking of pName left out */
pModule=PyImport_Import(pName);

初始化解释器之后,使用 PyImport_Import() 导入模块。这个函数需要字符串作为参数,使用 PyString_FromString() 来构造:

pFunc=PyObject_GetAttrString(pModule,argv[2]);
/* pFunc is a new reference */
if (pFunc && PyCallable_Check(pFunc)) {
    ...
}
Py_XDECREF(pFunc);

载入了模块以后,就可以通过 PyObject_GetAttrString() 来获取对象。如果名字存在并且可以执行则可以安全的调用它。程序随后构造参数元组,然后执行调用:

pValue=PyObject_CallObject(pFunc,pArgs);

函数调用之后,pValue要么是NULL,要么是返回值的对象引用。注意在检查完返回值之后要释放引用。

4   扩展嵌入的Python

至今为止,嵌入的Python解释器还不能访问应用程序本身的功能。Python的API允许扩展嵌入的Python的解释器。所以,Python可以获得其所嵌入的程序的功能。这听起来挺麻烦的,其实并不是那样。只要简单的忘记是应用程序启动了Python解释器。

可以把程序看作一对功能的集合,可以写一些胶水代码来来让Python访问这些功能,有如你在写一个普通的Python扩展一样。例如:

static int numargs=0;

/* Return the number of arguments of the application command line */
static PyObject*
emb_numargs(PyObject *self, PyObject *args)
{
    if(!PyArg_ParseTuple(args, ":numargs"))
        return NULL;
    return Py_BuildValue("i", numargs);
}

static PyMethodDef EmbMethods[] = {
    {"numargs", emb_numargs, METH_VARARGS,
     "Return the number of arguments received by the process."},
    {NULL, NULL, 0, NULL}
};

添加上面的代码到 main() 函数。同样,插入如下两个语句到 Py_Initialize() 函数之后:

numargs=argc;
Py_InitModule("emb",EmbMethods);

这两行代码初始化numargs变量,并且使得 emb.numargs() 函数更加易于被Python嵌入的解释器所理解。通过这个扩展,Python脚本可以做如下事情:

import emb
print "Number of arguments",emb.numargs()

在实际的应用程序中,方法需要导出API以供Python使用。

5   在C++中嵌入Python

有时候需要将Python嵌入到C++程序中,而你必须有一些要注意的C++系统的细节,一般来说你要为这个程序写一个main()函数,然后使用C++编译器来编译和链接程序。而这里不需要因为使用C++而重新编译Python本身。

6   链接必备条件

configure 脚本执行时,可以正确的生成动态链接库使用的导出符号,而这些却不会自动被嵌入的静态链接的Python所继承,至少是在Unix。这是用于静态链接运行库(libpython.a)并且需要载入动态扩展(.so)的方式。

问题是一些入口点是使用Python运行时定义的而仅供扩展模块使用。如果嵌入应用不使用任何这些入口点,一些链接器不会包含这些实体到最终可执行文件的符号表。一些附加的选项可以用于告知连接器不要删除这些符号。

对于不同的平台,想要正确的检测该使用何种参数是非常困难的,但是幸运的是Python配置好了这些值。只要通过已经安装的Python解释器,启动交互解释器然后执行如下会话即可:

>>> import distutils.sysconfig
>>> distutils.sysconfig.get_config_var('LINKFORSHARED')
'-Xlinker -export-dynamic'

字符串的内容就是生成的选项。如果字符串为空,则不需要任何的附加选项。LINKFORSHARED的定义与Python顶层Makefile中的同名变量相同。