1987WEB视界-分享互联网热点话题和事件

您现在的位置是:首页 > WEB开发 > 正文

WEB开发

Extending Python with C or C++¶

1987web2024-03-25WEB开发44
1.ExtendingPythonwithCorC++—Pythonv2.7.3documentationExtendingPythonwithC

1. Extending Python with C or C++ — Python v2.7.3 documentationExtending Python with C or C++¶I

Extending Python with C or C++¶

It is quite easy to add new built-in modules to Python, if you know how to program in C. Suchextension modulescan do two things that can’t be done directly in Python: they can implement new built-in object types, and they can call C library functions and system calls.

To support extensions, the Python API (Application Programmers Interface) defines a set of functions, macros and variables that provide access to most aspects of the Python run-time system. The Python API is incorporated in a C source file by including the header"Python.h".

The compilation of an extension module depends on its intended use as well as on your system setup; details are given in later chapters.

Do note that if your use case is calling C library functions or system calls, you should consider using thectypesmodule rather than writing custom C code. Not only doesctypeslet you write Python code to interface with C code, but it is more portable between implementations of Python than writing and compiling an extension module which typically ties you to CPython.

1.2. Intermezzo: Errors and Exceptions¶

An important convention throughout the Python interpreter is the following: when a function fails, it should set an exception condition and return an error value (usually aNULLpointer). Exceptions are stored in a static global variable inside the interpreter; if this variable isNULLno exception has occurred. A second global variable stores the “associated value” of the exception (the second argument toraise). A third variable contains the stack traceback in case the error originated in Python code. These three variables are the C equivalents of the Python variablessys.exc_type,sys.exc_valueandsys.exc_traceback(see the section on modulesysin the Python Library Reference). It is important to know about them to understand how errors are passed around.

The Python API defines a number of functions to set various types of exceptions.

The most common one isPyErr_SetString(). Its arguments are an exception object and a C string. The exception object is usually a predefined object likePyExc_ZeroDivisionError. The C string indicates the cause of the error and is converted to a Python string object and stored as the “associated value” of the exception.

Another useful function isPyErr_SetFromErrno(), which only takes an exception argument and constructs the associated value by inspection of the global variableerrno. The most general function isPyErr_SetObject(), which takes two object arguments, the exception and its associated value. You don’t need toPy_INCREF()the objects passed to any of these functions.

You can test non-destructively whether an exception has been set withPyErr_Occurred(). This returns the current exception object, orNULLif no exception has occurred. You normally don’t need to callPyErr_Occurred()to see whether an error occurred in a function call, since you should be able to tell from the return value.

When a functionfthat calls another functiongdetects that the latter fails,fshould itself return an error value (usuallyNULLor-1). It shouldnotcall one of thePyErr_*()functions — one has already been called byg.f‘s caller is then supposed to also return an error indication toitscaller, againwithoutcallingPyErr_*(), and so on — the most detailed cause of the error was already reported by the function that first detected it. Once the error reaches the Python interpreter’s main loop, this aborts the currently executing Python code and tries to find an exception handler specified by the Python programmer.

(There are situations where a module can actually give a more detailed error message by calling anotherPyErr_*()function, and in such cases it is fine to do so. As a general rule, however, this is not necessary, and can cause information about the cause of the error to be lost: most operations can fail for a variety of reasons.)

To ignore an exception set by a function call that failed, the exception condition must be cleared explicitly by callingPyErr_Clear(). The only time C code should callPyErr_Clear()is if it doesn’t want to pass the error on to the interpreter but wants to handle it completely by itself (possibly by trying something else, or pretending nothing went wrong).

Every failingmalloc()call must be turned into an exception — the direct caller ofmalloc()(orrealloc()) must callPyErr_NoMemory()and return a failure indicator itself. All the object-creating functions (for example,PyInt_FromLong()) already do this, so this note is only relevant to those who callmalloc()directly.

Also note that, with the important exception ofPyArg_ParseTuple()and friends, functions that return an integer status usually return a positive value or zero for success and-1for failure, like Unix system calls.

Finally, be careful to clean up garbage (by makingPy_XDECREF()orPy_DECREF()calls for objects you have already created) when you return an error indicator!

The choice of which exception to raise is entirely yours. There are predeclared C objects corresponding to all built-in Python exceptions, such asPyExc_ZeroDivisionError, which you can use directly. Of course, you should choose exceptions wisely — don’t usePyExc_TypeErrorto mean that a file couldn’t be opened (that should probably bePyExc_IOError). If something’s wrong with the argument list, thePyArg_ParseTuple()function usually raisesPyExc_TypeError. If you have an argument whose value must be in a particular range or must satisfy other conditions,PyExc_ValueErroris appropriate.

You can also define a new exception that is unique to your module. For this, you usually declare a static object variable at the beginning of your file:

staticPyObject*SpamError;

and initialize it in your module’s initialization function (initspam()) with an exception object (leaving out the error checking for now):

