跳到主要内容

在 C# 中调用 Python

💡 PuerTS 3.0 同时支持 C# 调用 JavascriptLua,语法各有不同,可点击链接查看对应教程。

通过 Delegate 调用

PuerTS 提供了一个关键能力:将 Python 函数转换为 C# 的 delegate。依靠这个能力,你就可以在 C# 侧调用 Python 函数。

public delegate void TestCallback(string msg);

public class TestClass
{
public TestCallback Callback;

public void TriggerCallback()
{
if (Callback != null)
{
Callback("hello_from_csharp");
}
}
}

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendPython());
env.Eval(@"
exec('''
import Puerts.UnitTest.TestClass as TestClass
obj = TestClass()

def callback(msg):
global info
info = msg

# Assign a Python function to the C# delegate property
obj.Callback = callback
# Trigger the callback from C# side
obj.TriggerCallback()
''')
");
// info is now 'hello_from_csharp'
env.Dispose();
}

⚠️ 注意:Python 中多行代码需要使用 exec('''...''') 包裹。单行表达式可以直接用 Eval 执行。

你也可以在 Python 侧主动调用 delegate 的 Invoke 方法:

# Directly invoke the delegate from Python
obj.Callback.Invoke('hello_from_python')

从 C# 往 Python 传参

把 Python 函数转换成 delegate 时,可以将其转换成带参数的 delegate,这样就可以把 C# 变量传递给 Python。传参时,类型转换的规则和把变量从 C# 返回到 Python 是一致的。

Python 支持使用 lambda 表达式来创建简单的匿名函数:

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendPython());
// Get a Python lambda as a C# delegate
System.Action<int> LogInt = env.Eval<System.Action<int>>("lambda a: print(a)");

LogInt(3); // Output: 3
env.Dispose();
}

对于更复杂的逻辑,使用 def 定义函数,然后通过 Eval 获取:

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendPython());
// Define a function with def, then retrieve it
env.Eval(@"
exec('''
def log_int(a):
print(a)
''')
");
System.Action<int> LogInt = env.Eval<System.Action<int>>("log_int");

LogInt(3); // Output: 3
env.Dispose();
}

Python 函数还支持可选参数,转换为不同签名的 delegate 后都可以正常工作:

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendPython());
env.Eval(@"
exec('''
def flexible_func(a, b=0):
if b == 0:
return str(a)
else:
return str(a) + str(b)
''')
");

// Cast as Action<int> — only pass the first argument
var cb1 = env.Eval<Action<int>>("flexible_func");
cb1(1); // Uses default b=0

// Cast as Action<string, long> — pass both arguments
var cb2 = env.Eval<Action<string, long>>("flexible_func");
cb2("hello", 999); // Output: hello999

env.Dispose();
}

需要注意的是,如果你生成的 delegate 带有值类型参数,需要添加 UsingAction 或者 UsingFunc 声明。具体请参见 FAQ


从 C# 调用 Python 并获得返回值

与上一部分类似,只需要将 Action delegate 变成 Func delegate 就可以了。

使用 lambda 表达式(适合简单的单行逻辑):

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendPython());
// Python lambda can directly return a value
System.Func<int, int> Add3 = env.Eval<System.Func<int, int>>("lambda a: 3 + a");

System.Console.WriteLine(Add3(1)); // Output: 4
env.Dispose();
}

使用 def 定义函数(适合复杂逻辑):

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendPython());
env.Eval(@"
exec('''
def add3(a):
return 3 + a
''')
");
System.Func<int, int> Add3 = env.Eval<System.Func<int, int>>("add3");

System.Console.WriteLine(Add3(1)); // Output: 4
env.Dispose();
}

你也可以直接使用 Eval<T> 来获取简单的返回值:

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendPython());
// Directly evaluate a Python expression and get the return value
int result = env.Eval<int>("1 + 2");
System.Console.WriteLine(result); // Output: 3

string str = env.Eval<string>("'hello python'");
System.Console.WriteLine(str); // Output: hello python

// Convert non-string types with Python builtins
var ret = env.Eval<string>("str(9999)");
System.Console.WriteLine(ret); // Output: 9999

env.Dispose();
}

