使用C语言来扩展Python程序和Zope服务器的教程


Posted in Python onApril 14, 2015

有几个原因使您可能想用 C 扩展 Zope。最可能的是您有一个已能帮您做些事的现成的 C 库,但是您对把它转换成 Python 却不感兴趣。此外,由于 Python 是解释性语言,所以任何被大量调用的 Python 代码都将降低您的速度。因此,即使您已经用 Python 写了一些扩展,您仍然要考虑把其中最常被调用的部分改用 C 来写。不论哪种方式,扩展 Zope 都是从扩展 Python 开始。此外,扩展 Python 会给您带来其它的好处,因为您的代码将可以从任何 Python 脚本访问,而不只是从 Zope。这里唯一要提醒的是在写本文的时候,Python 的当前版本是 2.1,但是 Zope 仍然只能和 Python 1.5.2 一起运行。对 C 扩展来说,两个版本并没有什么变化,但如果您有兴趣对您的库进行 Python 包装,又想让它们都能在 Zope 下工作,您就得注意不要使用任何比 1.5.2 更新的东西。
Zope 是什么?

Zope 代表“Z Object Publishing Environment(Z 对象发布环境)”,它是用 Python 实现的应用程序服务器。“太棒了,”您说,“但应用程序服务器的确切含义是什么呢?”应用程序服务器就是一个长期运行的进程,它为“活动的内容”提供服务。Web 服务器在运行期间调用应用程序服务器来构建页面。
扩展 Python:有趣又有益

想扩展 Zope,您首先要扩展 Python。虽然扩展 Python 不像“脑外科手术”那样复杂,但也不像“在公园中散步”那样悠闲。有两个基本组件用于 Python 扩展。第一个显然是 C 代码。我将马上探讨它。 另一个部分是 安装文件。安装文件通过提供模块名称、模块的 C 代码的位置和您可能需要的所有编译器标志来描述模块。该文件被预处理,以创建 makefile(在 UNIX 上)或 MSVC++ 工程文件(MSVC++ project file,在 Windows 上)。先说一下 ? Windows 上的 Python 事实上是用 Microsoft 编译器编译的。Python.org 的人也推荐用 MSVC++ 编译扩展。显然,您应该能够成功说服 GNU 的编译者们,但我本人还没试过。

无论如何,还是让我们来定义一个叫做‘foo'的模块吧。‘foo'模块会有一个叫做‘bar'的函数。当我们要使用时,我们可以用 import foo; 来把这个函数导入到 Python 脚本中,就跟导入任何模块一样。安装文件非常简单:
清单 1. 一个典型的安装文件

# You can include comment lines. The *shared* directive indicates
# that the following module(s) are to be compiled and linked for
# dynamic loading as opposed to static: .so on Unix, .dll on Windows.
*shared*
# Then you can use the variables later using the $(variable) syntax
# that 'make' uses. This next line defines our module and tells
# Python where its source code is.
foo foomain.c

编写代码

那么我们实际上该怎样写 Python 知道如何使用的代码呢,您问? foomain.c (当然,您可以随意命名它)文件包含三项内容:一个方法表,一个初始化函数和其余的代码。方法表简单地将函数名与函数联系起来,并告知 Python 各个函数所使用的参数传递机制(您可以选择使用一般的位置参数列表或位置参数和关键词参数的混合列表)。Python 在模块装入时调用初始化函数。初始化函数将完成模块所要求的所有初始化操作,但更重要的是,它还把一个指向方法表的指针传回给 Python。

那我们就来看看我们的小型 foo 模块的 C 代码。
清单 2. 一个典型的 Python 扩展模块

#include <Python.h>
/* Define the method table. */
static PyObject *foo_bar(PyObject *self, PyObject *args);
static PyMethodDef FooMethods[] = {
  {"bar", foo_bar, METH_VARARGS},
  {NULL, NULL}
};
/* Here's the initialization function. We don't need to do anything
  for our own needs, but Python needs that method table. */
void initfoo()
{
  (void) Py_InitModule("foo", FooMethods);
}
/* Finally, let's do something ... involved ... as an example function. */
static PyObject *foo_bar(PyObject *self, PyObject *args)
{
  char *string;
  int  len;
  if (!PyArg_ParseTuple(args, "s", &string))
    return NULL;
  len = strlen(string);
  return Py_BuildValue("i", len);
}

