But sometimes, we need to take it up a notch and write our own custom functions for Python in the good old C programming language. And let me tell ya, it ain’t easy!
First you gotta have your setup script ready. This is where all the magic happens. You can create this file using any text editor of your choice or even a fancy IDE if that’s what floats your boat. Here’s an example:
# Importing necessary modules
from distutils.core import setup, Extension # Importing the setup and Extension modules from the distutils.core library
import numpy as np # Importing the numpy module and assigning it an alias "np"
# Setting up the setup function
setup( # Calling the setup function
name='my_module', # Setting the name of the module to "my_module"
version='0.1', # Setting the version of the module to "0.1"
ext_modules=[Extension('fputs', ['fputs.c'])] # Creating an Extension object with the name "fputs" and the source file "fputs.c" and passing it as a list to the ext_modules parameter
)
# Note: The setup function is used to define the metadata and options for the module, including its name, version, and any external dependencies.
# Note: The Extension object is used to define the source files and any external libraries needed for the module.
# Note: The numpy module is used for scientific computing and provides efficient data structures and functions for working with arrays.
# Note: The alias "np" is commonly used for the numpy module to save time and make the code more readable.
Now let’s break this down for you lazy out there. First, we import the necessary modules from distutils and numpy (because why not?). Then we define our setup function with all its arguments name, version, and ext_modules. The last argument is where it gets interesting! We pass a list of objects to the Extensions class constructor. Each object describes one C extension module in your setup script.
In this case, we’re passing two keyword arguments: name (which is self-explanatory) and filename (a list of paths to files with source code). In our example, we only have one file fputs.c. But you can add more if you want to be fancy!
Now that your setup script is ready, it’s time to build your Python C extension module using the following command:
# This script is used to build a Python C extension module using the setup.py file.
# It takes in two arguments: name and filename.
# The name argument is used to specify the name of the extension module.
# The filename argument is a list of paths to files with source code.
# In this example, we only have one file, fputs.c, but more can be added if desired.
# The following command is used to build the extension module.
# The build_ext option specifies that we want to build a C extension module.
# The --inplace option tells the script to build the module in the current directory.
# This will create a shared library file that can be imported into Python.
python setup.py build_ext --inplace
This will compile and install your Python C extension module in the current directory. If there are any errors or warnings, then your program will throw them now. Make sure you fix these before you try to import your module!
But wait what if you want to use gcc instead of clang? No problemo! Just set the CC environment variable accordingly:
# Set the CC environment variable to gcc
export CC=gcc
# Run the setup.py script to build the extension module in the current directory
python setup.py build_ext --inplace
Now that everything is in place, it’s time to see your module in action! Once it’s successfully built, fire up the interpreter to test run your Python C extension module:
# Import the "fputs" module
import fputs
# Use the "write_string" function from the "fputs" module to write the string "Real Python!" to the file "test_write.txt"
fputs.write_string("Real Python! ", "test_write.txt")
# Open the file "test_write.txt" in read mode and print its contents
print(open('test_write.txt', 'r').read())
# The "fputs" module is imported to access its functions
# The "write_string" function is used to write a string to a file
# The string "Real Python!" is written to the file "test_write.txt"
# The "print" function is used to display the contents of the file "test_write.txt"
Your function performs as expected! You pass a string and a file to write this string to, test_write.txt. The call to fputs() returns the number of bytes written to the file. You can verify this by printing the contents of the file.
Also recall how you passed certain arguments to the PyModuleDef and PyMethodDef structures in your setup script. You can see from this output that Python has used these structures to assign things like the function name and docstring.
But wait what if something goes wrong? Let’s say you want to raise an exception whenever the length of the string to be written is less than 10 characters:
# Import the fputs module
import fputs
# Call the write_short_string function from the fputs module to write a short string to a file
fputs.write_short_string("Python Rocks! ", "test_write.txt")
# Use a try-except block to catch any potential errors
try:
# Call the write_short_string function again, this time with a string that is too short
fputs.write_short_string("Hello, World!", "test_write.txt")
# If an error occurs, print the error message
except ValueError as e:
print(e)
# The fputs module is used to write strings to a file
# The write_short_string function takes in two arguments: the string to be written and the file name
# The try-except block is used to handle potential errors, in this case a ValueError
# If the string is less than 10 characters, a ValueError will be raised and the error message will be printed
In this case, we’re raising a custom exception called ValueError with a message explaining what went wrong. The program execution stops as soon as the exception occurs.
But wait what if you want to define your own constants or macros? No problemo! Just add them using PyModule_AddIntConstant() and PyModule_AddStringMacro():
# Import the necessary module
import math
# Define a custom exception called ValueError with a message explaining what went wrong
class ValueError(Exception):
def __init__(self, message):
self.message = message
# Define a constant PI using PyModule_AddIntConstant()
PI = math.pi
# Define a macro MY_MACRO using PyModule_AddStringMacro()
MY_MACRO = "Hello, World!"
# Print the value of PI
print(PI) # prints 3.141592653589793
# Print the value of MY_MACRO
print(MY_MACRO) # prints "Hello, World!"
In this case, we’re defining a constant called PI and a macro called MY_MACRO using PyModule_AddIntConstant() and PyModule_AddStringMacro(), respectively. You can access them from within the Python interpreter just like any other variable or function!
But wait what if you want to test your module? No problemo! Just write some tests for it:
# Import the necessary modules
import unittest
import os
from fputs import * # Import the custom module "fputs" which contains the functions we want to test
# Create a test class that inherits from unittest.TestCase
class TestFputs(unittest.TestCase):
# Set up the necessary variables and files for testing
def setUp(self):
self.filename = "test_write.txt"
open(self.filename, 'w').close() # Create an empty file for testing
# Remove the test file after testing is complete
def tearDown(self):
os.remove(self.filename)
# Test the write_string() function
def test_fputs(self):
write_string("Real Python! ", self.filename) # Call the function with a string and the test file
with open(self.filename, 'r') as f:
content = f.read() # Read the contents of the test file
self.assertEqual(content, "Real Python!\n") # Check if the contents match the expected output
# Test the write_short_string() function
def test_write_short_string(self):
try:
write_short_string("Python Rocks! ", self.filename) # Call the function with a short string and the test file
except ValueError as e:
self.assertEqual(str(e), "String is too short.") # Check if the correct error message is raised
# Run the tests if the script is executed directly
if __name__ == '__main__':
unittest.main() # Run the tests defined in the test class
In this case, we’re writing some tests for our module using the Python testing framework unittest. We create a new class called TestFputs and define two test cases: one to write a string to a file and another to raise an exception when the length of the string is less than 10 characters.
And that’s it! You now have a basic version of your module ready, but there’s a lot more that you can do! You can improve your module by adding things like custom exceptions and constants.
So what are you waiting for? Go ahead and write some Python interfaces using C today!