天天看點

(Python程式設計)基本的内嵌技術

Programming Python, 3rd Edition 翻譯

最新版本見wiki: http://wiki.woodpecker.org.cn/moin/PP3eD

歡迎參與翻譯與修訂。

23.3. Basic Embedding Techniques

23.3. 基本的内嵌技術

As you can probably tell from the preceding overview, there is much flexibility in the embedding domain. To illustrate common embedding techniques in action, this section presents a handful of short C programs that run Python code in one form or another. Most of these examples make use of the simple Python module file shown in Example 23-1.

在前面的概覽一節中你已經知道,内嵌領域的靈活性很大。 本節列舉了幾個簡短的C程式, 它們以一種形式或另一種形式運作Python代碼, 來示範實際的通用的内嵌技術。 多數例子使用了這個簡單的Python子產品,見Example 23-1。(頭部注釋為:“C代碼以内嵌模式運作本子產品中的Python代碼。 這樣一個檔案可以更改而不必更改C代碼層。 這隻是标準的Python代碼(C代碼做轉換工作)。 你也可以運作标準子產品如string中的代碼。”)

Example 23-1. PP3E/Integrate/Embed/Basics/usermod.py

#########################################################

# C runs Python code in this module in embedded mode.

# Such a file can be changed without changing the C layer.

# There is just standard Python code (C does conversions).

# You can also run code in standard modules like string.

#########################################################

message = 'The meaning of life...'

def transform(input):

    input = input.replace('life', 'Python')

    return input.upper( )

If you know any Python at all, you probably know that this file defines a string and a function; the function returns whatever it is passed with string substitution and uppercase conversions applied. It's easy to use from Python:

如果你知道一點Python, 你可能知道, 這個檔案定義了一個字元串與一個函數;函數将輸入串替換并轉換成大寫後傳回。 這在Python中很容易使用:

.../PP3E/Integrate/Embed/Basics$ python

>>> import usermod                                      # import a module

>>> usermod.message                                     # fetch a string

'The meaning of life...'

>>> usermod.transform(usermod.message)                  # call a function

'THE MEANING OF PYTHON...'

With a little Python API wizardry, it's not much more difficult to use this module the same way in C.

利用一點Python API的魔力, 在C語言中以同樣的方式使用這個子產品也不是很難。

23.3.1. Running Simple Code Strings

23.3.1. 運作簡單代碼串

Perhaps the simplest way to run Python code from C is by calling the PyRun_SimpleString API function. With it, C programs can execute Python programs represented as C character string arrays. This call is also very limited: all code runs in the same namespace (the module _ _main_ _), the code strings must be Python statements (not expressions), and there is no direct way to communicate inputs or outputs with the Python code run.

從C語言運作Python代碼,最簡單的方法可能是, 調用PyRun_SimpleString API 函數。 使用它,C程式可以執行用C字元串數組表示的Python程式。 這個調用的限制也很大: 所有化碼運作于同一命名空間(子產品_ _main_ _), 代碼串必須是Python語句(不是表達式), 也沒有直接的辦法可以與運作的Python代碼通信, 進行輸入與輸出。

Still, it's a simple place to start. Moreover, when augmented with an imported C extension module that the embedded Python code can use to communicate with the enclosing C layer, this technique can satisfy many embedding goals. To demonstrate the basics, the C program in Example 23-2 runs Python code to accomplish the same results as the interactive session listed in the prior section.

但它仍是一個好的開始。 另外,如果在參數中導入C擴充子產品, 通過該擴充子產品, 内嵌的Python代碼就可以與外面的C語言層通信了, 這一技術可以滿足許多内嵌目标。 C程式Example 23-2 示範了這一基本方法, 它運作Python代碼, 産生與上面互動式運作相同的結果。

Example 23-2. PP3E/Integrate/Embed/Basics/embed-simple.c

#include <Python.h>    

main( ) {

    printf("embed-simple/n");

    Py_Initialize( );

    PyRun_SimpleString("import usermod");                

    PyRun_SimpleString("print usermod.message");         

    PyRun_SimpleString("x = usermod.message");           

    PyRun_SimpleString("print usermod.transform(x)");

}

The first thing you should notice here is that when Python is embedded, C programs always call Py_Initialize to initialize linked-in Python libraries before using any other API functions. The rest of this code is straightforwardC submits hardcoded strings to Python that are roughly what we typed interactively. Internally, PyRun_SimpleString invokes the Python compiler and interpreter to run the strings sent from C; as usual, the Python compiler is always available in systems that contain Python.