深入研究

我们来看会儿这些代码。首先,请注意您必须包含 Python.h 。除非您已在包含路径(include path)中设置了该文件的路径,否则您可能需要在安装文件中包含 -I 标志以指向该文件。

初始化函数必须命名为 init <模块名>,在我们的例子中是 initfoo 。初始化函数的名称,毫无疑问,是 Python 在装入模块时所知道的关于模块的全部信息,这也是初始化函数的名称如此死板的原因。顺便说一下,初始化函数必须是文件中唯一未被声明为 static 的全局标识符。这对静态链接比对动态链接更重要,因为非 static 标识符将是全局可见的。对动态链接来说,这不是一个很大的问题,但如果您打算在编译期间链接所有东西,又没有把所有可以声明为 static 的东西声明为 static ,那么您很可能就会碰到名称冲突的问题。

现在我们来观察实际的代码,看看参数是怎样被处理的,返回值又是怎样被传递的。当然,一切都是 PyObject ? Python 堆上的对象。您从参数中得到的是一个对“this”对象的引用(this 用于对象方法,对类似 bar() 这样的无参数的老式函数来说是 NULL)和一个存储在 args 中的参数元组。您用 PyArg_ParseTuple 找回您的参数,然后用 Py_BuildValue 把结果传回去。这些函数(还有更多)都归档在 Python 文档的“Python/C API”部分中。不幸的是,没有按名称排列的简单的函数清单,文档是按主题排列的。

另请注意,函数在出错的情况下返回 NULL。返回 NULL 表示出错了;如果想让 Python 做得更好,您应该抛出异常。我会指点您去查阅关于如何做这件事的文档。

编译扩展

现在剩下的全部问题是编译模块。您可以通过两种方式进行。第一种是按照文档中的指导,运行 make -f Makefile.pre.in boot ,这样将会使用您的 Setup 来编译一个 Makefile。然后您就用该 Makefile 编译您的工程。这种方式只适用于 UNIX。对 Windows 来说,存在一个叫“compile.py”的脚本(请参阅本文后面的 参考资料)。原始脚本很难找到;我从一个邮件列表中找到了一个来自 Robin Dunn(wxPython 的幕后工作者)的被大量改动了的副本。这个脚本能在 UNIX 和 Windows 上工作;在 Windows 上,它将从您的 Setup 开始编译 MSVC++ 工程文件。

要进行编译,您必须使包含的文件和库都可用。Python 的标准 Zope 安装没有包含这些文件,因此您需要从 www.python.org(请参阅 参考资料)安装 Python 的常规安装。在 Windows 上,您还必须从源代码安装的 PC 目录中获取 config.h 文件;它是 UNIX 安装为您编译的 config.h 的手工版。因此,在 UNIX 上,您应该已经拥有它了。

一旦这些都完成后,您就会得到一个以“.pyd”为扩展名的文件。把这个文件放到 Python 安装目录下的“lib”目录(在 Zope 下,Python 位于“bin”目录,因此您的扩展得结束于“bin/lib”目录,奇怪吧。)然后您就可以调用它了,就像调用任何源生的 Python 模块一样。

>>> import foo;
 >>> foo.bar ("This is a test");
 14

做到这里时,我的第一个问题是问自己该如何用 C 定义从 Python 中可见的 类。事实上,我可能问了一个错误的问题。在我已研究的示例中,特定于 Python 的一切都只 用 Python 来完成,也都只调用从您的扩展中导出的 C 函数。

把它带到 Zope 中去

一旦完成了您的 Python 扩展,下一步就是使 Zope 能和它一起工作。您有几种方式可以选择,但在一定程度上,您希望您的扩展以什么方式与 Zope 一起工作将首先影响到您编译扩展的方式。从 Zope 内使用 Python(以及用 C 所做的扩展)代码的基本方式是:

  •     如果函数很简单,您可以把它当作一个变量。这些被叫做“外部方法”。
  •     更复杂的类,可以从 Zope 脚本中调用(这是 Zope 2.3 的一个新功能)。
  •     您可以定义一个 Zope Product,然后可以用 ZClass(一组已做好的、Web 可访问的对象)扩展它,在脚本中使用它,根据它的自有权限发布它(它的实例被当作页来对待)。

