Of course! Using strings with SWIG and Python is a very common task. This guide will cover the essentials, from the basics to more advanced scenarios, with clear code examples.
The Core Concept: std::string and Python str
SWIG provides a seamless mapping between C++'s std::string and Python's built-in string type, str.
- When a C++ function returns a
std::string, SWIG automatically converts it into a Pythonstrobject. - When you pass a Python
strobject to a C++ function expecting astd::string, SWIG automatically converts it into astd::string.
This is handled by SWIG's built-in type conversions, so you don't need to write any special typemaps for the most common use cases.
The Simplest Case: Returning a std::string
Let's start with a basic C++ function that returns a std::string.
C++ Code (string_example.h and string_example.cpp)
string_example.h
#include <string> // A simple function that returns a C++ string std::string get_greeting(); // A function that takes a C++ string and returns a modified version std::string process_message(const std::string& input);
string_example.cpp
#include "string_example.h"
#include <iostream>
std::string get_greeting() {
return "Hello from C++!";
}
std::string process_message(const std::string& input) {
std::cout << "C++ received: " << input << std::endl;
return "Processed: " + input;
}
SWIG Interface File (string_example.i)
This is where you tell SWIG how to wrap your C++ code.
/* File: string_example.i */
%module string_example
%{
/* This block is included verbatim in the generated wrapper code. */
#include "string_example.h"
%}
/* Tell SWIG about the C++ header file. */
%include "std_string.i" /* Important for robust std::string support */
/* Include the C++ header file. SWIG will parse it. */
%include "string_example.h"
Key Points:
%include "std_string.i": While the basic conversion works automatically, including this file provides more robust support, especially for functions that takestd::stringby value or have more complex interactions. It's a best practice to include it.%include "string_example.h": This is where SWIG learns about your functions.
Build Script (setup.py)
We'll use distutils to build the Python extension.
from setuptools import setup, Extension
module = Extension('_string_example',
sources=['string_example_wrap.cxx', 'string_example.cpp'],
extra_compile_args=['-std=c++11'])
setup(name='string_example',
version='1.0',
description='A simple SWIG example for strings',
ext_modules=[module])
Build and Run
-
Generate the wrapper code:
swig -c++ -python -py3 string_example.i
This creates
string_example.pyandstring_example_wrap.cxx. -
Build the extension:
python setup.py build_ext --inplace
This compiles the C++ code and the wrapper, creating
_string_example.so(on Linux/macOS) or_string_example.pyd(on Windows). -
Test it in Python: Create a
test.pyfile.import string_example # Call get_greeting() - the returned std::string becomes a Python str greeting = string_example.get_greeting() print(f"Greeting from C++: {greeting}") print(f"Type of greeting: {type(greeting)}") # Call process_message() - Python str is converted to std::string input_str = "A message from Python" processed = string_example.process_message(input_str) print(f"\nProcessed message: {processed}") print(f"Type of processed: {type(processed)}")
Expected Output:
Greeting from C++: Hello from C++!
Type of greeting: <class 'str'>
C++ received: A message from Python
Processed message: Processed: A message from Python
Type of processed: <class 'str'>
Passing Strings from Python to C++
This is the reverse operation. As mentioned, SWIG handles the conversion from Python str to std::string automatically.
Let's modify our C++ code to accept strings as arguments.
C++ Code (string_arg.h and string_arg.cpp)
string_arg.h
#include <string> // Takes a string and prints it void print_message(const std::string& msg); // Takes two strings and concatenates them in C++ std::string join_strings(const std::string& a, const std::string& b);
string_arg.cpp
#include "string_arg.h"
#include <iostream>
void print_message(const std::string& msg) {
std::cout << "[C++ Print Function] Received: " << msg << std::endl;
}
std::string join_strings(const std::string& a, const std::string& b) {
return a + " " + b;
}
SWIG Interface File (string_arg.i)
/* File: string_arg.i */
%module string_arg
%{
#include "string_arg.h"
%}
%include "std_string.i"
%include "string_arg.h"
Build and Run
Follow the same build steps as before (swig, python setup.py build_ext --inplace).
Python Test (test_arg.py)
import string_arg
# Create a Python string
my_string = "This is a test string from Python"
# Pass it to a C++ function expecting std::string
string_arg.print_message(my_string)
# Pass two strings to a function that joins them
part_a = "SWIG"
part_b = "is powerful"
joined = string_arg.join_strings(part_a, part_b)
print(f"\nJoined string from C++: '{joined}'")
Expected Output:
[C++ Print Function] Received: This is a test string from Python
Joined string from C++: 'SWIG is powerful'
Advanced Topic: Modifying a String in C++
What if you want to modify the string inside a C++ function and have those changes reflected in the Python str object?
Crucial Point: Python strings are immutable. You cannot change a Python str in-place. Therefore, you cannot pass a Python string by reference and have it modified directly.
The standard and most Pythonic way to handle this is to pass the string by value and have the C++ function return the modified string.
C++ Code (string_modify.h and string_modify.cpp)
string_modify.h
#include <string> #include <algorithm> // Takes a string by value, modifies it, and returns it. // This is the idiomatic way to handle "modifying" strings. std::string to_uppercase(std::string text);
string_modify.cpp
#include "string_modify.h"
#include <algorithm> // For std::transform
#include <cctype> // For ::toupper
std::string to_uppercase(std::string text) {
std::transform(text.begin(), text.end(), text.begin(),
[](unsigned char c){ return std::toupper(c); });
return text;
}
Python Test (test_modify.py)
import string_modify
original = "hello world"
print(f"Original string: '{original}'")
# Call the function. The original Python string is unchanged.
modified = string_modify.to_uppercase(original)
print(f"Returned string: '{modified}'")
print(f"Original string after call: '{original}' (it's immutable)")
Expected Output:
Original string: 'hello world'
Returned string: 'HELLO WORLD'
Original string after call: 'hello world' (it's immutable)
Handling char* (C-style strings)
Sometimes you'll have to deal with C-style const char* or char*. SWIG has rules for this too:
const char*(input): Can be passed a Pythonstrobject. SWIG converts it to a C-style string.char*(output): If a C++ function returns achar*that was dynamically allocated (new char[...]), SWIG will automaticallydelete[]it for you to prevent memory leaks. This is very convenient.
C++ Code (c_string_example.h)
const char* get_c_style_string(); char* create_and_return_c_string();
C++ Implementation (c_string_example.cpp)
#include "c_string_example.h"
#include <cstring> // For strcpy
const char* get_c_style_string() {
// This string is in static memory, so it's safe to return.
return "This is a C-style string";
}
char* create_and_return_c_string() {
char* str = new char[50];
strcpy(str, "Dynamically allocated C-string");
return str;
}
SWIG Interface File (c_string_example.i)
No special typemaps are needed for this basic case.
%module c_string_example
%{
#include "c_string_example.h"
%}
%include "c_string_example.h"
Python Test (test_c_string.py)
import c_string_example
# Calling a function that returns const char*
# The result is a Python str
c_str = c_string_example.get_c_style_string()
print(f"C-style string result: '{c_str}' (type: {type(c_str)})")
# Calling a function that returns char*
# SWIG handles the memory deallocation automatically
dynamic_c_str = c_string_example.create_and_return_c_string()
print(f"Dynamic C-string result: '{dynamic_c_str}' (type: {type(dynamic_c_str)})")
# You can use it like a normal Python string
print(f"Uppercased version: {dynamic_c_str.upper()}")
Summary
| C++ Type | Python Type | Direction | Notes |
|---|---|---|---|
std::string |
str |
Input/Output | Automatic conversion. Use %include "std_string.i" for best results. |
const std::string& |
str |
Input | Automatic conversion. Pass-by-reference is efficient. |
std::string |
str |
Output | Automatic conversion. To "modify" a string, pass by value and return the new string. |
const char* |
str |
Input | Automatic conversion. Can accept a Python str. |
char* |
str |
Output | Automatic conversion. SWIG will delete[] the memory if it was allocated with new. |