首先請注意, 當Python内嵌時, 在調用任何其它API函數之前, C程式總是先調用Py_Initialize, 來初始化連結的Python庫。 代碼餘下部分 與互動方式下打字輸入差不多, 直截了當地向Python送出寫死的字元串。 在内部, PyRun_SimpleString調用Python編譯器和解釋器, 來運作C語言傳入的字元串; 通常,在安裝了Python的系統上, Python編譯器總是可用的。

23.3.1.1. Compiling and running
23.3.1.1. 編譯與運作

To build a standalone executable from this C source file, you need to link its compiled form with the Python library file. In this chapter, "library" usually means the binary library file that is generated when Python is compiled, not the Python source code library.

為了從這個C語言源檔案建構獨立的執行程式, 編譯後需要連結Python庫檔案。 在本章中,“庫”通常指Python編譯後生成的二進制庫檔案,而不是Python源代碼庫。

Today, everything in Python that you need in C is compiled into a single Python library file when the interpreter is built (e.g., libpython2.4.dll on Cygwin). The program's main function comes from your C code, and depending on your platform and the extensions installed in your Python, you may also need to link any external libraries referenced by the Python library.

目前,當Python解釋器建構時,你在C語言中所需的所有Python的東西,會被編譯到一個Python庫檔案 (在Cygwin上即libpython2.4.dll)。 程式的main函數來自于你的C語言代碼, 與你的平台和Python安裝的擴充有關, 你可能還需要連結Python庫所引用的外部庫。

Assuming no extra extension libraries are needed, Example 23-3 is a minimal makefile for building the C program in Example 23-2 under Cygwin on Windows. Again, makefile details vary per platform, but see Python manuals for hints. This makefile uses the Python include-files path to find Python.h in the compile step and adds the Python library file to the final link step to make API calls available to the C program.

假設不需要額外的擴充庫,Example 23-3 是在Windows的Cygwin下建構Example 23-2 C程式的最小的make檔案。同樣,make檔案的細節随平台而變,請檢視Python手冊擷取幫助。 這個make檔案在編譯時使用Python包含路徑來尋找Python.h, 并在最後的連結步驟中添加Python庫檔案,使API調用在C程式中可用。

Example 23-3. PP3E/Integrate/Embed/Basics/makefile.1

# a Cygwin makefile that builds a C executable that embeds

# Python, assuming no external module libs must be linked in;

# uses Python header files, links in the Python lib file;

# both may be in other dirs (e.g., /usr) in your install;

PYLIB = /usr/bin

PYINC = /usr/include/python2.4

embed-simple: embed-simple.o

        gcc embed-simple.o -L$(PYLIB) -lpython2.4 -g -o embed-simple

embed-simple.o: embed-simple.c

        gcc embed-simple.c -c -g -I$(PYINC)

To build a program with this file, launch make on it as usual:

用這個檔案建構程式時,按通常方式運作make:

.../PP3E/Integrate/Embed/Basics$ make -f makefile.1

gcc embed-simple.c -c -g -I/usr/include/python2.4

gcc embed-simple.o -L/usr/bin -lpython2.4 -g -o embed-simple

Things may not be quite this simple in practice, though, at least not without some coaxing. The makefile in Example 23-4 is the one I actually used to build all of this section's C programs on Cygwin.

但是,實際可能不這麼容易,會需要一點技術。 Example 23-4 是我在Cygwin下實際使用的make檔案,用于建構本章所有的C程式。

Example 23-4. PP3E/Integrate/Embed/Basics/makefile.basics

# cygwin makefile to build all 5

# basic embedding examples at once

PYLIB = /usr/bin

PYINC = /usr/include/python2.4

BASICS = embed-simple.exe   /

         embed-string.exe   /

         embed-object.exe   /

         embed-dict.exe     /

         embed-bytecode.exe

all:    $(BASICS)

embed%.exe: embed%.o

        gcc embed$*.o -L$(PYLIB) -lpython2.4 -g -o [email protected]

embed%.o: embed%.c

        gcc embed$*.c -c -g -I$(PYINC)

clean:

        rm -f *.o *.pyc $(BASICS) core

On some platforms, you may need to also link in other libraries because the Python library file used may have been built with external dependencies enabled and required. In fact, you may have to link in arbitrarily many more externals for your Python library, and frankly, chasing down all the linker dependencies can be tedious. Required libraries may vary per platform and Python install, so there isn't a lot of advice I can offer to make this process simple (this is C, after all). The standard C development techniques will apply.

