【重读经典】《Python核心编程(第3版)》

今天星期五,很高兴立刻将开启愉快的周末时光,今天要介绍的是指引了无数读者入门并提升的Python殿堂的神书《Python核心编程(第3版)》中文版累计销售超20万册。他被誉为提升Python技能的必读书,书中全面涵盖当今应用开发中的众多领域为中级Python开发人员提供实践方法,经过目录脉络慢慢了解。另外异步社区招募书评人,后台回复“书评”,加入咱们。前端

做者与Python


大约10多年之前,我在一家名为Four11的公司接触到Python。当时,该公司有一个主要的产品——Four11.com White Page目录服务。它们使用Python来设计该产品的下一代:Rocketmail Web E-mail服务,该服务最终演变为今天的Yahoo!Mail。python

学习Python并加入最初的Yahoo!Mail工程团队是一件至关有趣的事情。我帮助从新设计了地址簿和拼写检查程序。在当时,Python也成为其余Yahoo!站点的一部分,其中包括People Search、Yellow Pages、Maps和Driving Directions等。事实上,我当时是People Search部门的首席工程师。linux

尽管在当时Python对我而言是全新的,可是它也很容易学习—比我过去学习的其余语言都要简单。在当时,Python教程的缺少迫使我使用Library Reference和Quick Reference Guide做为主要的学习工具,而这也是促使我写做本书的一个驱动力。程序员

从我在Yahoo!的日子开始,我可以以各类有趣的方式在随后的工做中使用Python。在任何状况下,我都能使用Python的强大功能来及时地解决遇到的问题。我也开发了多门Python课程,并使用本书来说授那些课程—彻底使用本身的做品。正则表达式

Core Python

图书不只是卓越的Python学习资料,它们仍是用来说解Python的最佳工具。做为一名工程师,我知道学习、理解和应用一种新技术所须要的东西。做为一名专业讲师,我也知道为客户提供最有效的会话(session)所须要的是什么。这些图书栩栩如生,同时包含你没法从“纯粹的培训师”或“纯粹的图书做者”那里得到的提示。算法


以讲解技术为主,同时容易阅读


不一样于严格的“入门”图书或者纯粹的“重口味”计算机科学参考图书,我过去的教学经验告诉我,一本易于阅读同时又面向技术的图书应该服务于这样的一个目的,即可以让人尽量迅速地掌握Python,以便能将其应用到十万火急的任务上来。咱们在介绍概念时会辅之以合适的案例,以加速学习过程。每章最后都会给出大量练习,旨在夯实你对书中概念和理念的理解。数据库

