杰瑞科技汇

Swig中Python如何处理字符串?

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 Python str object.
  • When you pass a Python str object to a C++ function expecting a std::string, SWIG automatically converts it into a std::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 take std::string by 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

  1. Generate the wrapper code:

    swig -c++ -python -py3 string_example.i

    This creates string_example.py and string_example_wrap.cxx.

  2. 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).

  3. Test it in Python: Create a test.py file.

    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 Python str object. SWIG converts it to a C-style string.
  • char* (output): If a C++ function returns a char* that was dynamically allocated (new char[...]), SWIG will automatically delete[] 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.
分享:
扫描分享到社交APP
上一篇
下一篇