Basic Usage
This guide covers the fundamental concepts and patterns for using CSnakes in your .NET applications.
Basic Project Setup
1. Create Python Module
Create a Python file (e.g., math_utils.py
) with type-annotated functions:
def add(a: int, b: int) -> int:
"""Add two integers."""
return a + b
def divide(a: float, b: float) -> float:
"""Divide two numbers."""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
def get_info() -> dict[str, str]:
"""Return system information."""
import platform
return {
"system": platform.system(),
"version": platform.version(),
"machine": platform.machine()
}
2. Configure Project File
Add the Python file to your .csproj
:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CSnakes.Runtime" Version="1.*-*" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="math_utils.py">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AdditionalFiles>
</ItemGroup>
</Project>
3. Initialize Python Environment
using CSnakes.Runtime;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services
.WithPython()
.WithHome(Environment.CurrentDirectory)
.FromRedistributable();
var app = builder.Build();
var env = app.Services.GetRequiredService<IPythonEnvironment>();
4. Call Python Functions
// Get the generated module wrapper
var mathModule = env.MathUtils();
// Call Python functions
var sum = mathModule.Add(5, 3);
Console.WriteLine($"5 + 3 = {sum}"); // Output: 5 + 3 = 8
var quotient = mathModule.Divide(10.0, 3.0);
Console.WriteLine($"10 / 3 = {quotient:F2}"); // Output: 10 / 3 = 3.33
var info = mathModule.GetInfo();
foreach (var (key, value) in info)
{
Console.WriteLine($"{key}: {value}");
}
Naming Conventions
CSnakes automatically converts Python naming conventions to C# conventions for function names:
Python | C# |
---|---|
my_function |
MyFunction |
calculate_average |
CalculateAverage |
get_user_info |
GetUserInfo |
Function argument names are converted to C# conventions in lower case:
Python | C# |
---|---|
my_argument |
myArgument |
arg1 |
arg1 |
Module Access
Python modules are accessed through the environment using the module's filename:
# file: data_processor.py
def process_data(data: list[int]) -> list[int]:
return [x * 2 for x in data]
// Access the module
var processor = env.DataProcessor();
var result = processor.ProcessData(new[] { 1, 2, 3, 4 });
// result: [2, 4, 6, 8]
Error Handling
Python exceptions are automatically converted to PythonInvocationException
:
try
{
var result = mathModule.Divide(10.0, 0.0);
}
catch (PythonInvocationException ex)
{
Console.WriteLine($"Python error: {ex.Message}");
Console.WriteLine($"Python type: {ex.PythonExceptionType}");
Console.WriteLine($"Stack trace: {ex.PythonStackTrace}");
}
Working with Complex Types
Lists and Collections
def process_numbers(numbers: list[int]) -> list[int]:
return [n * 2 for n in numbers if n > 0]
def get_user_names() -> list[str]:
return ["Alice", "Bob", "Charlie"]
var numbers = new[] { -1, 2, -3, 4, 5 };
var processed = mathModule.ProcessNumbers(numbers);
// Result: [4, 8, 10]
var names = mathModule.GetUserNames();
foreach (var name in names)
{
Console.WriteLine(name);
}
Dictionaries
def create_user(name: str, age: int) -> dict[str, str | int]:
return {"name": name, "age": age, "id": hash(name)}
var user = mathModule.CreateUser("Alice", 30);
Console.WriteLine($"Name: {user["name"]}");
Console.WriteLine($"Age: {user["age"]}");
Tuples
def get_coordinates() -> tuple[float, float]:
return (12.34, 56.78)
def parse_name(full_name: str) -> tuple[str, str]:
parts = full_name.split(" ", 1)
return (parts[0], parts[1] if len(parts) > 1 else "")
var coordinates = mathModule.GetCoordinates();
Console.WriteLine($"X: {coordinates.Item1}, Y: {coordinates.Item2}");
var (firstName, lastName) = mathModule.ParseName("John Doe");
Console.WriteLine($"First: {firstName}, Last: {lastName}");
Optional Parameters
Python default values are preserved in the generated C# methods:
def greet(name: str, prefix: str = "Hello", suffix: str = "!") -> str:
return f"{prefix}, {name}{suffix}"
// All these calls are valid:
var greeting1 = mathModule.Greet("Alice"); // "Hello, Alice!"
var greeting2 = mathModule.Greet("Bob", "Hi"); // "Hi, Bob!"
var greeting3 = mathModule.Greet("Charlie", "Hey", "!!!"); // "Hey, Charlie!!!"