当然,您自己的应用程序可以使用这些方式的组合。

创建外部方法

从 Zope 调用 Python 的最简单的方式是把您的 Python 代码做成 外部方法。外部方法是被放到 Zope 安装目录下的“Extensions”目录中的 Python 函数。一旦那里有了这样一个 Python 文件,您就可以转到任意文件夹,选择“添加外部方法”,并添加调用要使用的函数的变量。然后您就可以往该文件夹中显示调用结果的任意页添加 DTML 字段。我们来看一个使用了上面所定义的 Python 扩展 ? foo.bar ? 的简单示例。

首先,来看扩展本身:我们把它放到一个例如叫 foo.pyd 的文件中。记住,这个文件位于 Zope 下的 Extensions 目录。为了能够顺利进行,当然,我们在上面创建的 foo.pyd 必须在位于 bin/lib 的 Python 库中。一个出于这个目的的、简单的包看起来可能像这样:
清单 3. 一个简单的外部方法(文件:Extensions/foo.py)

import foo
def bar(self,arg):
  """A simple external method."""
  return 'Arg length: %d' % foo.bar(arg)

很简单,不是吗?它定义了一个可以用 Zope 管理界面附加到任意文件夹的外部方法“bar”。要从该文件夹中的任何页中调用我们的扩展,我们只需简单地插入一个 DTML 变量引用,如下所示:

<dtml-var bar('This is a test')>

当用户查看我们的页时,DTML 字段将被文本“Arg length: 14”代替。我们就这样用 C 扩展了 Zope。

Zope 脚本:Cliff Notes 版

Zope 脚本是 Python 2.3 的一个想用来代替外部方法的新功能。外部方法能做到的,它都能做到,而且它能和安全性及管理系统更好地集成,在集成方面提供更多的灵活性,它还有很多对 Zope API 中公开的全部 Zope 功能的访问。

一个脚本基本上就是一个短小的 Python 程序。它可以定义类或函数,但不是必须的。它被作为对象安装在 Zope 文件夹中,然后就可以把它当作 DTML 变量或调用(就像一个外部方法)来调用或者“从 Web 中”(在 Zope 中的意思就是它将被当作页来调用)调用它。当然,这意味着脚本可以像 CGI 程序那样生成对表单提交的响应,但却没有 CGI 的开销。确实是一个很棒的功能。此外,脚本有权访问被调用者或调用者对象(通过“context”对象)、对象所在的文件夹(通过“container”对象)和其他一些零碎信息。要获得更多关于脚本的知识,请参阅 Zope 手册(请参阅 参考资料)中的“高级 Zope 脚本编制(Advanced Zope Scripting)”那一章。

您可能会错误地认为可以直接从脚本简单地导入 foo 并使用 foo.bar(我知道我确实犯过这种错误)。但事实并非如此。由于安全性限制,只有 Product 可以被导入,而不是什么模块都可以。一般而言,Zope 的设计者们认为任何脚本编制都需要访问文件系统,既然脚本对象是由 Web 使用 Zope 管理界面来管理,所以它们不是完全可信的。所以我打算就此打住,不给您展示示例脚本了,而是来讨论 Product 和基础类。

专注于 Product

Product 是扩展 Zope 的强大工具方法。从安装目录的级别来看,Product 就是位于 Zope 目录下的“lib/python/Products”目录中的一个目录。在您自己的 Zope 安装目录中,您可以看到很多 product 示例,但本质上,最小的 Product 只由位于该目录的两个文件组成:一个可任意命名的代码文件和一个 Zope 在启动时调用来初始化 Product 的称为 __init__.py 的文件。(请注意:Zope 只在启动时读取 Product 文件,这意味着为了测试,您必须能够停止和重新启动 Zope 进程)。本文只是尽量多提供一些您能通过使用 Zope Product 做到的事的提示。