在有些平台上,你可能需要連結其它庫,因為用到的Python庫檔案依賴和要求外部庫。 實際上,你的Python庫可能要求你必須連結許多外部庫, 并且坦白說,找出所有的連結依賴關系是很煩的。 要求的庫随平台和安裝的Python不同而有很大不同, 是以我也沒辦法使這個過程簡單點(畢竟這是C語言)。 請運用标準的C語言開發技術來解決。

One thing to note is that on some platforms, if you're going to do much embedding work and you run into external dependency issues, you might want to build Python on your machine from its source with all unnecessary extensions disabled in the Modules/Setup file (or the top-level setup.py Distutils script in more recent releases). This produces a Python library with minimal external requirements, which links much more easily.

注意,在有些平台上, 如果你要做許多内嵌工作, 并且你碰到了外部依賴問題, 你可能需要在你的機器上, 從源碼建構Python, 并在Modules/Setup檔案(或在新版本中的頂層setup.py Distutils腳本)中, 禁用所有不必要的擴充。 這樣生成的Python庫,具有最小的外部依賴,連結時會友善許多。

For example, if your embedded code won't be building GUIs, Tkinter can simply be removed from the library; see the README file at the top of Python's source distribution for details. You can also find a list of external libraries referenced from your Python in the generated makefiles located in the Python source tree. In any event, the good news is that you need to resolve linker dependencies only once.

例如,如果你的内嵌代碼不會建構GUI,就可以直接從庫裡面移除Tkinter; 詳情請參閱Python源代碼樹中頂層的README檔案。 在Python源代碼樹中,在生成的make檔案中, 你也可以找到你的Python引用外部庫的清單。 無論如何,好消息是連結依賴問題你隻需解決一次。

Once you've gotten the makefile to work, run it to build the C program with Python libraries linked in:

一旦make檔案可以工作,運作它來建構C程式,并連結Python庫:

.../PP3E/Integrate/Embed/Basics$ make -f makefile.basics clean 

rm -f *.o *.pyc embed-simple.exe embed-string.exe embed-object.exe embed-dict.ex

e embed-bytecode.exe core

.../PP3E/Integrate/Embed/Basics$ make -f makefile.basics 

gcc embed-simple.c -c -g -I/usr/include/python2.4

gcc embed-simple.o -L/usr/bin -lpython2.4 -g -o embed-simple.exe

 ...lines deleted...

gcc embed-bytecode.c -c -g -I/usr/include/python2.4

gcc embed-bytecode.o -L/usr/bin -lpython2.4 -g -o embed-bytecode.exe

rm embed-dict.o embed-object.o embed-simple.o embed-bytecode.o embed-string.o

After building, run the resulting C program as usual, regardless of how this works in your platform:[*]

建構之後,運作生成的C程式 (不同平台方法不同 [*]):

[*] Under Python 2.4 and Cygwin on Windows, I had to first set my PYTHONPATH to include the current directory in order to run the embedding examples under Python 2.4 and Cygwin, with the shell command export PYTHONPATH=.. I also had to use the shell command ./embed-simple to execute the program due to my system path setting. Your mileage may vary; if you have trouble, try running the embedded Python commands import sys and print sys.path from C to see what Python's path looks like, and take a look at the Python/C API manual for more on path configuration for embedded applications.

[*] 在Windows的Cygwin和Python2.4下,為了運作内嵌的例程, 我必須先使用shell指令export PYTHONPATH=., 來設定PYTHONPATH包含目前目錄。 同時,由于我的系統路徑設定關系,我必須使用shell指令./embed-simple來執行程式。 你的方法可以不同;如果你有麻煩,試試在C語言中運作内嵌Python指令 import sys和print sys.path, 看看Python路徑是怎樣的, 更多内嵌應用的路徑配置資訊,請查閱 Python/C API手冊。

.../PP3E/Integrate/Embed/Basics$ embed-simple

embed-simple

The meaning of life...

THE MEANING OF PYTHON...

Most of this output is produced by Python print statements sent from C to the linked-in Python library. It's as if C has become an interactive Python programmer.

大多數的輸出是由Python print語句産生的, 該語句由C語言發送到鍊入的Python庫。 就像C成為了一個互動式Python程式員。

Naturally, strings of Python code run by C probably would not be hardcoded in a C program file like this. They might instead be loaded from a text file or GUI, extracted from HTML or XML files, fetched from a persistent database or socket, and so on. With such external sources, the Python code strings that are run from C could be changed arbitrarily without having to recompile the C program that runs them. They may even be changed onsite, and by end users of a system. To make the most of code strings, though, we need to move on to more flexible API tools.

