zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    正则表达式语法
    flask 保存文件到 七牛云
    flask保存 文件到本地
    在文件保存中 os.getcwd() os.listdir() os.makedirs() os.mkdir() xx.join() ... 等函数 的使用介绍
    插件 DataTable 创建列表 render参数的详解与如何传递本行数据id
    日历插件bootstrap-datetimepicker的使用感悟
    Navicat for MySQL 安装和破解
    数据库迁移(创建关联等操作) Target database is not up to date报错
    dataTable之自定义按钮实现全表 复制 打印 导出 重载
    创建简单的表单Demo
  • 原文地址:https://www.cnblogs.com/mumue/p/2932582.html
Copyright © 2011-2022 走看看