Python基础教程读书笔记(第17章—第19章:扩展Python;程序打包;好玩的编程)

第十七章:扩展Python

python编写高性能的代码可能不是很好的选择。python意味着易用且能帮助提高开发速度。达到这种程序的灵活性需要以效率作为沉重代码。C语言、C++和Java比python快几个数量级

1:考虑哪个更重要:
A:在Python中开发一个原型(prototype)程序
B:分析程序并且找出瓶颈
C:用C语言(或者C++、C#、Java等)作为扩展来重写出现瓶颈的代码
最后的架构——带有一个或多个C组件的Python框架是非常强大的,因为它结合了两门语言的有点

2:简单的途径——Jython和IronPython
使用Jython和IronPython可以直接访问底层语言中的类和模块,这样就不需要遵照特定的API(扩展C Python时必须这样做)——只需实现需要的功能,就可以在python中使用这些功能。比如,可以在Jython中直接访问Java标准库,在IronPython中直接访问C#标准库

public class JythonTest{

    public void greeting(){
        System.out.printIn("Hello,world!");
    }  
}

可以用某种Java编译器编译,例如javac:
$ javac JythonTest.java
提示:如果使用Java工作,那么可以使用jythonc命令把Python类编译成Java类,这样的Java类能直接导入到Java程序中。

已经编译了这个类以后,就可以启动Jython(并且把.class文件放到当前目录中或者放到配置的Java CLASSPATH中的某处):
$ CLASSPATH = JythonTest.class jython
之后直接导入这个类:

>>> import JythonTest
>>> test = JythonTest()
>>> test.greeting()
Hello,world!
using System;
namespace FePyTest{
    publict class IronPythonTest{
        public void greeting(){
            Console.WriteLine("Hello,world!");
        }
    }
}

编译:
csc.exe /t:library IronPythonTest.cs
在IronPython中使用这个类的一种方法是把它编译成DLL,如果需要,还得更新相关的环境变量,然后能像下面的例子一样使用:

>>> import clr
>>> clr.AddReferenceToFile("IronPythonTest.dll")
>>> import FePyTest
>>> f = FePyTest.IronPythonTest()
>>> f.greeting()

3:编写C语言扩展——编写Python的C语言扩展时必须严格遵照API。有几个项目的目的是简化编写C语言扩展的过程,其中最有名的是SWIG

如果使用CPython,那么会有很多工具可以提高程序的运行速度——通过生成并使用C语言库,或是通过实际地提高Python的代码速度。如PyPy

1)SWIG:是简单包装和接口生成器的缩写。它是一个能用于几种语言的工具。一方面可以通过它使用C语言或者C++编写扩展代码;另一方面,它会自动包装那些代码,以便能在一些高级语言中使用。C语言(或者C++)扩展在协同工作时会变得很重要

A:它是做什么的——使用SWIG的过程很简单,首先要确保有一些C语言代码
a:为代码写接口文件。这很像C语言的头文件(而且,为了更简单,可以直接使用头文件)
b:为了自动的产生C语言代码(包装代码)要在接口文件上运行SWIG
c:把原来的C语言代码和产生的包装代码一起编译来产生共享库

B:更喜欢Pi
回文是忽略掉空格和标点后,正反读一样的句子。假设要识别很长的回文(如分析一个蛋白质序列),已经长到对于纯Python程序来说有问题的地步,于是决定写一些C语言代码来处理(可能找到一些已经完成的代码——正如曾经提过的,在Python中使用现存的C语言代码是SWIG的主要用途)。下面是一个可能的实现:

#include <string.h>

int is_palindrome(char *text){
    int i ,n=strlen(text):
    for (i=0;i<=n/2;++i){
        if (text[i] != text[n-i-1]) return 0;
    }
    return 1;
}

下面的代码是实现同样功能的纯Python函数:

def is_palindrome(text):
    n = len(text)
    for i in range(len(text)//2):
    if text[i] != text[n-i-1]:
        return False
return True

C:接口文件
假设把上面C代码放到一个叫做palindrome.c的文件中,那么现在就要把接口描述放到文件palindrome.i中。很多情况下,如果定义了头文件(即palindrome.h),SWIG就可以从头文件中得到想要的信息。因此如果拥有一个头文件,可以随意使用它。显式地编写一个接口文件的理由之一是可以知道SWIG是怎么包装代码的。最重要的是排除一些东西。比如,如果要包装一个巨大的C语言库,可能需要导出一些函数到Python中。在这种情况下,只要把需要导出的函数放到接口文件中就可以了
在接口文件中,就像在一个头文件中做的那样,只需声明要导出的所有的函数(和变量)即可。除此之外,头部的一个单元(通过%{和%}来分界)内,可以指定包含的头文件(比如本例中的string.h)以及在这之前的一个%module声明,即为模块定义一个名字。下面的代码显示了接口文件:

#回文库的接口(palindrome.i)
%module palindrome

%{
#include <string.h>
%}

extern int is_palindrome(char *text);

D:运行SWIG
尽管可以使用很多命令行选项开关(尝试运行swig -help命令以获得选项的列表),唯一需要的是-python选项,它会确保SWIG包装C语言代码,以便能在Python中使用。其他的选项-c++也有用,如果要包装一个C++库就要使用它。你需要使用接口文件(或者,如果循环也可以使用头文件)来运行SWIG:

$ swig -python palindrome.i

这些步骤之后,应该得到两个新文件:一个是palindrome_wrap.c,另一个是palindrome.py

E:编译连接以及使用
为了能正确地编译代码,需要知道Python分布版的源代码放在哪(或者,至少要指定pyconfig.h和Python.h这两个头文件在哪,可以分别再Python安装目录的根目录和根目录下的Include子目录中找到这两个文件)。还要根据选择的C语言编译器,将代码编译到一个共享库,指明正确的选项开关。如果找不出参数和选项开关的正确组合,参见“一条通过编译器的魔法森林的捷径”

这里有一个使用cc编译器的Solaris(Unix系统衍生版本)的例子,假设$PYTHON_HOME指的是Python安装目录的根目录:

$ cc -c palindrome.c
$ cc -I$PYTHON_HOME -I$PYTHON_HOME/Include -c palindrome_warp.c
$ cc -G palindrome.o palindrome_wrap.o -o _palindrome.so

Linux中使用gcc编译器的顺序:

$ gcc -c palindrome.c
$ gcc -I$PYTHON_HOME -I$PYTHON_HOME/Include -c palindrome_wrap.c
$ gcc -shared palindrome.o palindrome_wrap.o -o _palindrome.so

可能需要的所有文件都会再一个地方找到,如/usr/include/python2.4:

$ gcc -c palindrome.c
$ gcc -I/usr/inc/include/python2.5 -c palindrome_wrap.c
$ gcc -shared palindrome.o palindrome_wrap.o -o _palindrome.so

在windows中(还是假设在命令行中使用gcc),最后一步可以使用下面的命令,创建共享库:

$ gcc -shared palindrome.o palindrome_wrap.o C:/Python25/libs/libpython25.a -o

在Mac OS X内,可以使用下面的命令(如果使用的是官方Python安装包的话,PYTHON_HOME应该为/Library/Frameworks/Python.framework/Versions/Current):

$ gcc -dynamic -I$PYTHON_HOME/include/python2.5 -c palindrome.c
$ gcc -dynamic -I$PYTHON_HOME/include/python2.5 -c 
palindrome_wrap.c
$ gcc -dynamiclib palindrome_wrap.o palindrome.o -o _palindrome.so -Wl -undefined

在运行了这些“咒语”之后,应该会得到一个很有用的文件:_palindrome.so。这就是共享库,它能直接导入Python(如果它被放置在的PYTHONPATH中的某处,比如在当前目录):

>>> import _palindrome
>>> dir(_palindrome)
['__doc__','__file__','__name__','is_palindrome']
>>> _palindrome.is_palindrome('ipreferpi')
1
>>> _palindrome.is_palindrome('notlob')
0

较早版本的SWIG中,刚刚所提及的就是全部的内容了。近期的SWIG中,编译后还会产生一些Python的包装代码(文件palindrome.py),这个包装了的代码导入了_palindrome模块并且进行了一些代码检查。如果希望跳过这个步骤,可以移除palindrome.py文件,再直接把库链接到一个叫palindrome.so的文件上。
使用包装代码的方式和使用共享库一样:

>>> import palindrome
>>> from palindrome import is_palindrome
>>> if is_palindrome('abba'):
            print 'Wow -- that never occurred to me...'

Wow -- that never occurred to me...

F:一条通过编译器的魔法森林的捷径
如果让编译过程自动运行(比如,使用生成文件),那么用户就要通过指定一些选项来配置安装,这些选项包括指定python的安装位置、编译器使用的特定选项以及使用哪个编译器。使用Distutils可以优雅的避免那些配置。实际上,Distutils直接支持SWIG,这样一来甚至不需要手动运行,而只是写些代码以及接口文件,然后运行Distutils脚本

2)自己研究
SWIG在幕后做了非常多的事情,但不是每一件都那么必要。如果需要,还可以自己编写包装代码——或者只编写C语言代码以便直接使用Python C API

