Extending & Embedding Python Using C - Exceptions
Written by Mike James   
Monday, 30 October 2023
Article Index
Extending & Embedding Python Using C - Exceptions
Exception Classes
Raising Exceptions

Raising Exceptions

As well as passing exceptions raised by other functions back to the Python system, you can also quite easily raise exceptions directly using;

void PyErr_SetString(PyObject *type, const char *message)

where type is one of the standard exceptions with names starting PyExc_, such as PyExc_ZeroDivisionError and message is a UTF-8 C string that specifies the error message. For example, we can raise our own division-by-zero exception using:

static PyObject *exception2(PyObject *self, 
PyObject *args)
{
PyErr_SetString(PyExc_ZeroDivisionError,
"A Dummy divide error has occurred");
return NULL;
}

If you compile his and try it out:

import example
try:
ans=example.exception2()
except ZeroDivisionError as err:
print(err)

you will see:

A Dummy divide error has occurred

You can raise any of the builtin exceptions and warnings in the same way. You can also create your own exception classes by inheriting from Exception. Creating classes and inheritance is covered in Chapters 11 and 12 but it is very straight forward. However there is a shortcut in the form of the

PyObject *PyErr_NewException(const char *name,
                      PyObject *base, PyObject *dict)

The second and third parameters are usually set to NULL to accept the default base class, Exception, and dict. A new class is created and this can be added to the module and used in the same way as a built in exception class.

For example:

static PyObject *myCustomException;
PyMODINIT_FUNC PyInit_example(void)
{
PyObject *m;
m = PyModule_Create(&addmodule);
if (m == NULL)
return NULL;
myCustomException = PyErr_NewException(
"example.myCustomException", NULL, NULL);
if (PyModule_AddObjectRef(m, "myCustomException",
myCustomException) < 0)
{
Py_CLEAR(myCustomException);
Py_DECREF(m);
return NULL;
}
return m;
}

This adds a new class to the module and uses the technique for adding attributes to a module described in detail in Chapter 11. All you need to understand at this stage is that we have created a new exception class called myCustomException and stored it in a global variable of the same name and added it as an attribute to the module. Notice that the adding the attribute to the module is also an example of passing an exception up to the Python system. The new exception class can be used by the C extension module and Python programs that import it. For example:

static PyObject *exception3(PyObject *self, 
PyObject *args)
{
PyErr_SetString(myCustomException,
"A Custom error has occurred");
return NULL;
}

This raises the new exception with a suitable error message. A Python program that makes use of this function and the new exception is:

try:
ans=example.exception3()
except example.myCustomException as err:
print(err)

The result is:

A Custom error has occurred

There is also a range of API functions designed to make it easier to raise common errors.

Handling Exceptions

In most cases you are probably going to pass the exception on to the Python system but you can handle it in the C function if you can. If you handle the exception then you have to clean up anything that the function that raised the exception created before clearing the error using:

void PyErr_Clear()

Don’t clear the error unless you have cleaned up after the function that raised it. Of course if you are calling a C function you can deal with any errors it returns in the usual way – you don’t need to worry about Python exceptions unless you want to raise one to signal the error to the Python system.

Handling an exception can be as easy as calling PyErr_Clear for example:

static PyObject *exception3(PyObject *self, 
PyObject *args)
{
PyObject *x = PyLong_FromLong(1);
PyObject *y = PyLong_FromLong(0);
PyObject *ans = PyNumber_TrueDivide(x, y);
if (ans == NULL)
{ PyErr_Clear();
PyObject *z = PyLong_FromLong(1);
ans = PyNumber_TrueDivide(x, z);
Py_DECREF(z);
}
Py_DECREF(x);
Py_DECREF(y);
return ans;
}

In this case all we have to do is test for the error, clear the exception and try again.

What if you only want to handle particular types of exception? There are two functions that can help with this:

  • int PyErr_ExceptionMatches(PyObject *exc)
  • int PyErr_GivenExceptionMatches(PyObject *given,
    PyObject *exc)

