有幾個原因使您可能想用 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 上)。先說一下 ?D Windows 上的 Python 事實上是用 Microsoft 編譯器編譯的。Python.org 的人也推薦用 MSVC++ 編譯擴展。顯然,您應該能夠成功說服 GNU 的編譯者們,但我本人還沒試過。
無論如何,還是讓我們來定義一個叫做‘foo'的模塊吧?!甪oo'模塊會有一個叫做‘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/* 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 ?D 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 擴展 ?D foo.bar ?D 的簡單示例。
首先,來看擴展本身:我們把它放到一個例如叫 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 字段將被文本“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 訪問 ?D 如果一個方法包含有一個 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)。在本文發表之前,我希望能看到它的雛形。它已被列在下面的參考資料中,去看看吧;到您閱讀本文時,應該已經能夠從中找到一個擴展示例。祝我好運。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
