在 Python 中使用 .NET
SteelPython 執行在 .NET 上,因此整個 .NET 類庫——以及你手頭的任何 .NET 程式集——都可以在同一程序中供 Python 程式碼使用,無需設定任何橋接:.NET 名稱空間可以像 Python 包一樣匯入。如果你在 CPython 中用過 pythonnet,就會覺得這很熟悉,只是省去了它的 import clr 和 clr.AddReference 那套繁瑣步驟。
匯入 .NET 名稱空間
from System import Math, Environment
from System.Collections.Generic import List, Dictionary
print(Math.Max(3, 7), Math.Round(2.567, 2)) # 7 2.57
print(Environment.ProcessorCount)
.NET 名稱空間可以像 Python 包一樣匯入,無需事先做任何設定。Python 模組始終優先:如果 sys.path 上的某個資料夾或模組與某個名稱空間同名,import 會找到 Python 的那一個。
.NET 執行時的每個名稱空間都可以匯入,無論其程式集是否已經載入:from System.Collections.ObjectModel import ObservableCollection 會載入定義它的程式集。
你自己的程式集可以像擴充模組一樣匯入。把 Acme.Pricing.dll 放在 sys.path 上的某個資料夾中——例如放在指令碼旁邊——然後匯入它的名稱空間:
from Acme.Pricing import PriceList # finds Acme.Pricing.dll on sys.path and loads it
如果程式集所在的資料夾你不想新增到 sys.path,可以用 clr 模組按路徑載入它:
import clr
clr.AddReference(r"C:\Libraries\Acme.Pricing.dll")
from Acme.Pricing import PriceList
clr 還提供了幾個輔助工具,下文會用到:clr.python_enum、clr.cancellable、clr.GetClrType 和 clr.reflection_enabled。為 pythonnet 編寫、以 import clr 和 clr.AddReference(...) 開頭的程式碼可以不加修改地執行。
使用 .NET 物件
通過呼叫類來建立物件,並像在 C# 中一樣使用它們的方法、屬性、欄位和索引器:
from System.Text import StringBuilder
sb = StringBuilder()
sb.Append("a").Append(1).Append(2.5)
print(sb.ToString()) # a12.5
print(sb.Length) # 5
過載根據 Python 實參的型別來選擇,關鍵字實參和參數預設值也都可以使用。.NET 物件同樣符合 Python 的協議:IEnumerable 可迭代,IDisposable 可用於 with 語句,集合支援 len()、in 和索引(包括負索引),C# 中定義的運算子也可以在 Python 中使用。
from System.IO import StringWriter
with StringWriter() as w: # Dispose() is called at the end of the block
w.Write("written by .NET")
print(w.ToString())
泛型
在方括號中給出型別實參:
names = List[str]()
names.Add("Ada")
names.Add("Grace")
print(len(names), names[0], list(names)) # 2 Ada ['Ada', 'Grace']
ages = Dictionary[str, int]({"Ada": 36}) # built from a Python dict
ages["Grace"] = 85
print("Ada" in ages) # True
泛型方法會盡可能根據實參推斷型別實參;否則請顯式給出,例如 Enumerable.Where[int](numbers, predicate)。
out 和 ref 參數
它們會作為額外的返回值,以元組形式返回:
from System import Int32
ok, value = Int32.TryParse("42") # True 42
ok, value = Int32.TryParse("forty") # False 0
委託和事件
在 .NET 需要委託的任何地方,都可以傳入 Python 函式(或 lambda),也可以用 += 訂閱事件(並用 -= 取消訂閱):
from System.Linq import Enumerable
evens = Enumerable.Where[int](List[int]([1, 2, 3, 4]), lambda n: n % 2 == 0)
print(list(evens)) # [2, 4]
from System.Collections.ObjectModel import ObservableCollection
items = ObservableCollection[str]()
def changed(sender, e):
print("collection changed:", e.Action, list(e.NewItems))
items.CollectionChanged += changed
items.Add("bolts") # collection changed: Add ['bolts']
列舉
.NET 列舉值的行為類似整數,可以用 |、& 和 ~ 組合:
from System.IO import FileAccess
flags = FileAccess.Read | FileAccess.Write
print(flags, flags == FileAccess.ReadWrite, int(flags)) # ReadWrite True 3
clr.python_enum(FileAccess) 會給出一個具有相同成員的 Python enum.IntFlag(或 IntEnum),供需要 Python 列舉的程式碼使用。
實現 .NET 介面和派生自 .NET 類
Python 類可以實現 .NET 介面或派生自 .NET 類,然後由 .NET 程式碼呼叫它的方法:
from System.Collections.Generic import IComparer
class Descending(IComparer[int]):
def Compare(self, x, y):
return y - x
numbers = List[int]([3, 1, 2])
numbers.Sort(Descending()) # .NET's sort calls the Python Compare
print(list(numbers)) # [3, 2, 1]
重寫 .NET 基類的虛方法也是同樣的方式,並且 super() 可以存取 .NET 的實現。
非同步:等待 .NET 任務
.NET 的 Task 和 ValueTask 物件可以在 Python 協程中被 await,IAsyncEnumerable 物件可以用 async for 迭代:
import asyncio
from System.Threading.Tasks import Task
async def main():
await Task.Delay(10) # a .NET task, awaited by asyncio
return "awaited a .NET task"
print(asyncio.run(main()))
asyncio.gather 和 asyncio.wait_for 都可以與它們配合使用。若要讓 asyncio 能夠取消某個 .NET 操作,請用 clr.cancellable 向它傳入一個取消令牌:await clr.cancellable(lambda token: client.DownloadAsync(url, token)) 會在 asyncio 任務被取消時取消該令牌。
值如何轉換
值以自然的方式在 Python 和 .NET 之間傳遞,在記憶體佈局允許的情況下不進行復制:
| Python | .NET |
|---|---|
None |
null |
bool、int、float |
bool、各整數型別(超出範圍時引發 OverflowError)、double、float |
decimal.Decimal |
decimal,精確轉換 |
str |
string、char |
datetime、date、time、timedelta |
DateTime 和 DateTimeOffset、DateOnly、TimeOnly、TimeSpan |
uuid.UUID、pathlib.Path |
Guid、FileInfo 和 DirectoryInfo |
bytes、bytearray、numpy 陣列 |
byte[] 及其他陣列、Memory<T> 和 ReadOnlyMemory<T>(儘可能共享而不復制) |
list、tuple、set、dict |
陣列、List<T>、HashSet<T>、Dictionary<K,V> 及其介面 |
| 函式 | 委託 |
| 協程、非同步生成器 | Task<T>、IAsyncEnumerable<T> |
從 .NET 返回的結果會變成相應的 Python 值——這意味著它們此後就是 Python 物件:DateTime(2026, 10, 29) 得到的是 Python 的 datetime,具有 Python 的方法(+ timedelta(days=3)),而不是 .NET 的方法(AddDays)。
.NET 數值陣列通過 Python 的緩衝區協議共享記憶體,因此 numpy 可以就地處理它們:
from System import Array
import numpy as np
a = Array[float]([1.0, 2.0, 3.0])
np.asarray(a)[:] *= 10 # changes the .NET array itself
print(list(a)) # [10.0, 20.0, 30.0]
異常
.NET 異常到達 Python 時,會成為一個既是 .NET 型別、又是對應 Python 內建異常的異常,因此兩種 except 子句都能捕獲它:
try:
Int32.Parse("x")
except ValueError as e: # also catchable as System.FormatException
print(type(e).__name__) # FormatException
ArgumentException 和 FormatException 是 ValueError,KeyNotFoundException 是 KeyError,IndexOutOfRangeException 是 IndexError,InvalidOperationException 是 RuntimeError,IOException 是 OSError(FileNotFoundException 是 FileNotFoundError),NotImplementedException 是 NotImplementedError,依此類推。異常會保留其 .NET 成員:e.Message、e.StackTrace、e.InnerException。
你自己的 C# 程式碼
任何 .NET 類庫都可以在 Python 中使用:把它的 .dll 放到 sys.path 上,然後匯入它的名稱空間。若要編寫專為 Python 設計的庫——具有自己的模組名、原生速度的繫結,以及 Python 可以繼承的類——請參閱 編寫擴充。若要反過來在 C# 應用程式中執行 Python,請參閱 嵌入到 C# 中。
Native AOT 構建
上述所有內容都使用了 .NET 反射,而 SteelPython 的原生編譯(Native AOT)構建不包含反射。在這種構建中,import clr; clr.reflection_enabled 為 False,並且只有宿主應用程式公開的 .NET 型別才可匯入。如果你不確定自己使用的是哪種構建,請檢查 clr.reflection_enabled;在常規 .NET 構建中它為 True,本頁的所有內容都可以正常使用。