A:引用计数——是垃圾收集(garbage collection)的一种
在Python中,内存管理是自动的——只要创建对象,如果不再使用,它们会消失。C语言中必须显式的释放(deallocate)不再使用的对象(或者说是内存块)。如果不那么做,你的程序可能开始占据越来越多的内存,这种情况较内存泄露(memory leak)。
当编写Python扩展时,需要访问Python用来“偷偷地”管理内存的工具,其中之一就是引用计数。它的思想是,一个对象只要被代码中的某部分所引用(用C语言的术语来说就是还有指针指向那个对象),那个对象就不应该被释放掉。然后,一个对象的引用数目变成0以后,数目就不会再增加了——没有代码能创建那个对象的新引用,那个对象在内存中就是“自由浮动的”。这个时候释放它就很安全。

使用两个宏(macro)Py_INCREF 和 Py_DECREF 分别来增加和减少一个对象的引用计数:
a:不能拥有一个对象,但可以拥有一个指向它的引用。一个对象的引用计数是指向它的引用的数目
b:如果拥有一个引用,应该在不再需要这个引用的时候调用Py_DECREF
c:如果临时借用(borrow)了一个引用,就不应该再使用完对象后调用Py_DECREF;调用Py_DECREF是引用的所有者要做的事。
d:可以通过Py_INCREE 将借用的引用变成自己拥有的引用。这会创建一个新拥有的引用,而原来的所有者仍然拥有原来的引用
e:当接收到一个作为参数的对象,要不要变换所有者关系(比如,打算把它存起来)或者只是借用,都取决于自己。如果函数在Python中被调用,借用就很安全——对象的生存期持续到函数调用结束。如果函数是C语言中被调用,只是借用就不能保证安全了,你可能想创建一个归自己所有的引用,当使用完以后释放它

B:一个扩展用的框架

在编写Python的C语言扩展时需要写很多重复的代码,这就是使用工具(比如SWIG、Pyrex和modulator)会比较好的原因:它们会自动复制代码。还有很多其他的方法来构建的代码

要记住的第一件事就是Python.h头文件必须首先被包含,也就是要在其他的标准头文件之前。这是因为在一些平台上Python.h会执行一些被其他的头文件使用的重定义。因此为了简便需要将如下内容放在代码的第一行:
#include <Python.h>
你的函数能被你想要的任何东西调用。它应该是静态的,返回一个指向PyObject类型的对象的指针(一个包括所有权的引用),并且有两个参数,两个参数都是指向PyObject的指针。那些对象照惯例被称为self和args(self是自我对象,或者NULL,而args是一个参数的元组),换句话说,函数看起来应该像这样:

static PyObject *somename(PyObject *self, PyObject *args){
    PyObject *result;
    /* 进行处理,包括分配结果 */

    Py_INCREF(result); /*如果需要的话才这样做!*/
    return result;
}

参数self实际上只是被用在封装好的方法中。在其他的函数中,它只是一个NULL指针。
要注意,可能不需要调用Py_INCREF。如果对象在函数中被创建,函数中就有该对象的引用,并且能把引用返回。如果希望从函数中返回None,应该使用已经存在的Py_None对象。在这种情况下,函数不再拥有一个指向Py_None的引用,这样就需要在返回之前调用Py_INCREF(Py_None)
参数args包含了函数的所有参数(如果self存在,那么args不包含self)。为了提取对象,则可以使用PyArg_ParseTuple(为了得到参数的位置)和PyArg_ParseTupleAndKeywords(为了取得参数的位置和关键字)