當然,C語言運作的Python代碼串, 可能不是這樣在C語言檔案中寫死的。 它們可以是從一個文本檔案或GUI讀取、 從HTML或XML檔案中提取、 從資料庫或socket擷取、等等。 使用這樣的外部源, 就可以任意更改C語言運作的Python代碼串, 而不必重新編譯運作它們的C程式。 它們甚至可以由系統的最終使用者現場更改。 但是,為充分利用代碼串,我們需要繼續前進,學習更靈活的API工具。

23.3.2. Running Code Strings with Results and Namespaces

23.3.2. 帶結果和名字空間運作代碼串

Example 23-5 uses the following API calls to run code strings that return expression results back to C:

Example 23-5 使用下面的API調用來運作代碼串,并傳回表達式的結果到C:

Py_Initialize

Initializes linked-in Python libraries as before

同上,初始化鍊入的Python庫

PyImport_ImportModule

Imports a Python module and returns a pointer to it

導入一個Python子產品并傳回它的指針

PyModule_GetDict

Fetches a module's attribute dictionary object

擷取一個子產品的屬性字典對象

PyRun_String

Runs a string of code in explicit namespaces

在指定的名字空間内運作代碼串

PyObject_SetAttrString

Assigns an object attribute by namestring

通過namestring為一個對象的屬性指派

PyArg_Parse

Converts a Python return value object to C form

将Python傳回值對象轉換為C語言形式

The import calls are used to fetch the namespace of the usermod module listed in Example 23-1 earlier so that code strings can be run there directly (and will have access to names defined in that module without qualifications). Py_Import_ImportModule is like a Python import statement, but the imported module object is returned to C; it is not assigned to a Python variable name. As a result, it's probably more similar to the Python _ _import_ _ built-in function.

導入調用用來擷取前面 Example 23-1 中所列子產品usermod的名字空間, 然後,代碼串就可以直接在那裡運作 (并且可以通路那個子產品内定義的名字,無需修飾)。 PyImport_ImportModule就像Python的import語句, 但是導入的子產品對象會傳回給C語言; 它并不指派給一個Python變量名。 結果是,它可能更像Python的_ _import_ _内置函數。

The PyRun_String call is the one that actually runs code here, though. It takes a code string, a parser mode flag, and dictionary object pointers to serve as the global and local namespaces for running the code string. The mode flag can be Py_eval_input to run an expression, or Py_file_input to run a statement; when running an expression, the result of evaluating the expression is returned from this call (it comes back as a PyObject* object pointer). The two namespace dictionary pointer arguments allow you to distinguish global and local scopes, but they are typically passed the same dictionary such that code runs in a single namespace.[*]

而PyRun_String調用是實際運作代碼的地方。 它接受一個代碼串,一個分析模式标志, 和兩個字典對象指針, 這兩個字典分别作為代碼串運作的全局和局部的名字空間。 模式标志可以是Py_eval_input, 來運作一個表達式, 或者Py_file_input, 來運作一個語句; 當運作表達式時,這個調用會傳回表達式的計算結果(傳回一個對象指針PyObject*)。 兩個名字空間字典指針參數允許你區分全局和局部作用域, 但是它們一般是同一個字典,如此代碼會運作于單一的名字空間。 [*]

(譯注:難道就不能不分表達式和語句嗎?)

[*] A related function lets you run files of code but is not demonstrated in this chapter: PyObject* PyRun_File(FILE *fp, char *filename, mode, globals, locals). Because you can always load a file's text and run it as a single code string with PyRun_String, the PyRun_File call is not always necessary. In such multiline code strings, the /n character terminates lines and indentation groups blocks as usual.

[*] 有一個相關的函數可以讓你運作代碼檔案, 但是本章沒有示例: PyObject* PyRun_File(FILE *fp, char *filename, mode, globals, locals)。 因為你總是可以讀取檔案内容,并作為一個代碼串, 用PyRun_String來運作它, 是以PyRun_File并不總是必要的。 在多行代碼串的情況下, 可用/n字元分隔行, 并照常縮進代碼塊。

Example 23-5. PP3E/Integrate/Embed/Basics/embed-string.c

#include <Python.h>