要知道的是 Product 封装了一个或多个可从 ZClass、脚本或直接从 Web 上的 URL 使用的类。(当然,在最后一种情况下,Product 的实例被当作文件夹看待;那么 URL 的最后部分指定了将被调用的方法,该方法返回任意的 HTML。)您不必一定要把 Product 当作“可添加的”对象来对待,虽然这是它的主要目的。要看一个优秀的、现实存在的示例,可以去看 ZCatalog 实现,它是标准 Zope 分发的一部分。那里您可以在 __init__.py 中看到一个非常简单的安装脚本,可以在 ZCatalog.py 中看到 ZCatalog 类,该类提供了很多发布方法。请注意 Zope 采用一种奇怪的约定来确定哪些方法可以通过 Web 访问 ? 如果一个方法包含有一个 doc 字符串,那么该方法可通过 Web 访问;否则,就被认为是私有的。

无论如何,我们还是来看一个使用了 C 模块(我们在上面定义了它)的非常简单的 Product。首先来看非常简单的 __init__.py;请注意它只做了一件事,即告诉 Zope 我们正在安装的类的名称。更复杂的初始化脚本能做 更多的事,包括声明由服务器维护的全局变量以及设置访问权限等等。欲了解更多详细信息,请参阅在线文档中的 Zope 开发者指南,也请研究您的 Zope 安装目录中现成的 Product。您或许已经猜到了,我们的示例 Product 被称为“Foo”。这样您就将在 lib/python/Products 目录下创建一个 Foo 子目录。
清单 4. 基本的 Product 初始化脚本

import Foo
def initialize(context):
  context.registerClass(
    Foo.Foo, 
    permission='Add Foo',
    constructors=Foo.manage_addFoo
    )

现在请注意这个初始化脚本不仅导入了那个类,使它可被 Zope 的其它部件访问,而且还将该类注册成具有“可添加性”。 context.registerClass 调用通过首先命名我们所导入的类,然后指定可被用于添加实例的方法名称(这个方法必须显示一个管理页面,且该方法将自动与 Zope 管理界面集成)的名称来完成这项工作。酷。

我们来小结一下这个短小、简单的 Product。它会把我们的 foo.bar 函数公开给脚本和 ZClass,并且还有一个作为“可添加的”对象的小接口,这就是全部内容。
清单 5. 一个简单的 Zope Product

import foo
class Foo(SimpleItem.Item):
 "A Foo Product"
 meta_type = 'foo'
 def bar(self, string):
   return foo.bar(string)
 def __init__(self, id):
   "Initialize an instance"
   self.id = id
 def index_html(self):
   "Basic view of object"
   return '
My id is %s and its length is %d.
' % (self.id, foo.bar(self.id))
 def manage_addFoo(self, RESPONSE):
   "Management handler to add an instance to a folder."
   self._setObject('Foo_id', Foo('Foo_id'))
   RESPONSE.redirect('index_html')

这只是一个最简单的 Product。不能绝对地说它是可能的 Product 中最小的一个,但已经很接近了。不过,它确实说明了 Product 的一些关键特征。首先,请注意“index_html”方法:它被调用来显示一个对象实例,这是通过构建 HTML 完成的。它实际上是一个页面。 manage_addFoo 方法是 Zope 对象管理的接口;我们在上面的 __init__.py 中引用了它。“__init__”方法初始化对象;实际上它 必须做的全部工作就是记录实例的唯一标识符。

这个微型的 Product 不和 Zope 安全性进行交互操作。它不做很多管理工作。它没有交互功能。所以您可以给它添加很多东西(甚至连很有用的功能它也没有)。我希望这对您是一个很好的开始。

以后该做什么

对 Zope Product 的简单介绍已经告诉您如何把 C 语言函数从 C 代码变为 Zope 中可用的。要学会怎么写 Product,您还得阅读更多文档(其中有很多仍在完善之中),坦率地说,还要研究已有的 Product,看看它们是怎么做的。Zope 模型有很强大的功能和很大的灵活性,它们都很值得探究。

我目前正在做集成 C 和 Zope 的大工程:集成我的工作流工具包(workflow toolkit)。在本文发表之前,我希望能看到它的雏形。它已被列在下面的参考资料中,去看看吧;到您阅读本文时,应该已经能够从中找到一个扩展示例。祝我好运。