The first tests to see if the current exception matches exc, i.e. is an instance or a derived type. If exc is a tuple all classes are tested. The given parameter specifies a class to compare exc to. PyErr_ExceptionMatches should only be called when an exception is actually raised. You can discover if an exception is raised using:

  • PyObject *PyErr_Occurred()

It returns NULL if there is no exception or the exception object without incrementing its reference count. For example, to test specifically for division by zero and only handling the exception if it is:

static PyObject *exception5(PyObject *self, 
PyObject *args)
{
PyObject *x = PyLong_FromLong(1);
PyObject *y = PyLong_FromLong(0);
PyObject *ans = PyNumber_TrueDivide(x, y);
if (PyErr_Occurred() != NULL)
{
if(PyErr_ExceptionMatches(
PyExc_ZeroDivisionError)){
PyErr_Clear();
PyObject *z = PyLong_FromLong(1);
ans = PyNumber_TrueDivide(x, z);
Py_DECREF(z);
}
}
Py_DECREF(x);
Py_DECREF(y);
return ans;
}

Notice that if the exception is anything except a division-by zero-error it is passed back to the Python system.

extendPython180

 

In chapter but not in this extract

  • Testing Reference Counting
  • An Example
  • Using sys and gc

 Summary

  • C doesn’t provide any error handling beyond error numbers and error messages and most often an error means a runtime error.

  • Python provides a sophisticated exception system designed to allow errors to be caught and corrected, so avoiding a runtime error.

  • An exception is raised by the system creating an instance of an exception class and then moving up the call stack to find the first try/except that will handle the exception.

  • If no try/except is found then a runtime error occurs.

  • A try clause can be set to accept specific exceptions by specifying the classes and hence derived objects it can handle.

  • A Python program can raise a built-in exception using the raise keyword and it can raise a custom exception by inheriting from the Exception base class.

  • A Python C function returns NULL or -1 to signal that an error has occurred and sets three system variable to specify the exception.

  • Returning NULL or -1 without raising an exception is a run time error.

  • The C API provides a range of classes that represent built-in exceptions and you can create derived classes to represent custom exceptions.

  • The C API can raise an exception by creating an exception object and returning NULL or -1.

  • In most cases a C function will pass on any error that a C API function returns to the system after doing any cleaning up that might be necessary.

  • A C function can also test for the type of exception and handle it if this is possible.

  • Reference counting is very difficult to get right and is the cause of many errors and exceptions.

  • You can discover what the reference count is in C and Python.

  • Objects passed into a C function may not have the reference count you expect due to system references and caching. Check that the change in the reference count is correct.

  • You can use the sys and gc modules to monitor and control garbage collection.

Extending & Embedding Python Using C

By Mike James

extendPython360

Buy from Amazon.

Contents

       Preface

  1. Extending And Embedding Python
  2. A First C Module Using Linux 
  3. A First C Module Using Windows
  4. Module Basics
        Extract: A First Module
        Extract: 
    Pi 
  5. Arguments
  6. Returning Python Objects
  7. Objects And Attributes
  8. More Complex Objects – Tuples, Lists and Dicts
  9. Errors, Exceptions And Reference Counting
        Extract:
    Exceptions 
  10. Bytes And Strings
  11. Modules And Attributes
  12. New Types
  13. Advanced Types
  14. Threads And The GIL
  15. Embedding Python ***NEW!!!

<ASIN:B0CK3X93KF>

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

Banner


Google Updates Responsible AI Toolkit
01/11/2024

Google has announced updates to the Responsible Generative AI Toolkit to enable it to be used with any LLM model. The Responsible GenAI Toolkit provides resources to design, build, and evaluate open A [ ... ]



PHP 8.4 Adds Property Hooks
26/11/2024

PHP 8.4 is available with improvements including property hooks, asymmetric visibility, and an updated DOM API.


More News

espbook

 

Comments




or email your comment to: comments@i-programmer.info



Last Updated ( Saturday, 04 November 2023 )