main( ) {

    char *cstr;

    PyObject *pstr, *pmod, *pdict;

    printf("embed-string/n");

    Py_Initialize( );

    pmod  = PyImport_ImportModule("usermod");

    pdict = PyModule_GetDict(pmod);

    pstr  = PyRun_String("message", Py_eval_input, pdict, pdict);

    PyArg_Parse(pstr, "s", &cstr);

    printf("%s/n", cstr);

    PyObject_SetAttrString(pmod, "X", pstr);

    (void) PyRun_String("print transform(X)", Py_file_input, pdict, pdict);

    Py_DECREF(pmod);

    Py_DECREF(pstr);

}

When compiled and run, this file produces the same result as its predecessor:

編譯後運作的結果與上個程式是一樣的:

.../PP3E/Integrate/Embed/Basics$ embed-string

embed-string

The meaning of life...

THE MEANING OF PYTHON...

But very different work goes into producing this output. This time, C fetches, converts, and prints the value of the Python module's message attribute directly by running a string expression and assigning a global variable (X) within the module's namespace to serve as input for a Python print statement string.

但是為了産生這個輸出,所做的工作是很不一樣的。 這一次,通過運作一個字元串表達式, C語言直接擷取,轉換并列印Python子產品的message屬性, 并在子產品的名字空間中, 對一個全局變量(X)指派, 該變量再作為Python print語句的輸入。

Because the string execution call in this version lets you specify namespaces, you can better partition the embedded code your system runseach grouping can have a distinct namespace to avoid overwriting other groups' variables. And because this call returns a result, you can better communicate with the embedded code; expression results are outputs, and assignments to globals in the namespace in which code runs can serve as inputs.

因為該版本的字元串運作函數可以讓你指定名字空間, 你可以更好地隔離系統中運作的内嵌代碼, 每個代碼組都可以有一個獨特的名字空間, 避免改寫其它組的變量。 并且,這個函數傳回一個結果, 讓你可以更好地與内嵌代碼通信; 表達式結果是輸出, 在代碼運作的名字空間内, 它又指派給全局變量, 而全局變量可以作為輸入。