Python 相关文章推荐
SublimeText 2编译python出错的解决方法(The system cannot find the file specified)
Nov 27 Python
pycharm 使用心得(八)如何调用另一文件中的函数
Jun 06 Python
Python中endswith()函数的基本使用
Apr 07 Python
python实现自动重启本程序的方法
Jul 09 Python
python 生成器生成杨辉三角的方法(必看)
Apr 10 Python
Python实现计算两个时间之间相差天数的方法
May 10 Python
Python3使用turtle绘制超立方体图形示例
Jun 19 Python
python逆序打印各位数字的方法
Jun 25 Python
Django学习笔记之为Model添加Action
Apr 30 Python
python集合是否可变总结
Jun 20 Python
Python中变量的输入输出实例代码详解
Jul 28 Python
详解基于Jupyter notebooks采用sklearn库实现多元回归方程编程
Mar 25 Python
用Python中的wxPython实现最基本的浏览器功能
Apr 14 #Python
Python中SOAP项目的介绍及其在web开发中的应用
Apr 14 #Python
Python中的XML库4Suite Server的介绍
Apr 14 #Python
Python pickle模块用法实例
Apr 14 #Python
使用Python的PEAK来适配协议的教程
Apr 14 #Python
Python全局变量操作详解
Apr 14 #Python
Python and、or以及and-or语法总结
Apr 14 #Python
You might like
使用字符串函数输出整数化的PHP版本号
2006/10/09 PHP
php REMOTE_ADDR之获取访客IP的代码
2008/04/22 PHP
基于PHP CURL获取邮箱地址的详解
2013/06/03 PHP
ThinkPHP3.1新特性之对页面压缩输出的支持
2014/06/19 PHP
在win7中搭建Linux+PHP 开发环境
2014/10/08 PHP
PHP处理大量表单字段的便捷方法
2015/02/07 PHP
Laravel 实现密码重置功能
2018/02/23 PHP
Javascript实例教程(19) 使用HoTMetal(7)
2006/12/23 Javascript
SUN的《AJAX与J2EE》全文译了
2007/02/23 Javascript
JSQL 基于客户端的成绩统计实现方法
2010/05/05 Javascript
写JQuery插件的基本知识
2013/11/25 Javascript
javascript数组操作总结和属性、方法介绍
2014/04/05 Javascript
浅谈Javascript中深复制
2014/12/01 Javascript
深入浅析JavaScript中的作用域和上下文
2016/03/26 Javascript
input file上传 图片预览功能实例代码
2016/10/25 Javascript
javascript常用经典算法详解
2017/01/11 Javascript
原生JS实现《别踩白块》游戏(兼容IE)
2017/02/20 Javascript
Vue.js实战之组件的进阶
2017/04/04 Javascript
vue中使用vue-router切换页面时滚动条自动滚动到顶部的方法
2017/11/28 Javascript
vue.js删除列表中的一行
2018/06/30 Javascript
vue-cli和v-charts实现可视化图表过程解析
2019/10/08 Javascript
node读写Excel操作实例分析
2019/11/06 Javascript
基于JavaScript实现十五拼图代码实例
2020/04/26 Javascript
Djang中静态文件配置方法
2015/07/30 Python
python实现三次样条插值
2018/12/17 Python
增大python字体的方法步骤
2020/07/05 Python
CSS3悬停效果案例应用
2012/11/21 HTML / CSS
利用Bootstrap实现漂亮简洁的CSS3价格表实例源码
2017/03/02 HTML / CSS
英国汽车和货车租赁网站:Hertz英国
2016/09/02 全球购物
环保专业大学生职业规划设计
2014/01/10 职场文书
房地产开盘策划方案
2014/02/10 职场文书
竞选演讲稿范文大全
2014/05/12 职场文书
希特勒的演讲稿
2014/05/23 职场文书
2014年人民调解工作总结
2014/12/08 职场文书
2015年学校教育教学工作总结
2015/04/22 职场文书
SQL模糊查询报:ORA-00909:参数个数无效问题的解决
2021/06/21 Oracle