可以与Bruce Eckel的写做风格相提并论,我很激动也很谦卑(见本书第1版的评论,网址为http://corepython.com)。本书并不是一本枯燥的大学教材,咱们的目标是营造一个与你交谈的环境,就像你是在参加个人一个广受好评的Python培训课程同样。做为一名终身学习的学生,我不断地因材施教,告诉你须要学习什么才能快速、完全地掌握Python的概念。你也将发现,能够快速、轻松地阅读本书,并且不会错失任何技术细节。编程

做为一名工程师,我知道应该怎样作才能向你讲授Python中的概念。做为一名教师,我能够将技术细节所有打散,而后转换成一种易于理解和迅速掌握的语言。你将从个人写做风格和教学风格中获益,更重要的是,你会喜欢上用Python来编程。windows

所以,你也将注意到,尽管我是本书惟一的做者,可是我使用的是“第三人称”的写做风格,也就是说,我使用了诸如“咱们”这样的一些废话,缘由是在学习本书的过程当中,咱们是一块儿的,共同朝着扩展Python编程技能的目标而努力。数组


历 史


在本书第1版刚问世时,Python刚发布了2.0版本。从那时起,Python语言发生了重大的改进,Python语言被愈来愈多的人接受,其使用率也大幅提高。Python编程语言大获成功。Python语言的缺陷已被删除,并且有新的特性不断加入,这将全世界Python开发人员的能力和编程修养提高到了一个新的水平。本书第2版于2006年问世,当时也是Python的鼎盛时期,它的版本是迄今为止最为流行的2.5版本。

本书第2版问世以后好评如潮,其销量超过了第1版。在那期间,Python自己也赢得了无数荣誉,包括下面这些。

  • Tiobe(www.tiobe.com)
    ——年度编程语言(2007年、2010年)

  • LinuxJournal(linuxjournal.com)
    ——最喜欢的编程语言(2009~2011年)
    ——最喜欢的脚本语言(2006~2008年、2010年、2011年)

  • LinuxQuestions.org会员选择奖
    ——年度编程语言(2007~2010年)

这些奖项和荣誉推进着Python进一步发展。如今,Python已经进入了下一代:Python 3。一样,本书也在向着其“第三代”前进。我很是高兴Prentice Hall可以让我写做本书第3版。因为Python 3.x版本不可以后向兼容Python 1和Python 2,所以还须要一段时间,Python 3.x才能被业界全面采用和集成进来。咱们很乐意引导你经历这个过渡。本书第3版的代码也适用于Python 2和Python 3(视状况而定——并不是全部代码都移植了过来)。在移植代码时,咱们还会讨论各类工具和作法。

Python 3.x版本带来的挑战延续着对Python编程语言进行迭代和改进的趋势,要移除Python语言最后的重大缺陷还有很长的路要走,并且在不断演变的Python语言中移除重大缺陷也是一个至关大的飞跃。与之类似,本书的结构也作出了至关重大的转变。限于篇幅和范围,已出版的第2版没法处理第3版中引入的全部新内容。

所以,Prentice Hall和我想到了一个好方法来向前推动本书,即从逻辑上将其拆分为两部分,其中一部分讲述Python核心语言主题,另外一部分讲述高级应用主题,并由此将书拆分为两卷。而你手头上当前拿着的这本书是

Core Python Programming

(第3版)的第二部分。好消息是因为第二部分的内容已经至关完整齐备,所以第一部分的内容也就没有存在的必要了。要阅读本书,咱们建议读者可以拥有Python中级编程经验。若是你最近已经学过Python,并且可以至关轻松地驾驭它,或者你已经具有Python技能,可是但愿能进一步提高该技能,那么你算是找对图书了。

Core Python Programming

的读者都知道,个人主要目标是以一种全面的方式来说解Python语言的本质,而非仅仅是其语法(学习Python的语法貌似也不须要一本书)。在知道了Python的工做机制以后—包括数据对象和内存管理之间的关系—你将成为一名更高效的Python程序员。而这是第一部分(即

Core Python Language Fundamentals

)要作的工做。

与本书全部版本同样,我会继续更新图书的Web站点以及博客,以确保不管你移植到哪一个新发布的Python版本,均可以让本书作到与时俱进。

对以前的读者来讲,本书第3版新增了下述主题:

  • 基于Web的E-mail示例(第3章);

  • 使用Tile/Ttk(第5章);

  • 使用MongoDB(第6章);

  • 更重要的Outlook和PowerPoint示例(第7章);

  • Web服务器网关接口(WSGI)(第10章);

  • 使用Twitter(第13章);

  • 使用Google+(第15章)。

此外,咱们还在当前版本中添加了全新的3章,分别是第11章、第12章和第14章。这几章表明着常用Python进行应用开发的一些新领域或正在进行的领域。全部的现有章节已经面目一新,并更新到Python的最新版本,同时还包含了一些新内容。经过随后的“章节指南”部分,你能够了解到本书每部分要讲解的内容。


关于本书


在本书中,你将会用到从其余地方学习到的全部Python知识,并培养新的技能,从而构建本身的工具箱。借助于该工具箱,你可以使用Python开发各类类型的应用程序。关于高级主题的章节旨在快速概述各类不一样的主题。若是你开始转向这些章节中涵盖的特定应用开发领域,你将会发现它们不只给出了正确的方向,还包含了更多的信息。可是不要期待有一个深刻的解决方案,由于这有悖于本书的初衷—提供更为普遍的解决方案。

与其余全部

Core Python

图书同样,本书一样包含了许多示例,你能够在计算机上进行尝试。为了牢固掌握概念,你也会在每章最后发现有趣、有挑战性的练习。这些初级和中级难度的练习旨在测试你的知识掌握状况,提高你的Python技能。毕竟,没有什么能够替代实践经验。咱们相信,你不只可以学到不少Python编程技能,同时还能在尽量短的时间内迅速掌握它们。

对咱们来说,扩展Python技能的最佳方式就是动手练习,所以你会发现这些练习是本书的一个最大优点。它们能够测试你对每章主题和定义的掌握状况,并激励你尽量多地动手编程。除了本身编写应用程序以外,没有其余方法能够更有效地提高你的编程技能。你须要解决初级、中级和高级难度的编程问题。并且你应该须要编写一个大型的应用程序(这也是不少读者想要在本书中看到的),而不是采用一些脚原本实现。坦白说,你可能作得没有那么好,可是经过亲自动手实践,你的收获会更大。附录A给出了每章中某些练习的答案。附录B包含了一些有用的参考表。

感谢全部读者的反馈和鼓励,大家是我写做这些图书的动力。但愿大家能继续给我发送反馈信息,并促使本书第4版尽快问世,并且其质量优于以前全部版本。


本书分为3部分。其中第1部分占据了本书2/3的篇幅,它讲解了应用开发工具箱中(固然,Python是关注重点)“核心”成员的解决方案。第2部分讲解了与Web编程相关的各类主题。第3部分是补充部分,它提供了一些仍然在开发过程当中的实验章节,在本书后续版本中,这些章节有望成为独立的章节。

本书提供了一些高级主题,以展现Python能够用来开发什么应用程序。值得高兴的是,本书起码能够向你提供Python开发中许多关键领域的入门知识,其中包括以前版本中提到的一些主题。

下面是本书每章的内容简介。

第1部分:通用应用主题

第1章——正则表达式

正则表达式是一种功能强大的工具,它能够用来进行模式匹配、提取、查找和替换。

第2章——网络编程

现在许多应用都是面向网络的。该章将介绍如何使用TCP/IP与UDP/IP来建立客户端和服务器,以及如何快速入门SocketServer和Twisted。

第3章——因特网客户端编程

现在在用的大多数Internet协议都是使用套接字开发的。该章将探究一些用来构建Internet协议客户端的高级库。该章重点讨论的是FTP、Usenet消息协议(NNTP)以及各类E-mail协议(SMTP、POP3及IMAP4)。

第4章——多线程编程

多线程编程是一种经过引入并发来提高多种应用程序执行性能的方式。该章经过解释概念并展现正确建立Python多线程应用程序的方法、什么是最佳用例来说解如何在Python中实现线程。

第5章——GUI编程

Tkinter(在Python 3中重名为tkinter)以Tk图形工具包为基础,是Python中的默认GUI开发库。该章经过演示如何建立简单的GUI应用来介绍Tkinter。一种最佳的学习方式是复制,并在某些应用的顶层进行建立,这样能够很快上手。该章最后简要讨论其余图形库,好比Tix、Pmw、wxPython、PyGTK和Ttk/Tile。

第6章——数据库编程

Python也有助于简化数据库编程。该章首先回顾一些基本概念,而后介绍Python数据库应用编程接口(DB-API)。随后介绍如何使用Python链接到关系数据库,并执行查询和操做。若是你更喜欢使用结构化查询语言(SQL)的放手管理方法(hands-off approach),并且只是想在无须考虑底层数据库层的状况下处理对象,则可使用对象-关系映射。最后,该章以MongoDB做为NoSQL示例介绍了非关系数据库。

第7章——Microsoft Office编程

不管喜欢与否,咱们都生活在一个不得不和Microsoft Windows PC打交道的世界。咱们可能偶尔与它们打交道,也可能天天都要接触到它们,可是不管处于哪一种状况下,均可以使用Python的强大功能来让生活更轻松一些。该章将探究使用Python来编写COM客户端,以控制Office应用程序(好比Word、Excel、PowerPoint和Outlook)并与它们进行通讯。尽管该章在本书以前版本中是实验章节,可是咱们很高兴可以为其添加足够的内容,使其单独成章。

第8章——扩展Python

前面提到,可以重用代码并对语言进行扩展将具备至关强大的功能。在纯Python中,这些扩展是模块和包,可是你也可使用C/C++、C#或Java来开发底层的代码。这些扩展可以以无缝方式与Python相接。用低级编程语言来编写本身的扩展能够提高性能,并加强安全性(由于源代码没有必要泄露)。该章讲解使用C语言来开发扩展的整个过程。

第2部分:Web开发

第9章——Web客户端和服务器

该章将扩展第2章讨论的客户端/服务器架构,咱们将这一律念应用到Web上。该章不只探究客户端,还介绍用来解析Web内容的各类Web客户端工具。最后,该章介绍如何使用Python来定制本身的Web服务器。

第10章——Web编程:CGI和WSGI

Web服务器的主要工做是接受客户端的请求,而后返回结果。可是服务器如何得到客户端的请求数据呢?因为服务器只擅长返回结果,所以它们一般没有获取数据的能力或逻辑,因而这个工做须要在他处完成。CGI给了服务器生成另一个程序的能力,让这个程序来进行数据处理(长久以来一直也是这么作的),可是该程序不具有扩展性,所以并不会在实践中使用。可是,不管使用的是什么框架,这一律念仍然适用,所以咱们将用一章的篇幅来学习CGI。该章介绍WSGI如何经过通用编程接口来为应用开发人员提供帮助。此外,该章还将介绍当框架开发人员须要在一端链接Web服务器而应用程序的代码放在另一端时,WSGI如何提供帮助,以便应用开发人员可以在无须担忧执行平台的状况下编写代码。

第11章——Web框架:Django

Python有不少Web框架,Django是其中最为流行的一个。该章介绍这个框架,而后介绍如何编写简单的Web应用。在具有了这些知识后,你能够自行研究其余Web框架。

第12章——云计算:Google App Engine

云计算在IT业界引起了轰动。尽管像Amazon的AWS这样的基础设施服务和Gmail、Yahoo!Mail这样的在线应用等在当今世界中更为常见,可是有不少平台凭借其强大的功能,成为这些服务的替代者。这些平台充分利用了基础设施,无须用户介入,并且要比云软件具备更多的灵活性,缘由是你能够自行控制应用及其代码。该章全面介绍使用Python的第一个平台服务——Google App Egnine。在掌握了该章的内容后,你能够探讨该章介绍的其余相似服务。

第13章——Web服务

该章介绍Web上的高级服务(使用HTTP)。该章先介绍一个较为古老的服务(Yahoo!Finance),而后再给出一个较新的服务(Twitter)。该章讨论如何使用Python以及前面学到的知识来与这些服务进行交互。

第3部分:补充/实验章节

第14章——文本处理

这是本书的第一个补充章节,它介绍使用Python来处理文本的方法。该章先介绍CSV,而后是JSON,最后是XML。在该章最后一节,咱们将前面学到的客户端/服务器知识融合到XML中,以查看如何使用XML-RPC来建立在线的远程过程调用(RPC)。

第15章——其余内容

该章包含一些附加材料,这些内容可能会在本书下一版中成为单独的章节。该章讨论的主题包含Java/Jython和Google+。


导 读 第8章 扩展Python

 

C语言效率很高。但这种效率的代价是须要用户亲自进行许多低级资源管理工做。因为如今的机器性能很是强大,这种亲历亲为是得不偿失的。若是能使用一种在机器执行效率较低而用户开发效率很高的语言,则是很是明智的。Python就是这样的一种语言。

——Eric Raymond,1996年10月

本章内容:

  • 简介和动机;

  • 编写Python扩展;

  • 相关主题。

本章将介绍如何编写扩展代码,并将其功能集成到Python编程环境中。首先介绍这样作的动机,接着逐步介绍如何编写扩展。须要指出的是,虽然Python扩展主要用C语言编写,且出于通用性的考虑,本节的全部示例代码都是纯C语言代码。由于C++是C语言的超集,因此读者也可使用C++。若是读者使用Microsoft Visual Studio构建扩展,须要用到Visual C++。

8.1 简介和动机

本章第一节将介绍什么是Python扩展,并尝试说明什么状况下须要(或不须要)考虑建立一个扩展。

8.1.1 Python扩展简介

通常来讲,任何能够集成或导入另外一个Python脚本的代码都是一个扩展。这些新代码可使用纯Python编写,也可使用像C和C++这样的编译语言编写(在Jython中用Java编写扩展,在IronPython中用C#或VisualBasic.NET编写扩展)。

核心提示:在不一样的平台上分别安装客户端和服务器来运行网络应用程序!

这里须要提醒一下,通常来讲,即便开发环境中使用了自行编译的Python解释器,Python扩展也是通用的。手动编译和获取二进制包之间存在着微妙的关系。尽管编译比直接下载并安装二进制包要复杂一些,可是前者能够灵活地定制所使用的Python版本。若是须要建立扩展,就应该在与扩展最终执行环境类似的环境中进行开发。

本章的示例都是在基于UNIX的系统上构建的(这些系统一般自带编译器),但这里假定读者有可用的C/C++(或Java)编译器,以及针对C/C++(或Java)的Python开发环境。这二者的惟一区别仅仅是编译方法。而扩展中的实际代码可通用于任何平台上的Python环境中。

若是是在Windows平台上开发,须要用到Visual C++开发环境。Python发行包中自带了7.1版的项目文件,但也可使用老版本的VC++。

关于构建Python扩展的更多信息请查看下面的网址。

警告:
尽管在相同架构下的不一样计算机之间移动二进制扩展通常状况下不会出现问题,可是有时编译器或CPU之间的细微差异可能致使代码不能正常工做。

Python中一个很是好的特性是,不管是扩展仍是普通Python模块,解释器与其交互方式彻底相同。这样设计的目的是对导入的模块进行抽象,隐藏扩展中底层代码的实现细节。除非模块使用者搜索相应的模块文件,不然他就不会知道某个模块是使用Python编写,仍是使用编译语言编写的。

8.1.2 什么状况下须要扩展Python

简要纵观软件工程的历史,编程语言过去一直都根据原始定义来使用。只能使用语言定义的功能,就没法向已有的语言添加新的功能。然而,在现今的编程环境中,可定制性编程是很吸引人的特性,它能够促进代码重用。Tcl和Python就是第一批这样可扩展的语言,这些语言可以扩展其语言自己。那么为何须要扩展像Python这样已经很完善的语言呢?有下面几点充分的理由。

  • 须要Python没有的额外功能:扩展Python的缘由之一是须要该语言核心部分没有提供一些的新功能。使用纯Python或编译后的扩展均可以作到这一点,不过像建立新的数据类型或在已有应用中嵌入Python,就必须使用编译后的模块。

  • 改善瓶颈性能:众所周知,因为解释型语言的代码在运行时即时转换,所以执行起来比编译语言慢。通常来讲,将一段代码移到扩展中能够提高整体性能。但问题在于,若是转移到扩展中,有时代价会太高。
    从性价比的角度来看,先对代码进行一些简单的性能分析,找出瓶颈所在,而后将这些瓶颈处的代码移到扩展中是个更聪明的方式。这样既能更快地得到效率提高,也不会花费太多的资源。

  • 隐藏专有代码:建立扩展的另外一个重要缘由是脚本语言的缺陷。全部这样易用的语言都没有关注源码的私密性,由于这些语言的源码自己就是可执行程序。

    将代码从Python中转到编译型语言中能够隐藏这些专有代码,由于后者提供的是二进制文件。编译过的文件相对来讲不易进行逆向工程,这样就将源码隐藏起来了。在涉及特殊算法、加密或软件安全性时这,这就显得十分重要。

    另外一个保证代码私有的方式是只提供预编译的.pyc文件。在提供实际代码(.py文件)和将代码迁移到扩展这两种方法之间,这是比较好的折中。

8.1.3 什么状况下不该该扩展Python

在真正介绍如何编写扩展以前,还要了解什么状况下不该该编写扩展。这一节至关于一个告诫,不然读者会认为做者一直在为扩展Python作虚假宣传。是的,编写扩展有前面提到的那些优势,但也有一些缺点。

  • 必须编写C/C++代码。

  • 须要理解如何在Python和C/C++之间传递数据。

  • 须要手动管理引用。

  • 还有一些封装工具能够完成相同的事情,这些工具能够生成高效的C/C++代码,但用户又无须手动编写任何C/C++代码就可使用这些代码。本章末尾将介绍其中一些工具。不要说我没提醒过你!下面继续……

8.2 编写Python扩展

为Python编写扩展主要涉及三个步骤。

1.建立应用代码。

2.根据样板编写封装代码。

3.编译并测试。

本节将深刻了解这三个步骤。

8.2.1 建立应用代码

首先,全部须要成为扩展的代码应该组成一个独立的“库”。换句话说,要明白这些代码将做为一个Python模块存在。所以在设计函数和对象时须要考虑Python代码与C代码之间的交互和数据共享,反之亦然。

下一步,建立测试代码来保证代码的正确性。甚至可使用Python风格的作法,即将main()函数放在C中做为测试程序。若是代码编译、连接并加载到一个可执行程序中(而不是共享库文件),调用这样的可执行程序能对软件库进行回归测试。下面将要介绍的扩展现例都使用这种方法。

测试用例包含两个须要引入Python环境中的C函数。一个是递归阶乘函数fac()。另外一个是简单的字符串逆序函数reverse(),主要用于“原地”逆序字符串,即在不额外分配字符串空间的状况下,逆序排列字符串中的字符。因为这些函数须要用到指针,所以须要仔细设计并调试这些C代码,以防将问题带入Python。

第1版的文件名为Extest1.c,参见示例8-1。

示例8-1 纯C版本的库(Extest1.c)

这段代码含有两个函数:fac()和reverse(),用来实现前面所说的功能。fac()接受一个整型参数,而后递归计算结果,最后从递归的最外层返回给调用者。

最后一部分是必要的main()函数。它用来做为测试函数,将不一样的参数传入fac()和reverse()。经过这个函数能够判断前两个函数是否能正常工做。

如今编译这段代码。许多类UNIX系统都含有gcc编译器,在这些系统上可使用下面的命令。


要运行代码,能够执行下面的命令并得到输出。


再次强调,必须尽量先完善扩展程序的代码。把针对Python程序的调试与针对扩展库自己bug的调试混在一块儿是一件很是痛苦的事情。换句话说,将调试核心代码与调试Python程序分开。与Python接口的代码写得越完善,就越容易把它集成进Python并正确工做。

这里每一个函数都接受一个参数,也只返回一个参数。这简单明了,所以集成进Python应该不难。注意,到目前为止,还没涉及任何与Python相关的内容。仅仅建立了一个标准的C或C++应用而已。

8.2.2 根据样板编写封装代码

完整地实现一个扩展都围绕“封装”相关的概念,读者应该熟悉这些概念,如组合类、修饰函数、类委托等。开发者须要精心设计扩展代码,无缝链接Python和相应的扩展实现语言。这种接口代码一般称为样板(boilerplate)代码,由于若是须要与Python解释器交互,会用到一些格式固定的代码。

样板代码主要含有四部分。

1.包含Python头文件。

2.为每个模块函数添加形如PyObject*

Module_func

()的封装函数。

3.为每个模块函数添加一个PyMethodDef

ModuleMethods

[]数组/表。

4.添加模块初始化函数

void initModule

()。

包含Python头文件

首先要作的是找到Python包含文件,并确保编译器能够访问这个文件的目录。在大多数类UNIX系统上,Python包含文件通常位于/usr/local/include/python2.x或/usr/include/python2.x中,其中2.x是Python的版本。若是经过编译安装的Python解释器,应该不会有问题,由于系统知道安装文件的位置。

将Python.h这个头文件包含在源码中,以下所示。

这部分很简单。下面须要添加样板软件中的其余部分。

为函数编写形如PyObject* Module_func()的封装函数

这一部分有点难度。对于每一个须要在Python环境中访问的函数,须要建立一个以static PyObject\*标识,以模块名开头,紧接着是下划线和函数名自己的函数。

例如,若要让fac()函数能够在Python中导入,并将Extest做为最终的模块名称,须要建立一个名为Extest_fac()的封装函数。在用到这个函数的Python脚本中,可使用import Extest和Extest.fac()的形式在任意地方调用fac()函数(或者先from Extest import fac,而后直接   调用fac())。

封装函数的任务是将Python中的值转成成C形式,接着调用相应的函数。当C函数执行完毕时,须要返回Python的环境中。封装函数须要将返回值转换成Pytho形式,并进行真正的返回,传回全部须要的值。

在fac()的示例中,当客户程序调用Extest.fac()时,会调用封装函数。这里会接受一个Python整数,将其转换成C整数,接着调用C函数fac(),获取返回结果,一样是一个整数。将这个返回值转换成Python整数,返回给调用者(记住,编写的封装函数就是def fac(n)声明的代理函数。当这个封装函数返回时,就至关于Python fac()函数执行完毕了)。

如今读者可能会问,怎样才能完成这种转换?答案是在从Python到C时,调用一系列的PyArg_Parse*()函数,从C返回Python时,调用Py_BuildValue()函数。

这些PyArg_Parse*()函数与C中的sscanf()函数相似。其接受一个字节流,而后根据一些格式字符串进行解析,将结果放入到相应指针所指的变量中。若解析成功就返回1;不然返回0。

Py_BuildValue()的工做方式相似sprintf(),接受一个格式字符串,并将全部参数按照格式字符串指定的格式转换为一个Python对象。

表8-1总结了这些函数。

表8-1 在Python和C/C++之间转换数据

在Python和C之间使用一系列的转换编码来转换数据对象。转换编码见表8-2。

表8-2 Python和C/C++之间的“转换编码”

格式编码

Python数据类型

① Python 2和Python 3之间的格式编码基本相同。

② 与“O”相似,但不递增对象的引用计数。

这些转换编码用在格式字符串中,用于指出对应的值在两种语言中应该如何转换。注意,其转换类型不可用于Java中,Java中全部数据类型都是类。能够阅读Jython文档来了解Java类型和Python对象之间的对应关系。对于C#和VB.NET一样如此。

这里列出完整的Extest_fac()封装函数。

封装函数中首先解析Python中传递进来的参数。这里应该是一个普通的整型变量,因此使用“i”这个转换编码来告知转换函数进行相应的操做。若是参数的值确实是一个整型变量,则将其存入num变量中。不然,PyArg_ParseTuple()会返回NULL,在这种状况下封装函数也会返回NULL。此时,它会生成TypeError异常来通知客户端用户,所需的参数应该是一个整型变量。

接着使用num做为参数调用fac()函数,将结果放在res中,这里重用了res变量。如今构建返回对象,即一个Python整数,依然经过“i”这个转换编码。Py_BuildValue()建立一个整型Python对象,并将其返回。这就是封装函数的全部内容。

实际上,当封装函数写多了后,就会试图简化代码来避免使用中间变量。尽可能让代码保持可读性。这里将Extest_fac()函数精简成下面这个更短的版本,它只用了一个变量num。

那reverse()怎么实现?因为已经知道如何返回单个值,这里将对reverse()的需求稍微修改下,返回两个值。将以元组的形式返回一对字符串,第一个元素是传递进来的原始字符串,第二个是新逆序的字符串。

为了更灵活地调用函数,这里将该函数命名为Extest.doppel(),来表示其行为与reverse()有所不一样。将C代码封装进Extest_doppel()函数中,以下所示。

在Extest_fac()中,接受一个字符串值做为输入,将其存入orig_str中。注意,选择使用“s”这个转换编码。接着调用strdup()来建立该字符串的副本。(由于须要返回原始字符串,同时须要一个字符串来逆序,因此最好的选择是直接复制原始字符串。)strdup()建立并返回一个副本,该副本当即传递给reverse()。这样就得到逆序后的字符串。

如你所见,Py_BuildValue()使用转换字符串“ss”将这两个字符串放到了一块儿。这里建立了含有原始字符串和逆序字符串的元组。都结束了吗?尚未。

这里遇到了C语言中一个危险的东西:内存泄露(分配了内存但没有释放)。内存泄露就至关于从图书馆借书,可是没有归还。在获取了某些资源后,当再也不须要时,必定要释放这些资源。咱们怎么能在代码中犯这样的错误呢(虽然看上去很无辜)?

当Py_BuildValue()将值组合到一个Python对象并返回时,它会建立传入数据的副本。在这里的例子中,建立了一对字符串。问题在于分配了第二个字符串的内存,但在结束时没有释放这段内存,致使了内存泄露。而实际想作的是构建返回值,接着释放在封装函数中分配的内存。为此,必须像下面这样修改代码。


这里引入了dupe_str变量来指向新分配的字符串并构建返回对象。接着使用free()来释放分配的内容,并最终返回给调用者。如今才算真正完成。

为模块编写PyMethodDef ModuleMethods[]数组

既然两个封装函数都已完成,下一步就须要在某个地方将函数列出来,以便让Python解释器知道如何导入并访问这些函数。这就是

Module

Methods[]数组的任务。

这个数组由多个子数组组成,每一个子数组含有一个函数的相关信息,母数组以NULL数组结尾,表示在此结束。对Extest模块来讲,建立下面这个ExtestMethods[]数组。

首先给出了在Python中访问所用到的名称,接着是对应的封装函数。常量METH_VARARGS表示参数以元组的形式给定。若是使用PyArg_ParseTupleAndKeywords()来处理包含关键字的参数,须要将这个标记与METH_KEYWORDS常量进行逻辑OR操做。最后,使用一对NULL来表示结束函数信息列表,还表示只含有两个函数。

添加模块初始化函数void initModule()

最后一部分是模块初始化函数。当解释器导入模块时会调用这段代码。这段代码中只调用了Py_Init

Module

()函数,其第一个参数是模块名称,第二个是

Module

Methods[]数组,这样解释器就能够访问模块函数。对于Extest模块,其initExtest()过程以下所示。


如今已经完成了全部封装任务。将Extest1.c中原先的代码与全部这些代码合并到一个新文件Extest2.c中。至此,就完成了示例中的全部开发步骤。

另外一种建立扩展的方式是先编写封装代码,使用存根(stub)函数、测试函数或假函数,在开发的过程当中将其替换成具备完整功能的实现代码。经过这种方式,能够保证Python和C之间接口的正确性,并使用Python来测试相应的C代码。

8.2.3 编译

如今进入了编译阶段。为了构建新的Python封装扩展,须要将其与Python库一同编译。(从2.0版开始)扩展的编译步骤已经跨平台标准化了,简化了扩展编写者的工做。如今使用distutils包来构建、安装和发布模块、扩展和软件包。从Python 2.0开始,这种方式替换了老版本1.x中使用makefile构建扩展的方式。使用distutils,能够经过下面这些简单的步骤构建扩展。

1.建立setup.py。

2.运行setup.py来编译并连接代码。

3.在Python中导入模块。

4.测试函数。

建立setup.py

第一步就是建立setup.py文件。大部分编译工做由setup()函数完成。在该函数以前的全部代码都只是预备步骤。为了构建扩展模块,须要为每一个扩展建立一个Extension实例。由于这里只有一个扩展,因此只需一个Extension实例。


第一个参数是扩展的完整名称,以及该扩展中拥有的全部高阶包。该名称应该使用完整的点分割表示方式。因为这里是个独立的包,所以名称为“Extest”。sources参数是全部源码文件的列表。一样,只有一个文件Extest2.c。

如今就能够调用setup()。其接受一个命名参数来表示构建结果的名称,以及一个列表来表示须要构建的内容。因为这里是建立一个扩展,所以设置一个含有扩展模块的列表,传递给ext_modules。语法以下所示。

因为这里只有一个模块,所以将扩展模块的实例化代码集成到setup()的调用中,在预备步骤中将模块名称设置为“常量”MOD。

setup()中含有许多其余选项,这里就不一一列举了。读者能够在官方的Python文档中找到关于建立setup.py和调用setup()的更多信息,在本章末尾能够找到这些连接。示例8-2显示了示例扩展中用到的完整脚本。

示例8-2 构建脚本(setup.py)

运行setup.py来编译并连接代码

既然有了setup.py文件,就运行python setup.py build命令构建扩展。这里在Mac上完成构建(根据操做系统和Python版本的不一样,对应的输出与下面的内容会有些差异)。


8.2.4 导入并测试

最后一步是回到Python中使用扩展包,就像这个扩展就是用纯Python编写的那样。

在Python中导入模块

扩展模块会建立在build/lib.*目录下,即运行setup.py脚本的位置。要么切换到这个目录中,要么用下面的方式将其安装到Python中。

若是安装该扩展,会获得下面的输出。


如今能够在解释器中测试模块了。

添加测试函数

须要完成的最后一件事是添加测试函数。实际上,咱们已经有测试函数了,就是那个main()函数。但要当心,在扩展代码中含有main()函数有潜在的风险,由于系统中应该只有一个main()函数。将main()的名称改为test()并对其封装能够消除这个风险,添加Extest_test()并更新ExtestMethods数组,以下所示。

Extest_test()模块函数仅仅运行test()并返回一个空字符串,在Python中是一个None值返回给调用者。

如今能够在Python中进行相同的测试。

示例8-3中列出了Extest2.c的最终版本,上述输出都是用这个版原本完成的。

示例8-3 C函数库的Python封装版本(Extest2.c)


在这个示例中,仅在同一个文件中将原始的C代码与Python相关的封装代码进行了隔离。这样方便阅读,在这个短小的例子中也没有什么问题。但在实际应用中,源码文件会越写越大,能够将其分割到不一样的源码文件中,使用如ExtestWrappers.c这样好记的名字。

8.2.5 引用计数

也许读者还记得Python使用引用计数来追踪对象,并释放再也不引用的对象。这是Python垃圾回收机制的一部分。当建立扩展时,必须额外注意如何处理Python对象,必须留心是否须要修改此类对象的引用计数。

一个对象有两种类型的引用,一种是拥有引用(owned reference),对该对象的引用计数递增1表示拥有该对象的全部权。当从零建立一个Python对象时,就必定会含有一个拥有引用。

当使用完一个Python对象后,必须对全部权进行处理,要么递减其引用计数,经过传递它转移其全部权,要么将该对象存储到其余容器。若是没有处理引用计数,则会致使内存泄漏。

对象还有一个借用引用(borrowered reference)。相对来讲,这种方式的责任就小一些。通常用于传递对象的引用,但不对数据进行任何处理。只要在其引用计数递减至零后不继续使用这个引用,就无须担忧其引用计数。能够经过递增对象的引用计数来将借用引用转成拥有引用。

Python提供了一对C宏来改变Python对象的引用计数。如表8-3所示。

{-:-}表8-3 用于执行Python对象引用计数的宏

函  数


在上面的Extest_test()函数中,在构建PyObject对象时使用空字符串来返回None。但能够经过拥有一个None对象来完成这个任务。即递增一个PyNone的引用计数并显式返回这个对象,以下所示。

Py_INCREF()和 Py_DECREF()还有一个先检测对象是否为 NULL 的版本,分别为Py_XINCREF()和Py_XDECREF()。

这里强烈建议读者阅读相关Python文档中关于扩展和嵌入Python里面全部关于引用计数的细节(详见附录C中的参考文献部分)。

8.2.6 线程和全局解释器锁

扩展的编写者必需要注意,他们的代码可能在多线程Python环境中执行。4.3.1节介绍了Python虚拟机(Python Virtual Machine,PVM)和全局解释器锁(Global Interpreter Lock,GIL),描述了在PVM中,任意时间只有一个线程在执行,GIL就负责阻止其余线程的执行。除此以外,还指出了调用外部函数的代码,如扩展代码,将会锁住GIL,直至外部函数返回。

但也提到了一种折衷方法,即让扩展开发者释放GIL。例如,在执行系统调用前就能够实现。这是经过将代码和线程隔离实现的,这些线程使用了另外的两个C宏:Py_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS,保证了运行和非运行时的安全性。用这些宏围起来的代码块会容许其余线程在其执行时同步执行。

与引用计数宏相同,这里也建议读者阅读Python文档中关于扩展和嵌入Python的内容,以及Python/C API参考手册。

延伸推荐


点击关键词阅读更多新书:

Python|机器学习|Kotlin|Java|移动开发|机器人|有奖活动|Web前端|书单


在“异步图书”后台回复“关注”,便可免费得到2000门在线视频课程;推荐朋友关注根据提示获取赠书连接,免费得异步图书一本。赶忙来参加哦!

点击阅读原文,查看本书更多信息

扫一扫上方二维码,回复“关注”参与活动!

相关文章
相关标签/搜索