函数PyArg_ParseTuple的签名如下:int PyArg_ParseTuple(PyObject *args, char *format,...);
格式字符串用来描述需要得到的参数,然后提供所要最后填充的变量的地址。返回值是一个布尔值:真表示一切顺利,否则就表示有错误。如果存在错误,那么就要进行适当的准备来引发异常,而所要做的就是返回NULL来触发它。如果不需要任何参数(一个空的格式字符串),下面就是一个很有用的处理参数的方法:

if (!PyArg_ParseTuple(args, "")){
    return NULL;
}

格式字符串:"s"表示字符串,"i"表示一个整数,"o"表示一个python对象

C:Python C API 版本的回文

View Code
/* palindrome2.c */
#include <Python.h>

static PyObject *is_palindrome(PyObject *self, PyObject *args){
    int i, n;
    const char *text;
    int result;
    /* "s"表示一个字符串: */
    if (!PyArg_ParseTuple(args, "s", &text)){
        return NULL;
    }
    /*旧版代码*/
    n = strlen(text);
    result = 1;
    for (i=0; i<=n/2; ++i){
        if (text[i] != text[n-i-1]){
            result = 0;
            break;
        }
    }
    /* "i"表示一个整数: */
    return Py_BuildValue("i", result);
}

/* 方法/函数的列表: */
static PyMethodDef PalindromeMethods[] = {
    /* 名称、函数、参数类型和文档字符串 */
    {"is_palindrome", is_palindrome, METH_VARARGS, "Detect palindromes"},
    /* 一个列表结束的标记: */
    {NULL,NULL,0,NULL}
};

/* 初始化模块的函数(名称很重要) */
PyMODINIT_FUNC initpalindrome(){
    Py_InitModule("palindrome", PalindromeMethods);
}

开始编译:
$ gcc -I$PYTHON_HOME -I$PYTHON_HOME/Include -shared palindrome2.c -o palindrome.so
同样,应该有一个叫做palindrome.so的文件类供使用。把它放到你的PYTHONPATH然后就可以使用了

小结
扩展的思想:扩展Python主要有两个用途:使用现有的代码(老代码),或者提高瓶颈的速度
Python和IronPython
扩展方法:有很多扩展代码或者加速代码的工具:SWIG,Pyrex,Weave,NumPy等
SWIG:是一个自动为C语言库生成包装代码的工具。封装的代码会处理Python C API,这样就不用自己处理了
使用Python/C API

 

第十八章:程序打包

用于发布Python包的工具包Distutils能让程序员轻松的用Python编写安装脚本。

1:Distutils基础——相关内容在Python库参考

#简单的Distutils安装脚本—setup.py
from distutils.core import setup

setup(name='Hello',
           version='1.0',
           description='A simple example',
           author='Magnus Lie Hetland',
           py_modules=['hello'])

在setup函数内,不必提供所有这些信息(事实上可以不提供任何参数),也可以提供更多(比如author_email或url)的参数
提示:setuptools项目基于Distutils,但包含一些增强功能的程序

将上面的脚本存储为setup.py(Distutils安装脚本的惯例),确保在同一个目录下存在名为hello.py的模块文件
警告:运行这个setup脚本时会再当前目录创建新的文件和子目录,所以最好在全新的目录中进行试验,避免旧文件被覆盖

现在就可以使用这个脚本了,执行如下命令:python setup.py

Distutils创建了一个叫做build的子目录,其中包含名为lib的子目录,并且把hello.py的一个副本放置在build/lib内。build子目录是Distutils组装包(以及编译扩展库等)的工作区。在安装的时候不需要运行build命令——如果需要的话,在运行install命令的时候它就自动运行了

2:打包
写完供用户安装模块使用的setup.py脚本以后,就可以用它来建立存档文件,windows安装程序或者RPM包

1)建立存档文件:可以使用sdist命令(用于”源代码发布“)
python setup.py sdist

posted @ 2013-02-26 17:48  cateatmycode  阅读(557)  评论(0编辑  收藏  举报