繁體中文
English 简体中文 繁體中文
Methodox 首頁

在 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,本頁的所有內容都可以正常使用。