PyMODINIT_FUNCinitspam(void){PyObject*m;m=Py_InitModule("spam",SpamMethods);if(m==NULL)return;SpamError=PyErr_NewException("spam.error",NULL,NULL);Py_INCREF(SpamError);PyModule_AddObject(m,"error",SpamError);}

Note that the Python name for the exception object isspam.error. ThePyErr_NewException()function may create a class with the base class beingException(unless another class is passed in instead ofNULL), described inBuilt-in Exceptions.

Note also that theSpamErrorvariable retains a reference to the newly created exception class; this is intentional! Since the exception could be removed from the module by external code, an owned reference to the class is needed to ensure that it will not be discarded, causingSpamErrorto become a dangling pointer. Should it become a dangling pointer, C code which raises the exception could cause a core dump or other unintended side effects.

We discuss the use ofPyMODINIT_FUNCas a function return type later in this sample.

Thespam.errorexception can be raised in your extension module using a call toPyErr_SetString()as shown below:

staticPyObject*spam_system(PyObject*self,PyObject*args){constchar*command;intsts;if(!PyArg_ParseTuple(args,"s",&command))returnNULL;sts=system(command);if(sts<0){PyErr_SetString(SpamError,"System command failed");returnNULL;}returnPyLong_FromLong(sts);}

1.3. Back to the Example¶

Going back to our example function, you should now be able to understand this statement:

if(!PyArg_ParseTuple(args,"s",&command))returnNULL;

It returnsNULL(the error indicator for functions returning object pointers) if an error is detected in the argument list, relying on the exception set byPyArg_ParseTuple(). Otherwise the string value of the argument has been copied to the local variablecommand. This is a pointer assignment and you are not supposed to modify the string to which it points (so in Standard C, the variablecommandshould properly be declared asconstchar*command).

The next statement is a call to the Unix functionsystem(), passing it the string we just got fromPyArg_ParseTuple():

sts=system(command);

Ourspam.system()function must return the value ofstsas a Python object. This is done using the functionPy_BuildValue(), which is something like the inverse ofPyArg_ParseTuple(): it takes a format string and an arbitrary number of C values, and returns a new Python object. More info onPy_BuildValue()is given later.

returnPy_BuildValue("i",sts);

In this case, it will return an integer object. (Yes, even integers are objects on the heap in Python!)

If you have a C function that returns no useful argument (a function returningvoid), the corresponding Python function must returnNone. You need this idiom to do so (which is implemented by thePy_RETURN_NONEmacro):

Py_INCREF(Py_None);returnPy_None;

Py_Noneis the C name for the special Python objectNone. It is a genuine Python object rather than aNULLpointer, which means “error” in most contexts, as we have seen.

1.4. The Module’s Method Table and Initialization Function¶

I promised to show howspam_system()is called from Python programs. First, we need to list its name and address in a “method table”:

staticPyMethodDefSpamMethods[]={...{"system",spam_system,METH_VARARGS,"Execute a shell command."},...{NULL,NULL,0,NULL}/* Sentinel */};

Note the third entry (METH_VARARGS). This is a flag telling the interpreter the calling convention to be used for the C function. It should normally always beMETH_VARARGSorMETH_VARARGS|METH_KEYWORDS; a value of0means that an obsolete variant ofPyArg_ParseTuple()is used.

When using onlyMETH_VARARGS, the function should expect the Python-level parameters to be passed in as a tuple acceptable for parsing viaPyArg_ParseTuple(); more information on this function is provided below.

TheMETH_KEYWORDSbit may be set in the third field if keyword arguments should be passed to the function. In this case, the C function should accept a thirdPyObject*parameter which will be a dictionary of keywords. UsePyArg_ParseTupleAndKeywords()to parse the arguments to such a function.

The method table must be passed to the interpreter in the module’s initialization function. The initialization function must be namedinitname(), wherenameis the name of the module, and should be the only non-staticitem defined in the module file:

PyMODINIT_FUNCinitspam(void){(void)Py_InitModule("spam",SpamMethods);}

Note that PyMODINIT_FUNC declares the function asvoidreturn type, declares any special linkage declarations required by the platform, and for C++ declares the function asextern"C".

When the Python program imports modulespamfor the first time,initspam()is called. (See below for comments about embedding Python.) It callsPy_InitModule(), which creates a “module object” (which is inserted in the dictionarysys.modulesunder the key"spam"), and inserts built-in function objects into the newly created module based upon the table (an array ofPyMethodDefstructures) that was passed as its second argument.Py_InitModule()returns a pointer to the module object that it creates (which is unused here). It may abort with a fatal error for certain errors, or returnNULLif the module could not be initialized satisfactorily.

When embedding Python, theinitspam()function is not called automatically unless there’s an entry in the_PyImport_Inittabtable. The easiest way to handle this is to statically initialize your statically-linked modules by directly callinginitspam()after the call toPy_Initialize():

int
声明:本站所有文章,如无特殊说明或标注,均为爬虫抓取以及网友投稿,版权归原作者所有。