⚠️ 与 Lua 的差异:Python 的 lambda 表达式会自动返回结果(类似 JS),无需显式 return。但 def 定义的函数中必须使用 return 语句返回值,否则返回 None

需要注意的是,如果你生成的 delegate 带有值类型参数,需要添加 UsingAction 或者 UsingFunc 声明。具体请参见 FAQ


Python 中的错误处理

当 Python 代码中使用 raise 抛出异常时,C# 侧可以通过 try-catch 捕获:

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendPython());

// Python raise will be caught as a C# exception
try
{
env.Eval(@"
exec('''
raise Exception('something went wrong')
''')
");
}
catch (Exception e)
{
Debug.Log(e.Message); // Contains: something went wrong
}

// SyntaxError is also catchable
try
{
env.Eval(@"
exec('''
def test():
return 1 +
''')
");
}
catch (Exception e)
{
Debug.Log(e.Message); // Contains: SyntaxError
}

// RuntimeError (e.g. KeyError) is catchable too
try
{
env.Eval(@"
exec('''
obj = {}
obj['nonexistent']()
''')
");
}
catch (Exception e)
{
Debug.Log(e.Message); // Contains: KeyError
}

env.Dispose();
}

环境销毁与 Delegate 生命周期

当 Python 环境(ScriptEnv)被 Dispose() 后,之前转换的 delegate 将不再可用。调用已销毁环境的 delegate 会抛出异常,请务必注意管理好生命周期。

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendPython());
System.Action callback = env.Eval<System.Action>("lambda: print('hello')");

callback(); // OK — Output: hello

env.Dispose();

// ❌ This will throw an exception!
// callback();
}

在 Python 中实现 MonoBehaviour

综合上面所有能力,我们可以在 Python 里实现 MonoBehaviour 的生命周期回调:

using System;
using Puerts;
using UnityEngine;

public class PythonBehaviour : MonoBehaviour
{
public Action PythonStart;
public Action PythonUpdate;
public Action PythonOnDestroy;

static ScriptEnv pythonEnv;

void Awake()
{
if (pythonEnv == null) pythonEnv = new ScriptEnv(new BackendPython());

pythonEnv.Eval(@"
exec('''
import UnityEngine.MonoBehaviour as MonoBehaviour

def init_behaviour(bindTo):
def on_update():
print(""update..."")
def on_destroy():
print(""onDestroy..."")
bindTo.PythonUpdate = on_update
bindTo.PythonOnDestroy = on_destroy
''')
");
var init = pythonEnv.Eval<Action<MonoBehaviour>>("init_behaviour");
if (init != null) init(this);
}

void Start()
{
if (PythonStart != null) PythonStart();
}

void Update()
{
if (PythonUpdate != null) PythonUpdate();
}

void OnDestroy()
{
if (PythonOnDestroy != null) PythonOnDestroy();
PythonStart = null;
PythonUpdate = null;
PythonOnDestroy = null;
}
}

⚠️ 注意 Python 与其他语言的关键差异:

  • Python 多行代码需要 exec('''...''') 包裹
  • Python 使用 def 定义函数,无需 end 或花括号
  • Python 使用 import 语法访问 C# 类型
  • Python 的缩进(indentation)是语法的一部分,请注意保持一致

Python 与其他语言在 C# 调用方面的主要差异

特性JavascriptLuaPython
Eval 返回值表达式最后一个值自动返回必须使用 returnlambda 自动返回;def 需要 return
匿名函数(a) => { ... }function(a) ... endlambda a: ...
命名函数function f(a) { ... }function f(a) ... enddef f(a): ...
多行代码直接写直接写exec('''...''') 包裹
delegate 赋值obj.Callback = (msg) => { ... }obj.Callback = function(msg) ... endobj.Callback = callback_func
方法调用点号 obj.Method()冒号 obj:Method()点号 obj.Method()
输出到控制台console.log()print()print()
空值null / undefinednilNone
异常抛出throw new Error()error()raise Exception()

平台限制

⚠️ Python 后端当前不支持 WebGL、iOS、Android 平台。如需跨平台支持,请使用 Javascript 或 Lua 后端。


📖 其他语言的 C# 调用教程:C# 调用 Javascript | C# 调用 Lua | 三语言对比速查表