Before we move on, I need to explain two coding issues here. First, this program also decrements the reference count on objects passed to it from Python, using the Py_DECREF call introduced in Chapter 22. These calls are not strictly needed here (the objects' space is reclaimed when the programs exits anyhow), but they demonstrate how embedding interfaces must manage reference counts when Python passes their ownership to C. If this was a function called from a larger system, for instance, you would generally want to decrement the count to allow Python to reclaim the objects.

在我們繼續前進前,我要解釋一下,這裡有兩個代碼問題。 首先,這個程式使用 第22章 介紹的Py_DECREF函數, 減小了從Python傳來的對象的引用計數。 這些調用在此并非嚴格需要 (無論如何,對象占用空間在程式退出時會收回), 但是它們示範了, 當Python将對象所有權傳遞到C語言時, 内嵌接口必須如何管理引用計數。 如果這是個大型系統中的函數調用,通常你需要減小計數,以允許Python收回對象。

Second, in a realistic program, you should generally test the return values of all the API calls in this program immediately to detect errors (e.g., import failure). Error tests are omitted in this section's example to keep the code simple, but they will appear in later code listings and should be included in your programs to make them more robust.

第二,在一個實際的程式中, 你一般應該立即檢查程式中 所有API調用的傳回值, 以檢測錯誤(例如導入失敗)。 本節中的例子省略了錯誤檢查, 以保持代碼清晰, 但是它們将會在以後的代碼清單中出現, 并且應該包含在你的程式中,使之更健壯。

23.3.3. Calling Python Objects

23.3.3. 調用Python對象

The last two sections dealt with running strings of code, but it's easy for C programs to deal in terms of Python objects too. Example 23-6 accomplishes the same task as Examples 23-2 and 23-5, but it uses other API tools to interact with objects in the Python module directly:

上面兩節讨論的是代碼串的運作, 但是對C程式來說, 按Python對象運作也很容易。 Example 23-6 實作Examples 23-2 和23-5相同的任務, 但是使用其它的API工具, 來與Python子產品中的對象直接互動。

PyImport_ImportModule

Imports the module from C as before

同上,從C語言導入子產品

PyObject_GetAttrString

Fetches an object's attribute value by name

按名字擷取對象的屬性值

PyEval_CallObject

Calls a Python function (or class, or method)

調用Python的函數(或類,或方法)

PyArg_Parse

Converts Python objects to C values

将Python對象轉換為C語言值

Py_BuildValue

Converts C values to Python objects

将C語言值轉換為Python對象

We met both of the data conversion functions in Chapter 22. The PyEval_CallObject call in this version of the example is the key call here: it runs the imported function with a tuple of arguments, much like the Python apply built-in function and newer func(*args) call syntax. The Python function's return value comes back to C as a PyObject*, a generic Python object pointer.

我們在第22章 見過這兩個資料轉換函數。 本例中的PyEval_CallObject是關鍵性的調用: 它以一個元組為參數執行導入的函數, 很像Python内置函數apply, 或新的調用方法func(*args)。 Python函數傳回到C語言的傳回值是一個通用的Python對象指針,PyObject*。

Example 23-6. PP3E/Integrate/Embed/Basics/embed-object.c

#include <Python.h>

main( ) {

    char *cstr;

    PyObject *pstr, *pmod, *pfunc, *pargs;

    printf("embed-object/n");

    Py_Initialize( );

    pmod = PyImport_ImportModule("usermod");

    pstr = PyObject_GetAttrString(pmod, "message");

    PyArg_Parse(pstr, "s", &cstr);

    printf("%s/n", cstr);

    Py_DECREF(pstr);

    pfunc = PyObject_GetAttrString(pmod, "transform");

    pargs = Py_BuildValue("(s)", cstr);

    pstr  = PyEval_CallObject(pfunc, pargs);

    PyArg_Parse(pstr, "s", &cstr);

    printf("%s/n", cstr);

    Py_DECREF(pmod);

    Py_DECREF(pstr);

    Py_DECREF(pfunc);        

    Py_DECREF(pargs);        

}

When compiled and run, the result is the same again:

編譯後運作的結果仍是一樣的:

.../PP3E/Integrate/Embed/Basics$ embed-object

embed-object

The meaning of life...

THE MEANING OF PYTHON...

But this output is generated by C this timefirst, by fetching the Python module's message attribute value, and then by fetching and calling the module's transform function object directly and printing its return value that is sent back to C. Input to the TRansform function is a function argument here, not a preset global variable. Notice that message is fetched as a module attribute this time, instead of by running its name as a code string; there is often more than one way to accomplish the same goals with different API calls.

但是這次輸出是由C語言生成的:首先,擷取Python子產品的message屬性值, 然後擷取子產品的transform函數對象并直接調用, 接着列印發回到C語言的傳回值。 這裡輸入transform函數的是一個函數參數,而不是預設的全局變量。 注意這次message是按子產品屬性擷取的, 而不是用它的名字作為一個代碼串運作; 通常有不止一個方法, 可以調用不同的API, 來達到相同的目的。

Running functions in modules like this is a simple way to structure embedding; code in the module file can be changed arbitrarily without having to recompile the C program that runs it. It also provides a direct communication model: inputs and outputs to Python code can take the form of function arguments and return values.

像這樣運作子產品中的函數, 是構造内嵌的一個簡單的方法; 子產品檔案中的代碼可以任意更改, 而不必重編譯運作它的C程式。 它也提供了一個直接通信模式: Python代碼的輸入和輸出, 可以采用函數參數和傳回值的形式。

23.3.4. Running Strings in Dictionaries

23.3.4. 在字典中運作字元串

When we used PyRun_String earlier to run expressions with results, code was executed in the namespace of an existing Python module. However, sometimes it's more convenient to create a brand-new namespace for running code strings that is independent of any existing module files. The C file in Example 23-7 shows how; the new namespace is created as a new Python dictionary object, and a handful of new API calls are employed in the process:

前面我們使用PyRun_String, 運作表達式并傳回結果時, 代碼是在現存的Python子產品的名字空間中執行的。 然而,當運作一個與任何現存子產品檔案都無關的代碼串時, 建立一個全新的名字空間将更友善。 Example 23-7的C檔案顯示了該如何做; 建立的新的名字空間是一個新的Python字典對象, 還有,該過程中使用了幾個新的API調用:

PyDict_New

Makes a new empty dictionary object

構造一個新的空字典對象

PyDict_SetItemString

Assigns to a dictionary's key

給一個字典的鍵值指派

PyDict_GetItemString

Fetches (indexes) a dictionary value by key

按鍵值擷取(查詢)一個字典值

PyRun_String

Runs a code string in namespaces, as before

同上,在名字空間中運作一個代碼串

PyEval_GetBuiltins

Gets the built-in scope's module

得到内置作用域的子產品

The main trick here is the new dictionary. Inputs and outputs for the embedded code strings are mapped to this dictionary by passing it as the code's namespace dictionaries in the PyRun_String call. The net effect is that the C program in Example 23-7 works exactly like this Python code:

這裡的關鍵是建立的字典。 該字典作為内嵌代碼串執行的名字空間, 傳遞給PyRun_String調用, 代碼的輸入和輸出就都映射到了這個字典。 實際效果是,Example 23-7 中的C程式等同與這個Python代碼:

 >>> d = {}

 >>> d['Y'] = 2

 >>> exec 'X = 99' in d, d

 >>> exec 'X = X + Y' in d, d

 >>> print d['X']

 101

But here, each Python operation is replaced by a C API call.

但是這裡的每一個Python操作都被替換成了C API調用。

Example 23-7. PP3E/Integrate/Embed/Basics/embed-dict.c

#include <Python.h>

main( ) {

    int cval;

    PyObject *pdict, *pval;

    printf("embed-dict/n");

    Py_Initialize( );

    pdict = PyDict_New( );

    PyDict_SetItemString(pdict, "_ _builtins_ _", PyEval_GetBuiltins( ));

    PyDict_SetItemString(pdict, "Y", PyInt_FromLong(2));   

    PyRun_String("X = 99",  Py_file_input, pdict, pdict);  

    PyRun_String("X = X+Y", Py_file_input, pdict, pdict);  

    pval = PyDict_GetItemString(pdict, "X");               

    PyArg_Parse(pval, "i", &cval);                         

    printf("%d/n", cval);                                  

    Py_DECREF(pdict);

}

When compiled and run, this C program creates this sort of output:

編譯運作,該C程式輸出為:

.../PP3E/Integrate/Embed/Basics$ embed-dict

embed-dict

101

The output is different this time: it reflects the value of the Python variable X assigned by the embedded Python code strings and fetched by C. In general, C can fetch module attributes either by calling PyObject_GetAttrString with the module or by using PyDict_GetItemString to index the module's attribute dictionary (expression strings work too, but they are less direct). Here, there is no module at all, so dictionary indexing is used to access the code's namespace in C.

這次輸出不一樣: 它反映了Python變量X的值, 該變量由内嵌代碼串指派, 并由C語言擷取。 一般來說,C語言擷取子產品屬性時, 要麼通過子產品調用PyObject_GetAttrString, 要麼使用PyDict_GetItemString來查詢子產品的屬性字典 (表達式字元串也行,但是不夠直接)。 這裡根本沒有子產品,是以C語言使用字典查詢進入代碼的名字空間。

Besides allowing you to partition code string namespaces independent of any Python module files on the underlying system, this scheme provides a natural communication mechanism. Values that are stored in the new dictionary before code is run serve as inputs, and names assigned by the embedded code can later be fetched out of the dictionary to serve as code outputs. For instance, the variable Y in the second string run refers to a name set to 2 by C; X is assigned by the Python code and fetched later by C code as the printed result.

這個方案不僅允許你隔離代碼串的名字空間, 使之不依賴于任何下層系統上的Python子產品檔案, 而且,它還提供了一個自然的通信機制。 代碼運作前儲存于建立字典中的值可以作為輸入, 而内嵌代碼指派的名字, 稍後可以從字典中取出,成為輸出。 例如,第二個運作的字元串中的變量Y,它所指的名字在C語言中被設為2; X由Python代碼指派,後來被C代碼擷取,并作為結果列印。

There is one subtlety: dictionaries that serve as namespaces for running code are generally required to have a _ _builtins_ _ link to the built-in scope searched last for name lookups, set with code of this form:

有一個細微之處:運作代碼時作為名字空間的字典, 一般要求具有_ _builtins_ _鍵值, 它将指向内置作用域,用于名字查找, 用這樣的代碼進行設定:

PyDict_SetItemString(pdict, "_ _builtins_ _", PyEval_GetBuiltins( ));

This is esoteric, and it is normally handled by Python internally for modules. For raw dictionaries, though, we are responsible for setting the link manually.

這有點深奧,該字典通常是由Python内部為子產品自動産生的。 但是,對于原始字典,我們應負責手工設定。

23.3.5. Precompiling Strings to Bytecode

23.3.5. 預編譯字元串為位元組碼

When you call Python function objects from C, you are actually running the already compiled bytecode associated with the object (e.g., a function body). When running strings, Python must compile the string before running it. Because compilation is a slow process, this can be a substantial overhead if you run a code string more than once. Instead, precompile the string to a bytecode object to be run later, using the API calls illustrated in Example 23-8:[*]

當你從C語言調用Python函數對象時,你實際上是運作該對象已編譯的位元組碼(例如一個函數體)。 當運作字元串時,Python必須在運作它之前編譯這個字元串。 因為編譯是一個費時的過程,如果你要多次運作一個代碼串,這将是可觀的開銷。 不過,可以使用 Example 23-8 所示的API調用,将字元串預編譯為位元組碼對象,然後再運作: [*]

[*] In case you've forgotten: bytecode is simply an intermediate representation for already compiled program code in the current standard Python implementation. It's a low-level binary format that can be quickly interpreted by the Python runtime system. Bytecode is usually generated automatically when you import a module, but there may be no notion of an import when running raw strings from C.

[*] 可能你已經忘了:在目前的标準Python實作中, 程式代碼編譯後的 位元組碼 隻是一種中間代碼。 它是低級的二進制格式的,可以被Python運作時系統快速解釋。 位元組碼通常在你導入一個子產品時自動生成, 但是在C語言中運作原始字元串時,沒有導入這個概念。

Py_CompileString

Compiles a string of code and returns a bytecode object

編譯一個代碼串并傳回一個位元組碼對象

PyEval_EvalCode

Runs a compiled bytecode object

運作一個已編譯位元組碼對象

The first of these takes the mode flag that is normally passed to PyRun_String, as well as a second string argument that is used only in error messages. The second takes two namespace dictionaries. These two API calls are used in Example 23-8 to compile and execute three strings of Python code in turn.

第一個函數的第二個字元串參數僅用于出錯資訊中, 第三個參數是一個模式标志, 與通常傳遞給PyRun_String的模式标志一樣。 第二個函數接受兩個名字空間字典。 Example 23-8 用這兩個API調用依次編譯與執行三個Python代碼串。

Example 23-8. PP3E/Integrate/Embed/Basics/embed-bytecode.c

#include <Python.h>

#include <compile.h>

#include <eval.h>

main( ) {

    int i;

    char *cval;

    PyObject *pcode1, *pcode2, *pcode3, *presult, *pdict;

    char *codestr1, *codestr2, *codestr3;

    printf("embed-bytecode/n");

    Py_Initialize( );

    codestr1 = "import usermod/nprint usermod.message";     

    codestr2 = "usermod.transform(usermod.message)";        

    codestr3 = "print '%d:%d' % (X, X ** 2),";              

    pdict = PyDict_New( );

    if (pdict == NULL) return -1;

    PyDict_SetItemString(pdict, "_ _builtins_ _", PyEval_GetBuiltins( ));

    pcode1 = Py_CompileString(codestr1, "<embed>", Py_file_input);

    pcode2 = Py_CompileString(codestr2, "<embed>", Py_eval_input);

    pcode3 = Py_CompileString(codestr3, "<embed>", Py_file_input);

    if (pcode1 && pcode2 && pcode3) {

        (void)    PyEval_EvalCode((PyCodeObject *)pcode1, pdict, pdict);

        presult = PyEval_EvalCode((PyCodeObject *)pcode2, pdict, pdict);

        PyArg_Parse(presult, "s", &cval);

        printf("%s/n", cval);

        Py_DECREF(presult);

        for (i = 0; i <= 10; i++) {

            PyDict_SetItemString(pdict, "X", PyInt_FromLong(i));

            (void) PyEval_EvalCode((PyCodeObject *)pcode3, pdict, pdict);

        }

        printf("/n");

    }

    Py_XDECREF(pdict);

    Py_XDECREF(pcode1);

    Py_XDECREF(pcode2);

    Py_XDECREF(pcode3);

}

This program combines a variety of techniques that we've already seen. The namespace in which the compiled code strings run, for instance, is a newly created dictionary (not an existing module object), and inputs for code strings are passed as preset variables in the namespace. When built and executed, the first part of the output is similar to previous examples in this section, but the last line represents running the same precompiled code string 11 times:

這個程式組合了我們所見過的多種技術。 例如,編譯的代碼串所運作的名字空間是建立的字典(而不是已存在的子產品對象), 代碼串的輸入是通過名字空間中的預設變量傳遞的。 編譯執行時,輸出的第一部分與本節以前的例子類似, 但最後一行是相同的預編譯代碼串運作11次的結果:

.../PP3E/Integrate/Embed/Basics$ embed-bytecode

embed-bytecode

The meaning of life...

THE MEANING OF PYTHON...

0:0 1:1 2:4 3:9 4:16 5:25 6:36 7:49 8:64 9:81 10:100

If your system executes strings multiple times, it is a major speedup to precompile to bytecode in this fashion.

如果你的系統需要多次執行字元串,以這種方式預編譯成位元組碼可以大大提高速度。

(轉載請注明來源于金慶的專欄)