目錄
IronPython 旨在成為 Python 語言的完全相容實作。同時,相較於 CPython,獨立實作的價值在於提供 .NET 的函式庫生態系。IronPython 透過將 .NET 概念顯示為 Python 實體來執行這項工作。目前存在的 Python 語法和新的 Python 函式庫(如 clr)可用於讓 IronPython 程式碼使用 .NET 功能。
.NET 中功能性的最小分配單位是一個 組件,通常對應到一個副檔名為 .dll 的檔案。組件會提供,位置則位於應用程式的安裝資料夾,或是 GAC (全球組件快取) 中。組件可以使用 clr 模組的方法進行載入。下列程式碼會載入 System.Xml.dll 組件,該組件為 .NET 標準實作的一部分,而且已安裝在 GAC 中
>>> import clr >>> clr.AddReference("System.Xml")
IronPython 載入的組件完整清單可在 clr.References 中找到
>>> "System.Xml" in [assembly.GetName().Name for assembly in clr.References] True
所有 .NET 組件都有獨一無二的版本號碼,這允許使用給定組件的特定版本。下列程式碼會載入 System.Xml.dll 的版本,隨附 .NET 2.0 和 .NET 3.5
>>> import clr >>> clr.AddReference("System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
你可以載入 GAC 或 appbase(通常為 ipy.exe 或主機應用程式執行檔的資料夾)中都沒有的組件,方法是使用 clr.AddReferenceToFileAndPath 或設定 sys.path。有關詳細資訊,請參閱 clr.AddReference-methods。
注意
IronPython 只會識別已使用 clr.AddReference-methods 之一載入的組件。在載入 IronPython 之前,可能會已經載入其他組件,或是其他組件會由應用程式的其他部分呼叫 System.Reflection.Assembly.Load 來載入,但 IronPython 不會察覺到這些組件。
預設情況下,核心組件(mscorlib.dll 和 .NET Framework 上的 System.dll/ .NET Core 上的 System.Private.CoreLib.dll)會由 DLR 自動載入。這能讓你開始使用這些組件(IronPython 本身依賴於這些組件),而不必呼叫 clr.AddReference-methods。
組件載入後,就可以從 IronPython 程式碼存取組件中包含的命名空間和型別。
.NET 命名空間和已載入組件的子命名空間顯示為 Python 模組
>>> import System >>> System #doctest: +ELLIPSIS <module 'System' (CLS module, ... assemblies loaded)> >>> System.Collections #doctest: +ELLIPSIS <module 'Collections' (CLS module, ... assemblies loaded)>
命名空間中的型別顯示為 Python 型別,而且可以做為命名空間的屬性來存取。下列程式碼從 mscorlib.dll 存取 System.Environment 類別
>>> import System >>> System.Environment <type 'Environment'>
就像正常的 Python 模組,您也可以使用其他形式的import
>>> from System import Environment >>> Environment <type 'Environment'>
>>> from System import * >>> Environment <type 'Environment'>
警告
使用from <名稱空間> import *可能會導致 Python 內建(__builtins__ 的元素)被 .NET 類型或子空間隱藏。特別是,在執行完from System import *, Exception將存取 System.Exception .NET 類型,而不是 Python 的 Exception 類型。
根名稱空間儲存在 sys.modules 的模組中
>>> import System >>> import sys >>> sys.modules["System"] #doctest: +ELLIPSIS <module 'System' (CLS module, ... assemblies loaded)>
當載入新的組件時,他們可能會新增屬性到現有的名稱空間模組物件中。
import 會優先給予 .py 檔案。舉例來說,如果在路徑中存在稱為 System.py 的檔案,它會被導入,而不是 System 名稱空間
>>> # create System.py in the current folder >>> f = open("System.py", "w") >>> f.write('print "Loading System.py"') >>> f.close() >>> >>> # unload the System namespace if it has been loaded >>> import sys >>> if sys.modules.has_key("System"): ... sys.modules.pop("System") #doctest: +ELLIPSIS <module 'System' (CLS module, ... assemblies loaded)> >>> >>> import System Loading System.py >>> System #doctest: +ELLIPSIS <module 'System' from '...System.py'>
注意
請務必刪除 System.py
>>> import os >>> os.remove("System.py") >>> sys.modules.pop("System") #doctest: +ELLIPSIS <module 'System' from '...System.py'> >>> import System >>> System #doctest: +ELLIPSIS <module 'System' (CLS module, ... assemblies loaded)>
.NET 支援泛型類型,這些類型允許相同的程式碼支援多個類型參數,同時保留類型安全的優點。集合類型(像是清單、向量等)是泛型類型有用的典型範例。.NET 在 System.Collections.Generic 名稱空間中具有許多泛型集合類型。
IronPython 將泛型類型揭露為一個特殊的 type 物件,它支援使用 type 物件(或索引)作為索引。
>>> from System.Collections.Generic import List, Dictionary >>> int_list = List[int]() >>> str_float_dict = Dictionary[str, float]()
請注意,可能會存在一個非泛型類型以及一個或多個具有相同名稱的泛型類型 [1]。在這種情況下,此名稱可以使用不帶任何索引的方式來存取非泛型類型,而且可以用不同的類型數目來索引它以存取具有相應類型參數數目的泛型類型。下面的程式碼存取 System.EventHandler 和 System.EventHandler<TEventArgs>
>>> from System import EventHandler, EventArgs >>> EventHandler # this is the combo type object <types 'EventHandler', 'EventHandler[TEventArgs]'> >>> # Access the non-generic type >>> dir(EventHandler) #doctest: +ELLIPSIS ['BeginInvoke', 'Clone', 'DynamicInvoke', 'EndInvoke', ... >>> # Access the generic type with 1 type paramter >>> dir(EventHandler[EventArgs]) #doctest: +ELLIPSIS ['BeginInvoke', 'Call', 'Clone', 'Combine', ...
[1] | 這指的是使用者友善的名稱。在幕後,.NET 類型名稱包含類型參數的數目 >>> clr.GetClrType(EventHandler[EventArgs]).Name 'EventHandler`1' |
巢狀類型被揭露為外部類別的屬性
>>> from System.Environment import SpecialFolder >>> SpecialFolder <type 'SpecialFolder'>
.NET 類型被揭露為 Python 類別。與 Python 類別一樣,您通常無法使用from <名稱> import *:
>>> from System.Guid import * Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: no module named Guid
您可以匯入特定的成員,不論靜態和實例
>>> from System.Guid import NewGuid, ToByteArray >>> g = NewGuid() >>> ToByteArray(g) #doctest: +ELLIPSIS Array[Byte](...
請注意,如果您匯入靜態屬性,您會在import執行時匯入值,而不是像您可能誤認的每次使用上要評估的名稱物件
>>> from System.DateTime import Now >>> Now #doctest: +ELLIPSIS <System.DateTime object at ...> >>> # Let's make it even more obvious that "Now" is evaluated only once >>> a_second_ago = Now >>> import time >>> time.sleep(1) >>> a_second_ago is Now True >>> a_second_ago is System.DateTime.Now False
某些 .NET 型別僅有靜態方法,可與命名空間相提並論。C# 將它們稱為 靜態類別,並要求此類別僅包含靜態方法。IronPython 允許您匯入所有此類 靜態類別 的靜態方法。System.Environment 即為靜態類別的一個範例
>>> from System.Environment import * >>> Exit is System.Environment.Exit True
巢狀型別也會被匯入
>>> SpecialFolder is System.Environment.SpecialFolder True
不過,不會匯入屬性
>>> OSVersion Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'OSVersion' is not defined >>> System.Environment.OSVersion #doctest: +ELLIPSIS <System.OperatingSystem object at ...>
.NET 使用 System.Type 表現型別。不過,當您在 Python 程式碼中存取 .NET 型別時,將取得 Python type 物件 [2]
>>> from System.Collections import BitArray >>> ba = BitArray(5) >>> isinstance(type(ba), type) True
這允許統一 (Pythonic) 檢視 Python 及 .NET 型別。例如,isinstance 也適用於 .NET 型別
>>> from System.Collections import BitArray >>> isinstance(ba, BitArray) True
如果需要取得 .NET 型別的 System.Type 執行個體,則需要使用 clr.GetClrType。反之,您可以使用 clr.GetPythonType 來對應 System.Type 物件取得 type 物件。
統一也延伸至其他型別系統實體,例如方法。.NET 方法會顯示為 method 的執行個體
>>> type(BitArray.Xor) <type 'method_descriptor'> >>> type(ba.Xor) <type 'builtin_function_or_method'>
[2] | 請注意,對應到 .NET 型別的 Python 型別為 type 的子型別 >>> isinstance(type(ba), type) True >>> type(ba) is type False 這是實作詳情。 |
.NET 型別如同內建型別 (例如 list),且不可變。也就是說,您無法對 .NET 型別新增或刪除描述子
>>> del list.append Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: cannot delete attribute 'append' of builtin type 'list' >>> >>> import System >>> del System.DateTime.ToByteArray Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't set attributes of built-in/extension type 'DateTime'
.NET 型別顯示為 Python 類別,而且您可以在 .NET 型別上執行與 Python 類別相同的許多作業。在任何情況下,您可以透過呼叫型別來建立執行個體
>>> from System.Collections import BitArray >>> ba = BitArray(5) # Creates a bit array of size 5
IronPython 也支援執行個體屬性的內嵌初始化。考慮以下兩行
>>> ba = BitArray(5) >>> ba.Length = 10
上述兩行等同於這一行
>>> ba = BitArray(5, Length = 10)
您也可以呼叫 __new__ 方法來建立執行個體
>> ba = BitArray.__new__(BitArray, 5)
.NET 方法顯示為 Python 方法。呼叫 .NET 方法與呼叫 Python 方法相同。
呼叫 .NET 執行個體方法與在 Python 物件上使用屬性表示法來呼叫方法相同
>>> from System.Collections import BitArray >>> ba = BitArray(5) >>> ba.Set(0, True) # call the Set method >>> ba[0] True
IronPython 也支援命名參數
>>> ba.Set(index = 1, value = True) >>> ba[1] True
IronPython 也支援字典參數
>>> args = [2, True] # list of arguments >>> ba.Set(*args) >>> ba[2] True
IronPython 也支援關鍵字參數
>>> args = { "index" : 3, "value" : True } >>> ba.Set(**args) >>> ba[3] True
當參數型別與 .NET 方法預期的參數型別不完全相符時,IronPython 會嘗試轉換參數。IronPython 會使用傳統的 .NET 轉換規則,例如 轉換運算子,以及 IronPython 特有的規則。這個程式片段顯示呼叫 Set(System.Int32, System.Boolean) 方法時,是如何轉換參數的
>>> from System.Collections import BitArray >>> ba = BitArray(5) >>> ba.Set(0, "hello") # converts the second argument to True. >>> ba[0] True >>> ba.Set(1, None) # converts the second argument to False. >>> ba[1] False
詳細的轉換規則請參閱 附錄型別轉換規則。請注意,某些 Python 類型是實作為 .NET 類型,而且此類情況不需要進行轉換。請參閱 內建類型對應 來了解對應關係。
支援的部分轉換如下
Python 參數類型 | .NET 函式參數類型 |
---|---|
int | System.Int8、System.Int16 |
float | System.Float |
僅含有類型 T 的元素的組 | System.Collections.Generic.IEnumerable<T> |
函式、方法 | System.Delegate 及其任何子類別 |
.NET 支援 方法超載,依據參數數量及參數類型而定。IronPython 程式碼呼叫超載方法時,IronPython 會嘗試在執行時期依據傳遞給方法的參數數量及類型,以及任何關鍵字參數的名稱,選取其中一個超載。在多數情況下,都會選取預期的超載。倘若參數類型與其中一個超載簽章完全相符,則很容易選取一個超載
>>> from System.Collections import BitArray >>> ba = BitArray(5) # calls __new__(System.Int32) >>> ba = BitArray(5, True) # calls __new__(System.Int32, System.Boolean) >>> ba = BitArray(ba) # calls __new__(System.Collections.BitArray)
參數類型不一定要與方法簽章完全相符。倘若存在明確的轉換可轉換為其中一個超載簽章,IronPython 會嘗試轉換參數。以下程式碼呼叫 __new__(System.Int32),即使有兩個建構函式會採用一個參數,而且它們都不接受 浮點數 作為參數
>>> ba = BitArray(5.0)
然而,請注意,倘若存在可轉換為多個超載的轉換,IronPython 會引發 TypeError
>>> BitArray((1, 2, 3)) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: Multiple targets could match: BitArray(Array[Byte]), BitArray(Array[bool]), BitArray(Array[int])
倘若要控制被呼叫的超載,可以在 方法 物件上使用 Overloads 方法
>>> int_bool_new = BitArray.__new__.Overloads[int, type(True)] >>> ba = int_bool_new(BitArray, 5, True) # calls __new__(System.Int32, System.Boolean) >>> ba = int_bool_new(BitArray, 5, "hello") # converts "hello" to a System.Boolan >>> ba = int_bool_new(BitArray, 5) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __new__() takes exactly 2 arguments (1 given)
待辦事項 - 使用 Type.MakeByrefType 以陣列、byref 等來為 Overloads 編製索引的範例
有時會希望使用未繫結類別實例方法並傳遞明確 self 物件作為第一個參數來呼叫實例方法。例如,.NET 允許類別宣告一個與基底類型中方法同名的實例方法,但並未覆寫基底方法。請參閱 System.Reflection.MethodAttributes.NewSlot 以取得更多資訊。在這種情況下,使用未繫結類別實例方法語法可讓您精準選擇您希望呼叫的時段
>>> import System >>> System.ICloneable.Clone("hello") # same as : "hello".Clone() 'hello'
未繫結類別實例方法語法會導致虛擬呼叫,並呼叫虛擬方法時段的最衍生實作
>>> s = "hello" >>> System.Object.GetHashCode(s) == System.String.GetHashCode(s) True >>> from System.Runtime.CompilerServices import RuntimeHelpers >>> RuntimeHelpers.GetHashCode(s) == System.String.GetHashCode(s) False
.NET 允許使用不同的名稱來覆寫基底方法實作或介面方法時段。倘若類型實作兩個具有相同名稱的方法介面,這會很有用。這稱為 明確實作介面方法。例如,Microsoft.Win32.RegistryKey 明確實作 System.IDisposable.Dispose
>>> from Microsoft.Win32 import RegistryKey >>> clr.GetClrType(RegistryKey).GetMethod("Flush") #doctest: +ELLIPSIS <System.Reflection.RuntimeMethodInfo object at ... [Void Flush()]> >>> clr.GetClrType(RegistryKey).GetMethod("Dispose") >>>
在這種情況下,倘若沒有模糊性,IronPython 會嘗試使用其簡單名稱公開方法
>>> from Microsoft.Win32 import Registry >>> rkey = Registry.CurrentUser.OpenSubKey("Software") >>> rkey.Dispose()
不過,類型可能還有另一個名稱相同的方法。如此一來,明確實作的方法無法像屬性一般取得。不過,還是可以使用未繫結類別實體方法語法呼叫它
>>> rkey = Registry.CurrentUser.OpenSubKey("Software") >>> System.IDisposable.Dispose(rkey)
呼叫靜態 .NET 方法的方式類似於呼叫 Python 靜態方法
>>> System.GC.Collect()
如同 Python 靜態方法,.NET 靜態方法也可以作為子類型的屬性取得
>>> System.Object.ReferenceEquals is System.GC.ReferenceEquals True
待辦如果子類型有一個名稱相同但簽章不同的靜態方法,會發生什麼事?是否兩個覆寫都會可用?
泛型方法會公開為可以以 type 物件索引的屬性。下述程式碼呼叫 System.Activator.CreateInstance<T>
>>> from System import Activator, Guid >>> guid = Activator.CreateInstance[Guid]()
在許多情況下,類型參數可以根據傳遞給方法呼叫的引數來推斷。考慮下述使用泛型方法的方式 [3]
>>> from System.Collections.Generic import IEnumerable, List >>> list = List[int]([1, 2, 3]) >>> import clr >>> clr.AddReference("System.Core") >>> from System.Linq import Enumerable >>> Enumerable.Any[int](list, lambda x : x < 2) True
搭配泛型類型參數推斷,最後的敘述也可寫成
>>> Enumerable.Any(list, lambda x : x < 2) True
有關詳細規則,請參閱 附錄。
[3] | System.Core.dll 是 .NET 3.0 與更高版本的一部分。 |
Python 語言以值傳遞所有引數。沒有語法可以表示應以參照傳遞引數,就像 C# 和 VB.NET 等 .NET 語言可以透過 ref 和 out 關鍵字一般。IronPython 支援兩種將 ref 或 out 引數傳遞至方法的方式,一種是明確的方式,另一種是非明確的方式。
以非明確的方式傳遞引數時,引數會以一般方式傳遞至方法呼叫,且其(可能)已更新的值會隨一般傳回值(如果有)一起從方法呼叫傳回。這個方式與 Python 的多重傳回值功能很搭。 System.Collections.Generic.Dictionary 有 bool TryGetValue(K key, out value) 方法。它可以用一個引數從 IronPython 呼叫,而呼叫會傳回一個 tuple,其中第一個元素為 boolean,第二個元素為值(或如果第一個元素為 False,則傳回預設值 0.0)
>>> d = { "a":100.1, "b":200.2, "c":300.3 } >>> from System.Collections.Generic import Dictionary >>> d = Dictionary[str, float](d) >>> d.TryGetValue("b") (True, 200.2) >>> d.TryGetValue("z") (False, 0.0)
以明確的方式傳遞引數時,可以傳遞 clr.Reference[T] 的實體作為 ref 或 out 引數,呼叫會設定其 Value 欄位。如果有多個會使用 ref 參數的複寫,則明確的方式會很有用
>>> import clr >>> r = clr.Reference[float]() >>> d.TryGetValue("b", r) True >>> r.Value 200.2
.NET 索引器 公開為 __getitem__ 和 __setitem__。因此,可以使用 Python 索引語法來索引 .NET 集合(以及任何有索引器的類型)
>>> from System.Collections import BitArray >>> ba = BitArray(5) >>> ba[0] False >>> ba[0] = True >>> ba[0] True
索引器可以使用非繫結類別實例方法語法,使用 __getitem__ 和 __setitem__ 呼叫。如果索引器是虛擬的,並且是以明確實作介面方法實作,這將會很有用
>>> BitArray.__getitem__(ba, 0) True
請注意,預設索引器只是一個具有單一引數的屬性(通常稱為 Item)。如果宣告類型使用 DefaultMemberAttribute 將屬性宣告為預設成員,它就會被視為索引器。
請參閱 property-with-parameters 以取得關於非預設索引器的資訊。
.NET 屬性會使用類似於 Python 屬性的方式公開。在內部,.NET 屬性會實作為一對取得和設定屬性的方法,而 IronPython 會視您是要讀取還是寫入屬性,來呼叫適當的方法
>>> from System.Collections import BitArray >>> ba = BitArray(5) >>> ba.Length # calls "BitArray.get_Length()" 5 >>> ba.Length = 10 # calls "BitArray.set_Length()"
若要使用非繫結類別實例方法語法呼叫取得或設定方法,IronPython 會在屬性描述符上公開稱為 GetValue 和 SetValue 的方法。以上的程式碼等同於以下程式碼
>>> ba = BitArray(5) >>> BitArray.Length.GetValue(ba) 5 >>> BitArray.Length.SetValue(ba, 10)
COM 和 VB.NET 支援具有參數的屬性。它們也稱為非預設索引器。C# 不支援宣告或使用具有參數的屬性。
IronPython 支援具有參數的屬性。例如,以上的預設索引器也可以使用以下非預設格式來存取
>>> ba.Item[0] False
.NET 事件會以具有 __iadd__ 和 __isub__ 方法的物件公開,這允許使用 += 和 -= 來訂閱和取消訂閱事件。以下程式碼會示範如何使用 += 來訂閱 Python 函式至事件,並使用 -= 來取消訂閱
>>> from System.IO import FileSystemWatcher >>> watcher = FileSystemWatcher(".") >>> def callback(sender, event_args): ... print event_args.ChangeType, event_args.Name >>> watcher.Created += callback >>> watcher.EnableRaisingEvents = True >>> import time >>> f = open("test.txt", "w+"); time.sleep(1) Created test.txt >>> watcher.Created -= callback >>> >>> # cleanup >>> import os >>> f.close(); os.remove("test.txt")
您也可以使用繫結方法進行訂閱
>>> watcher = FileSystemWatcher(".") >>> class MyClass(object): ... def callback(self, sender, event_args): ... print event_args.ChangeType, event_args.Name >>> o = MyClass() >>> watcher.Created += o.callback >>> watcher.EnableRaisingEvents = True >>> f = open("test.txt", "w+"); time.sleep(1) Created test.txt >>> watcher.Created -= o.callback >>> >>> # cleanup >>> f.close(); os.remove("test.txt")
您也可以明確建立 委派 實例,以訂閱事件。否則,IronPython 會自動為您執行。 [4]
>>> watcher = FileSystemWatcher(".") >>> def callback(sender, event_args): ... print event_args.ChangeType, event_args.Name >>> from System.IO import FileSystemEventHandler >>> delegate = FileSystemEventHandler(callback) >>> watcher.Created += delegate >>> watcher.EnableRaisingEvents = True >>> import time >>> f = open("test.txt", "w+"); time.sleep(1) Created test.txt >>> watcher.Created -= delegate >>> >>> # cleanup >>> f.close(); os.remove("test.txt")
[4] | 建立明確委派的唯一優點是它使用較少的記憶體。如果您訂閱很多事件,而且注意到使用大量的 System.WeakReference 物件,您應該考慮這麼做。 |
IronPython 支援使用 type 物件對 System.Array 進行索引編製,以存取一維強型別陣列
>>> System.Array[int] <type 'Array[int]'>
IronPython 也新增一個 __new__ 方法,可以接受 IList<T> 來初始化陣列。這允許使用 Python list 文字來初始化 .NET 陣列
>>> a = System.Array[int]([1, 2, 3])
此外,IronPython 公開 __getitem__ 和 __setitem__,允許使用 Python 索引編製語法,來為陣列物件編製索引
>>> a[2] 3
請注意,索引編製語法會產生 Python 語意。如果您使用負值編製索引,這將會從陣列結尾開始編製索引,而 .NET 索引編製(透過呼叫以下 GetValue 示範)會引發 System.IndexOutOfRangeException 例外狀況
>>> a.GetValue(-1) Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: Index was outside the bounds of the array. >>> a[-1] 3
類似地,也支援切片
>>> a[1:3] Array[int]((2, 3))
代辦事項
raise 可以引發 Python 例外以及 .NET 例外
>>> raise ZeroDivisionError() Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError >>> import System >>> raise System.DivideByZeroException() Traceback (most recent call last): File "<stdin>", line 1, in <module> ZeroDivisionError: Attempted to divide by zero.
except 關鍵字可以擷取 Python 例外以及 .NET 例外
>>> try: ... import System ... raise System.DivideByZeroException() ... except System.DivideByZeroException: ... print "This line will get printed..." ... This line will get printed... >>>
IronPython 在 .NET 例外機制之上實作 Python 例外機制。如此一來,Python 程式碼拋出的 Python 例外便可以被非 Python 程式碼擷取,反之亦然。不過,Python 例外物件必須表現得像 Python 使用者物件,而不是內建類型。舉例來說,Python 程式碼可以對 Python 例外物件設定任意屬性,但無法對 .NET 例外物件設定
>>> e = ZeroDivisionError() >>> e.foo = 1 # this works >>> e = System.DivideByZeroException() >>> e.foo = 1 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'DivideByZeroException' object has no attribute 'foo'
為了支援這兩種不同的觀點,IronPython 會建立一組物件,一個 Python 例外物件和一個 .NET 例外物件,其中 Python 型式和 .NET 例外型式具有一對一的獨特對應,如下表所定義。兩個物件彼此都知道對方。當 Python 程式碼執行 raise 陳述式時,由 IronPython 執行階段實際拋出的 .NET 例外物件。當 Python 程式碼使用 except 關鍵字來擷取 Python 例外時,則會使用 Python 例外物件。不過,如果例外是由呼叫 Python 程式的 C# (例如) 程式碼所擷取,則 C# 程式碼自然會擷取 .NET 例外物件。
可以透過使用 clsException 屬性 (如果模組已執行 import clr) 來存取與 Python 例外物件相符的 .NET 例外物件
>>> import clr >>> try: ... 1/0 ... except ZeroDivisionError as e: ... pass >>> type(e) <type 'exceptions.ZeroDivisionError'> >>> type(e.clsException) <type 'DivideByZeroException'>
IronPython 還可以存取與 .NET 例外物件相符的 Python 例外物件[5],雖然這點並無對使用者公開[6]。
[5] | IronPython 執行階段) 可以透過 System.Exception.Data 屬性存取與 .NET 例外物件相符的 Python 例外物件。請注意,這是實作細節,可能會有所變更 >>> e.clsException.Data["PythonExceptionInfo"] #doctest: +ELLIPSIS <IronPython.Runtime.Exceptions.PythonExceptions+ExceptionDataWrapper object at ...> |
[6] | ... 只能透過 DLR Hosting API ScriptEngine.GetService<ExceptionOperations>().GetExceptionMessage 存取 |
Python 例外 | .NET 例外 | |
---|---|---|
Exception | System.Exception | |
SystemExit | IP.O.SystemExit | |
StopIteration | System.InvalidOperationException 子型 | |
StandardError | System.SystemException | |
KeyboardInterrupt | IP.O.KeyboardInterruptException | |
ImportError | IP.O.PythonImportError | |
EnvironmentError | IP.O.PythonEnvironmentError | |
IOError | System.IO.IOException | |
OSError | S.R.InteropServices.ExternalException | |
WindowsError | System.ComponentModel.Win32Exception | |
EOFError | System.IO.EndOfStreamException | |
RuntimeError | IP.O.RuntimeException | |
NotImplementedError | System.NotImplementedException | |
NameError | IP.O.NameException | |
UnboundLocalError | IP.O.UnboundLocalException | |
AttributeError | System.MissingMemberException | |
SyntaxError | IP.O.SyntaxErrorException (請注意,System.Data 近似相同) | |
IndentationError | IP.O.IndentationErrorException | |
TabError | IP.O.TabErrorException | |
TypeError | Microsoft.Scripting.ArgumentTypeException | |
AssertionError | IP.O.AssertionException | |
查閱錯誤 | IP.O.LookupException | |
索引錯誤 | System.IndexOutOfRangeException | |
關鍵字錯誤 | S.C.G.KeyNotFoundException | |
算術錯誤 | System.ArithmeticException | |
溢位錯誤 | System.OverflowException | |
除數為零錯誤 | System.DivideByZeroException | |
浮點運算錯誤 | IP.O.PythonFloatingPointError | |
值錯誤 | 參數錯誤 | |
Unicode 錯誤 | IP.O.UnicodeException | |
Unicode 編碼錯誤 | System.Text.EncoderFallbackException | |
Unicode 解碼錯誤 | System.Text.DecoderFallbackException | |
Unicode 轉換錯誤 | IP.O.UnicodeTranslateException | |
參照錯誤 | IP.O.ReferenceException | |
系統錯誤 | IP.O.PythonSystemError | |
記憶體錯誤 | System.OutOfMemoryException | |
警告 | System.ComponentModel.WarningException | |
用戶警告 | IP.O.PythonUserWarning | |
棄用警告 | IP.O.PythonDeprecationWarning | |
待棄用警告 | IP.O.PythonPendingDeprecationWarning | |
語法警告 | IP.O.PythonSyntaxWarning | |
溢位警告 | IP.O.PythonOverflowWarning | |
執行階段警告 | IP.O.PythonRuntimeWarning | |
未來警告 | IP.O.PythonFutureWarning |
假定 提高 會同時產生 Python 例外物件和 .NET 例外物件,以及 救援 可以捕捉 Python 例外和 .NET 例外,那麼 救援 關鍵字會使用哪個例外物件呢?答案是 救援 子句中使用的類型。例如,如果 救援 子句使用 Python 例外,那麼會使用 Python 例外物件。如果 救援 子句使用 .NET 例外,那麼會使用 .NET 例外物件。
以下範例說明1/0會同時產生兩個物件,以及它們如何相互連結。例外會先作為 .NET 例外捕捉。.NET 例外會再次引發,但是會作為 Python 例外捕捉
>>> import System >>> try: ... try: ... 1/0 ... except System.DivideByZeroException as e1: ... raise e1 ... except ZeroDivisionError as e2: ... pass >>> type(e1) <type 'DivideByZeroException'> >>> type(e2) <type 'exceptions.ZeroDivisionError'> >>> e2.clsException is e1 True
Python 使用者定義例外會映射到 System.Exception。如果非 Python 程式碼捕捉 Python 使用者定義例外,這將會是 System.Exception 的實例,而且無法存取例外詳細資料
>>> # since "Exception" might be System.Exception after "from System import *" >>> if "Exception" in globals(): del Exception >>> class MyException(Exception): ... def __init__(self, value): ... self.value = value ... def __str__(self): ... return repr(self.value) >>> try: ... raise MyException("some message") ... except System.Exception as e: ... pass >>> clr.GetClrType(type(e)).FullName 'System.Exception' >>> e.Message 'Python Exception: MyException'
在這種情況下,非 Python 程式碼可以使用 ScriptEngine.GetService<ExceptionOperations>().GetExceptionMessage DLR Hosting API 來取得例外訊息。
.NET 列舉類型是 System.Enum 的子類型。列舉類型的列舉值以類別屬性公開
print System.AttributeTargets.All # access the value "All"
IronPython 也支援對列舉值使用位元運算子
>>> import System >>> System.AttributeTargets.Class | System.AttributeTargets.Method <enum System.AttributeTargets: Class, Method>
Python 預期所有可變動數值都代表參照類型。另一方面,.NET 引進了值類型的概念,這些類型通常會複製,而不是參照。特別地,傳回值類型的 .NET 方法和屬性會永遠傳回一個副本。
從 Python 程式設計師的角度來看,這可能會造成混淆,因為針對此類值類型的欄位進行後續更新時,會在本地端副本執行,而不是在最初提供值類型的任何圍繞物件內執行。
儘管大多數 .NET 值型別被設計成不可變,而 .NET 設計指南建議值型別不可變,.NET 並未強制執行,因此確實有些 .NET 值型別可變。待辦事項 - 範例。
例如,取用下列 C# 定義
struct Point { # Poorly defined struct - structs should be immutable public int x; public int y; } class Line { public Point start; public Point end; public Point Start { get { return start; } } public Point End { get { return end; } } }
如果 line 是參考型別 Line 的執行個體,則 Python 程式員可能會預期 "line.Start.x = 1" 設定該直線起點的 x 座標。實際上,屬性 Start 傳回 Point 值型別的複本,更新的動作也是對該複本進行
print line.Start.x # prints ‘0’ line.Start.x = 1 print line.Start.x # still prints ‘0’
這項行為微妙且容易產生混淆,因此 C# 會在撰寫類似的程式碼 (嘗試修改剛從屬性呼叫傳回的值型別的欄位) 時產生編譯時期錯誤。
更糟的是,當嘗試透過 Line 公開的起始欄位 (亦即 “`line.start.x = 1`”) 直接修改值型別時,IronPython 仍會更新 Point 結構的區域複本。這是因為 Python 的結構為 “foo.bar” 始終會產生可變的數值:在上述情況下,“line.start” 需要傳回完整的值型別,這反過來會隱含複本作業。
反之,C# 會解釋 “`line.start.x = 1`” 陳述式的完整性,並為 “line.start” 部分實際給出值型別參考,反過來可以用來設定 “x” 欄位。
這凸顯了兩種語言之間的語意差異。在 Python 中,“line.start.x = 1” 和 “foo = line.start; foo.x = 1” 在語意上等效。在 C# 中則不一定。
所以總結起來:對 Python 程式員而言,更新嵌入在物件中的值型別會讓那些更新靜默遺失,而相同的語法在 C# 中會產生預期的語意。對於從 .NET 屬性傳回的值型別更新,也會顯示為成功更新了區域複本,而且不會像在 C# 中那樣產生錯誤。這兩個問題很容易變成大型應用程式中難以追蹤的微妙錯誤根源。
為了防止意外更新值型別的區域複本,同時儘可能保持 Python 化和對世界的統一觀,IronPython 不允許直接更新值型別欄位,並會引發 ValueError
>>> line.start.x = 1 #doctest: +SKIP Traceback (most recent call last): File , line 0, in input##7 ValueError Attempt to update field x on value type Point; value type fields can not be directly modified
這讓值型別“大致上”不可變;仍可透過值型別本身的實體方法執行更新。
IronPython 無法直接使用 System.MarshalByRefObject 執行個體。IronPython 在執行期間會使用反射來判斷如何存取物件。然而,System.MarshalByRefObject 執行個體不支援反射。
可以使用 未繫結-類別-執行個體-方法 語法來呼叫此類代理物件中的方法。
Python 函式和繫結式實例方法可以轉換為委派
>>> from System import EventHandler, EventArgs >>> def foo(sender, event_args): ... print event_args >>> d = EventHandler(foo) >>> d(None, EventArgs()) #doctest: +ELLIPSIS <System.EventArgs object at ... [System.EventArgs]>
IronPython 也允許 Python 函式或方法的簽章與委派簽章不同(儘管相容)。例如,Python 函式可以使用關鍵字參數
>>> def foo(*args): ... print args >>> d = EventHandler(foo) >>> d(None, EventArgs()) #doctest: +ELLIPSIS (None, <System.EventArgs object at ... [System.EventArgs]>)
如果委派的傳回類型為空,IronPython 也允許 Python 函式傳回任何類型的傳回值,且只會忽略傳回值
>>> def foo(*args): ... return 100 # this return value will get ignored >>> d = EventHandler(foo) >>> d(None, EventArgs())
如果傳回值不同,IronPython 將嘗試轉換它
>>> def foo(str1, str2): ... return 100.1 # this return value will get converted to an int >>> d = System.Comparison[str](foo) >>> d("hello", "there") 100
待處理事項 - 具有 out/ref 參數的委派
使用 class 支援 .NET 型別和介面的子類別。.NET 型別和介面可以使用為 class 建構中子類別的其中一種
>>> class MyClass(System.Attribute, System.ICloneable, System.IComparable): ... pass
.NET 不支援多重繼承,但 Python 支援。IronPython 允許使用多個 Python 類別做為子類別,也允許使用多個 .NET 介面,但子類別集中只能有一個 .NET 類別(System.Object 除外)
>>> class MyPythonClass1(object): pass >>> class MyPythonClass2(object): pass >>> class MyMixedClass(MyPythonClass1, MyPythonClass2, System.Attribute): ... pass
類別的實例確實繼承自指定的 .NET 基底類別。這很重要,因為這表示使用靜態型別的 .NET 程式碼可以使用 .NET 型別存取物件。下列程式片段使用反射來顯示該物件可以傳回至 .NET 子類別
>>> class MyClass(System.ICloneable): ... pass >>> o = MyClass() >>> import clr >>> clr.GetClrType(System.ICloneable).IsAssignableFrom(o.GetType()) True
請注意,Python 類別並未真正繼承自 .NET 子類別。請參閱 類型對應。
基底類別方法可以藉由定義具有相同名稱的 Python 方法來覆寫
>>> class MyClass(System.ICloneable): ... def Clone(self): ... return MyClass() >>> o = MyClass() >>> o.Clone() #doctest: +ELLIPSIS <MyClass object at ...>
IronPython 要求您在類別宣告中提供介面方法的實作。方法查詢在存取該方法時會動態執行。在此,如果未定義該方法,我們會看到提出 AttributeError
>>> class MyClass(System.ICloneable): pass >>> o = MyClass() >>> o.Clone() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'MyClass' object has no attribute 'Clone'
Python 不支援方法重載。類別只可以有一個具有特定名稱的方法。因此,您無法覆寫 .NET 子類別的特定方法重載。反之,您需要使用定義接受任意引數清單的函式(請參閱 _tut-arbitraryargs),然後再透過檢查引數的型別來確定呼叫的方法重載
>>> import clr >>> import System >>> StringComparer = System.Collections.Generic.IEqualityComparer[str] >>> >>> class MyComparer(StringComparer): ... def GetHashCode(self, *args): ... if len(args) == 0: ... # Object.GetHashCode() called ... return 100 ... ... if len(args) == 1 and type(args[0]) == str: ... # StringComparer.GetHashCode() called ... return 200 ... ... assert("Should never get here") ... >>> comparer = MyComparer() >>> getHashCode1 = clr.GetClrType(System.Object).GetMethod("GetHashCode") >>> args = System.Array[object](["another string"]) >>> getHashCode2 = clr.GetClrType(StringComparer).GetMethod("GetHashCode") >>> >>> # Use Reflection to simulate a call to the different overloads >>> # from another .NET language >>> getHashCode1.Invoke(comparer, None) 100 >>> getHashCode2.Invoke(comparer, args) 200
注意
可能無法確定確切呼叫的重載,例如,如果 None 傳入為引數。
由於引數永遠都是以傳值方式傳遞,因此 Python 沒有用於指定方法參數是否以傳址方式傳遞的語法。在覆寫具有 ref 或 out 參數的 .NET 方法時,ref 或 out 參數會收到 clr.Reference[T] 實例。通過讀取 Value 屬性來存取輸入引數值,並通過設定 Value 屬性來指定結果值
>>> import clr >>> import System >>> StrFloatDictionary = System.Collections.Generic.IDictionary[str, float] >>> >>> class MyDictionary(StrFloatDictionary): ... def TryGetValue(self, key, value): ... if key == "yes": ... value.Value = 100.1 # set the *out* parameter ... return True ... else: ... value.Value = 0.0 # set the *out* parameter ... return False ... # Other methods of IDictionary not overriden for brevity ... >>> d = MyDictionary() >>> # Use Reflection to simulate a call from another .NET language >>> tryGetValue = clr.GetClrType(StrFloatDictionary).GetMethod("TryGetValue") >>> args = System.Array[object](["yes", 0.0]) >>> tryGetValue.Invoke(d, args) True >>> args[1] 100.1
在覆寫通用方法時,型別參數會作為引數傳入。考量以下通用方法宣告
// csc /t:library /out:convert.dll convert.cs public interface IMyConvertible { T1 Convert<T1, T2>(T2 arg); }
下列程式碼覆寫通用方法 Convert
>>> import clr >>> clr.AddReference("convert.dll") >>> import System >>> import IMyConvertible >>> >>> class MyConvertible(IMyConvertible): ... def Convert(self, t2, T1, T2): ... return T1(t2) >>> >>> o = MyConvertible() >>> # Use Reflection to simulate a call from another .NET language >>> type_params = System.Array[System.Type]([str, float]) >>> convert = clr.GetClrType(IMyConvertible).GetMethod("Convert") >>> convert_of_str_float = convert.MakeGenericMethod(type_params) >>> args = System.Array[object]([100.1]) >>> convert_of_str_float.Invoke(o, args) '100.1'
注意
泛用方法會收到關於方法簽名的資訊,但是一般的函式覆寫則不會。原因是 .NET 不允許一般函式覆寫以回傳型態不同,而且通常可以根據引數值來判斷引數型態。但是,在泛用方法中,其中一個型態參數只能用作回傳型態。在這種情況下,沒辦法決定型態參數。
當您從 Python 呼叫函式,而該函式基底型別覆寫了 .NET 函式,呼叫會以常規 Python 呼叫執行。引數不會進行轉換,也不會以任何方式修改,例如用 clr.Reference 包起來。因此,如果該函式被另一種語言覆寫,這個呼叫可能會需要以不同的方式編寫。例如,嘗試從 overriding-ref-args 區段呼叫 MyDictionary 型別上的 TryGetValue,如下所示,會產生 TypeError,而類似的呼叫可以用 System.Collections.Generic.Dictionary[str, float] 執行
>>> result, value = d.TryGetValue("yes") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: TryGetValue() takes exactly 3 arguments (2 given)
.NET 屬性由一對 .NET 函式作為後盾,用於讀取和寫入屬性。C# 編譯器會自動將它們命名為 get_<PropertyName> 和 set_<PropertyName>。然而,.NET 本身不要求這些函式有任何特定的命名模式,而且這些名稱會儲存在與屬性定義相關聯的元資料中。名稱可以使用 System.Reflection.PropertyInfo 類別的 GetGetMethod 和 GetSetMethods 存取
>>> import clr >>> import System >>> StringCollection = System.Collections.Generic.ICollection[str] >>> prop_info = clr.GetClrType(StringCollection).GetProperty("Count") >>> prop_info.GetGetMethod().Name 'get_Count' >>> prop_info.GetSetMethod() # None because this is a read-only property >>>
覆寫虛擬屬性需要定義一個 Python 函式,其名稱與底層 getter 或 setter .NET 函式相同
>>> >>> class MyCollection(StringCollection): ... def get_Count(self): ... return 100 ... # Other methods of ICollection not overriden for brevity >>> >>> c = MyCollection() >>> # Use Reflection to simulate a call from another .NET language >>> prop_info.GetGetMethod().Invoke(c, None) 100
事件有底層函式,可以使用 EventInfo.GetAddMethod 和 EventInfo.GetRemoveMethod 取得
>>> from System.ComponentModel import IComponent >>> import clr >>> event_info = clr.GetClrType(IComponent).GetEvent("Disposed") >>> event_info.GetAddMethod().Name 'add_Disposed' >>> event_info.GetRemoveMethod().Name 'remove_Disposed'
若要覆寫事件,您需要定義具有底層函式名稱的方法
>>> class MyComponent(IComponent): ... def __init__(self): ... self.dispose_handlers = [] ... def Dispose(self): ... for handler in self.dispose_handlers: ... handler(self, EventArgs()) ... ... def add_Disposed(self, value): ... self.dispose_handlers.append(value) ... def remove_Disposed(self, value): ... self.dispose_handlers.remove(value) ... # Other methods of IComponent not implemented for brevity >>> >>> c = MyComponent() >>> def callback(sender, event_args): ... print event_args >>> args = System.Array[object]((System.EventHandler(callback),)) >>> # Use Reflection to simulate a call from another .NET language >>> event_info.GetAddMethod().Invoke(c, args) >>> >>> c.Dispose() #doctest: +ELLIPSIS <System.EventArgs object at ... [System.EventArgs]>
.NET 建構函式可以覆寫。若要呼叫特定的基底型別建構函式覆寫,您需要定義一個 __new__ 函式(而非 __init__),並在 .NET 基底型別上呼叫 __new__。下列範例顯示 System.Exception 的子型別如何根據接收到的引數,選擇要呼叫的基底建構函式覆寫
>>> import System >>> class MyException(System.Exception): ... def __new__(cls, *args): ... # This could be implemented as: ... # return System.Exception.__new__(cls, *args) ... # but is more verbose just to make a point ... if len(args) == 0: ... e = System.Exception.__new__(cls) ... elif len(args) == 1: ... message = args[0] ... e = System.Exception.__new__(cls, message) ... elif len(args) == 2: ... message, inner_exception = args ... if hasattr(inner_exception, "clsException"): ... inner_exception = inner_exception.clsException ... e = System.Exception.__new__(cls, message, inner_exception) ... return e >>> e = MyException("some message", IOError())
通常,IronPython 不允許存取受保護的成員(除非您使用 private-binding)。例如,存取 MemberwiseClone 會造成 TypeError,因為它是一個受保護的方法
>>> import clr >>> import System >>> o = System.Object() >>> o.MemberwiseClone() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: cannot access protected member MemberwiseClone without a python subclass of object
IronPython 允許 Python 子型別存取 .NET 基底型別的受保護成員。但是,Python 沒有強制執行任何可存取性規則。而且,可以動態地新增和刪除類別中的函式。因此,IronPython 沒有嘗試保護存取 .NET 子型別的 受保護 成員。相反的,它總是讓受保護成員與公開成員一樣可用
>>> class MyClass(System.Object): ... pass >>> o = MyClass() >>> o.MemberwiseClone() #doctest: +ELLIPSIS <MyClass object at ...>
在 Python 中的類別定義並不會直接對應到唯一的 .NET 型別。這是因為類別的語意在 Python 和 .NET 中有所不同。例如,在 Python 中,只要指定型別物件上的__bases__屬性,就可以變更基本型別。但是,在 .NET 型別中則不行。因此,IronPython 在實作 Python 類別時並不會直接對應到 .NET 型別。IronPython 的確會使用一些.NET 型別,但是其成員並不會完全符合 Python 屬性。相反地,Python 類別儲存在名為.class的 .NET 資料欄位中,而 Python 實例屬性則儲存在名為.dict的字典中,後者被儲存在 .NET 資料欄位中 [7]
>>> import clr >>> class MyClass(object): ... pass >>> o = MyClass() >>> o.GetType().FullName #doctest: +ELLIPSIS 'IronPython.NewTypes.System.Object_...' >>> [field.Name for field in o.GetType().GetFields()] ['.class', '.dict', '.slots_and_weakref'] >>> o.GetType().GetField(".class").GetValue(o) == MyClass True >>> class MyClass2(MyClass): ... pass >>> o2 = MyClass2() >>> o.GetType() == o2.GetType() True
請另見型別系統整合(型別和 System.Type)
[7] | 這些資料欄位名稱是實作的詳細資料,可能會變更。 |
有時需要控制為 Python 類別產生的 .NET 型別。這是因為某些 .NET API 期望使用者定義具有特定屬性和成員的 .NET 型別。例如,若要定義 pinvoke 方法,使用者需要定義一個 .NET 型別,其中包含標記為DllImportAttribute的 .NET 方法,而且 .NET 方法的簽章必須明確描述目標平台的方法。
從 IronPython 2.6 開始,IronPython 支援一個低層級的勾子,可以自訂與 Python 類別相對應的 .NET 型別。如果 Python 類別的 metaclass 具有稱為__clrtype__的屬性,就會呼叫此屬性來產生 .NET 型別。這可以讓使用者控制產生的 .NET 型別的詳細資料。但是,這是一個低層級的勾子,使用者預期會以此為基礎進行建置。
可以在 IronPython 網站中取得 ClrType 範例,內容說明如何建置於 __clrtype__ 勾子上。
像 C# 和 VB.Net 等靜態型別語言可以編譯為組件,然後讓其他 .NET 程式碼使用。但是,會使用ipy.exe動態執行 IronPython 程式碼。如果您想從其他 .NET 程式碼執行 Python 程式碼,有多種方法可以做到。
DLR Hosting API允許 .NET 應用程式內嵌 DLR 語言(像是 IronPython 和 IronRuby),載入和執行 Python 和 Ruby 程式碼,以及存取 Python 或 Ruby 程式碼建立的物件。
pyc 範例可用於將 IronPython 程式碼編譯為組件。此範例建立於clr-CompileModules之上。然後可以使用Python-ImportModule在組件中載入和執行。但是,請注意組件中的 MSIL 不是CLS 相容的,而且無法直接從其他 .NET 語言存取。
從 .NET 4.0 開始,C# 和 VB.Net 支援使用 dynamic 關鍵字存取 IronPython 物件。這樣可以更輕鬆地存取 IronPython 物件。請注意,你需要使用 hosting-apis 來載入 IronPython 程式碼並取得 root 物件。
類型系統整合。
清單理解適用於實作 IList 的任何 .NET 類型
with 可搭配任何 System.Collections.IEnumerable 或 System.Collections.Generic.IEnumerable<T> 使用
pickle 和 ISerializable
在 .NET 類型和成員上使用 __doc__
__doc__ 使用 XML 註解(若有)。XML 註解檔案會在 TODO 時安裝。因此,可以使用 help
>>> help(System.Collections.BitArray.Set) #doctest: +NORMALIZE_WHITESPACE Help on method_descriptor: Set(...) Set(self, int index, bool value) Sets the bit at a specific position in the System.Collections.BitArray to the specified value. <BLANKLINE> index: The zero-based index of the bit to set. <BLANKLINE> value: The Boolean value to assign to the bit.
若沒有 XML 註解檔案,則 IronPython 會透過反映類型或成員來產生文件。
>>> help(System.Collections.Generic.List.Enumerator.Current) #doctest: +NORMALIZE_WHITESPACE Help on getset descriptor System.Collections.Generic in mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.Enumerator.Current: <BLANKLINE> Current Get: T Current(self)
import clr 在一些 Python 類型上公開額外的功能,讓 .NET 功能可供存取
IronPython 也為 .NET 類型新增擴充,讓它們更具備 Python 的特質。下列執行個體方法會公開在 .NET 物件(以及在明確提及的情況下,公開在 .NET 類別)上
有 op_Implicit 的類型
有 op_Explicit 的類型
從 .NET 類別或介面繼承的類型
.NET 基本類型 |
合成的 Python 方法 |
---|---|
System.Object |
所有 object 方法,例如 __class__, __str__, __hash__, __setattr__ |
System.IDisposable |
__enter__, __exit__ |
System.Collections.IEnumerator |
next |
System.Collections.ICollection System.Collections.Generic.ICollection<T> |
__len__ |
System.Collections.IEnumerable System.Collections.Generic.IEnumerable<T> System.Collections.IEnumerator System.Collections.Generic.IEnumerator<T> |
__iter__ |
System.IFormattable |
__format__ |
System.Collections.IDictionary System.Collections.Generic.IDictionary<TKey, TValue> System.Collections.Generic.ICollection<T> System.Collections.Generic.IList<T> System.Collections.IEnumerable System.Collections.Generic.IEnumerable<T> System.Collections.IEnumerator System.Collections.Generic.IEnumerator<T> |
__contains__ |
System.Array |
|
System.Delegate |
|
System.Enum |
__or__ TODO ? |
有 .NET 操作員方法名稱的類型
.NET 操作員方法 |
合成的 Python 方法 |
---|---|
op_Addition, Add |
__add__ |
Compare |
__cmp__ |
get_<Name> [8] |
__getitem__ |
set_<Name> [9] |
__setitem__ |
[8] | 其中此類型同時具有 <Name> 屬性,以及 <Name> 的 DefaultMemberAttribute |
[9] | 其中此類型同時具有 <Name> 屬性,以及 <Name> 的 DefaultMemberAttribute |
TODO - 這目前僅從 IronRuby 複製而來,且已知是不正確的
物件相等性和雜湊是物件的基本屬性。使用 Python API 來比較和雜湊物件分別為 __eq__ (以及 __ne__ )和 __hash__ 。CLR API 分別為 System.Object.Equals 和 System.Object.GetHashCode。IronPython 會自動對這兩個概念進行對應,讓 Python 物件能夠從非 Python .NET 程式碼比較並雜湊,且 __eq__ 和 __hash__ 也可以在 Python 程式碼中用於非 Python 物件。
當 Python 程式碼呼叫 __eq__ 和 __hash__ 時
当静态 MSIL 程式碼呼叫 System.Object.Equals 和 System.Object.GetHashCode 時
CLR 期望 System.Object.GetHashCode 對於特定物件總是傳回相同值。如果不保持此不變常數,則將物件用作 System.Collections.Generic.Dictionary<K,V> 中的索引鍵會產生錯誤行為。Python 允許 __hash__ 傳回不同的結果,並仰賴使用者處理將物件當作雜湊中的索引鍵來使用的情境。Python 與 CLR 的相等性和雜湊概念以上述方式對應,表示處理 Python 物件的 CLR 程式碼必須了解此問題。如果靜態 MSIL 程式碼將 Python 物件當作 Dictionary<K,V> 中的索引鍵,可能會發生預期之外的行為。
為了降低在使用一般 Python 類型時發生此狀況的機率,IronPython 沒有將 __hash__ 對應到陣列和雜湊的 GetHashCode。對於其他 Python 類別,如果 Python 類別是可以變動的,但需要用作 Dictionary<K,V> 中的索引鍵,則使用者可以提供 __eq__、 Equals、__hash__ 和 GetHashCode 的個別實作。
在 Python 物件上呼叫 ToString 會呼叫預設 System.Object.ToString 實作,即使 Python 類型定義 __str__
>>> class MyClass(object): ... def __str__(self): ... return "__str__ result" >>> o = MyClass() >>> # Use Reflection to simulate a call from another .NET language >>> o.GetType().GetMethod("ToString").Invoke(o, None) #doctest: +ELLIPSIS 'IronPython.NewTypes.System.Object_...'
所有的 Python 使用者類型都有 __repr__ 和 __str__
>>> class MyClass(object): ... pass >>> o = MyClass() >>> o.__repr__() #doctest: +ELLIPSIS '<MyClass object at ...>' >>> o.__str__() #doctest: +ELLIPSIS 'IronPython.NewTypes.System.Object_...' >>> str(o) #doctest: +ELLIPSIS '<MyClass object at ...>'
對於未覆寫 ToString 的 .NET 類型,IronPython 會提供 __repr__ 和 __str__ 方法,其行為類似於 Python 使用者類型 [10]
>>> from System.Collections import BitArray >>> ba = BitArray(5) >>> ba.ToString() # BitArray inherts System.Object.ToString() 'System.Collections.BitArray' >>> ba.__repr__() #doctest: +ELLIPSIS '<System.Collections.BitArray object at ... [System.Collections.BitArray]>' >>> ba.__str__() #doctest: +ELLIPSIS '<System.Collections.BitArray object at ... [System.Collections.BitArray]>'
對於有覆寫 ToString 的 .NET 類型,IronPython 會將 ToString 的結果包含在 __repr__ 中,並將 ToString 直接對應到 __str__
>>> e = System.Exception() >>> e.ToString() "System.Exception: Exception of type 'System.Exception' was thrown." >>> e.__repr__() #doctest: +ELLIPSIS "<System.Exception object at ... [System.Exception: Exception of type 'System.Exception' was thrown.]>" >>> e.__str__() #doctest: "System.Exception: Exception of type 'System.Exception' was thrown."
對於覆寫 ToString 的 Python 類型,__str__ 已對應到 ToString 覆寫
>>> class MyClass(object): ... def ToString(self): ... return "ToString implemented in Python" >>> o = MyClass() >>> o.__repr__() #doctest: +ELLIPSIS '<MyClass object at ...>' >>> o.__str__() 'ToString implemented in Python' >>> str(o) #doctest: +ELLIPSIS '<MyClass object at ...>'
[10] | __str__ 處理中有一些不一致的地方,可追蹤 http://ironpython.codeplex.com/WorkItem/View.aspx?WorkItemId=24973 |
IronPython 支援存取 OleAutomation 物件(支援 dispinterface 的 COM 物件)。
IronPython 不支援 win32ole 函式庫,但使用 win32ole 的 Python 程式只要做一些變更,即可在 IronPython 上執行。
不同的語言都有不同的方式來建立 COM 物件。VBScript 和 VBA 有個稱為 CreateObject 的方法來建立一個 OleAut 物件。JScript 有個稱為 TODO 的方法。IronPython 有多種方法可以達成相同的目的。
第一種方法是使用 System.Type.GetTypeFromProgID 和 System.Activator.CreateInstance 。這個方法可適用於已註冊的任何 COM 物件。
>>> import System >>> t = System.Type.GetTypeFromProgID("Excel.Application") >>> excel = System.Activator.CreateInstance(t) >>> wb = excel.Workbooks.Add() >>> excel.Quit()
第二種方法是使用 clr.AddReferenceToTypeLibrary 來載入 COM 物件的類型庫(如果有的話)。其優點是可以使用類型庫來存取其他命名值,例如常數。
>>> import System >>> excelTypeLibGuid = System.Guid("00020813-0000-0000-C000-000000000046") >>> import clr >>> clr.AddReferenceToTypeLibrary(excelTypeLibGuid) >>> from Excel import Application >>> excel = Application() >>> wb = excel.Workbooks.Add() >>> excel.Quit()
最後,您也可以使用 interop 組件。這可以使用 tlbimp.exe 工具來產生。這種方法的唯一優點是,這是 IronPython 1 建議使用的方法。如果您有使用這種方法編寫的程式碼,而且是為 IronPython 1 所開發,程式碼會繼續執行。
>>> import clr >>> clr.AddReference("Microsoft.Office.Interop.Excel") >>> from Microsoft.Office.Interop.Excel import ApplicationClass >>> excel = ApplicationClass() >>> wb = excel.Workbooks.Add() >>> excel.Quit()
取得對 COM 物件的存取權後,即可像使用其他物件一樣使用它。屬性、方法、預設索引和事件都按照預期一樣動作。
有一個重要的細節值得指出來。IronPython 會嘗試使用 OleAut 物件的類型庫(如果可以找到的話)來進行名稱解析,而這時會存取方法或屬性。這樣做的原因是,IDispatch 介面在屬性和方法呼叫之間的區別不大。這是因為 Visual Basic 6 的語意,其中「excel.Quit」和「excel.Quit()」具有完全相同的語意。然而,IronPython 對屬性和方法之間有明確的區分,而且方法是一等物件。為了讓 IronPython 知道是否應該呼叫 Quit 方法,或僅傳回可呼叫物件,IronPython 需要檢查 typelib。如果沒有提供 typelib,IronPython 會假設它是一個方法。因此,如果 OleAut 物件有一個稱為「prop」的屬性但沒有 typelib,您需要在 IronPython 中撰寫「p = obj.prop()」才能讀取屬性值。
呼叫具有「out」(或 in-out)參數的方法時,如果要在呼叫方法中取得更新的值,則必須明確傳入「clr.Reference」執行個體。請注意,具有 out 參數的 COM 方法不視為自動化友善[11]。JScript 完全不支援 out 參數。如果你確實遇到具有 out 參數的 COM 元件,使用「clr.Reference」不失為一個合理的解決方法
>>> import clr >>> from System import Type, Activator >>> command_type = Type.GetTypeFromProgID("ADODB.Command") >>> command = Activator.CreateInstance(command_type) >>> records_affected = clr.Reference[int]() >>> command.Execute(records_affected, None, None) #doctest: +SKIP >>> records_affected.Value 0
另一個解決方法是運用無繫結類別實例方法語法「outParamAsReturnValue = InteropAssemblyNamespace.IComInterface(comObject)」來利用中斷組件。
[11] | 請注意,Office API 特別有「VARIANT*」參數,但這些方法不會更新 VARIANT 的值。這些方法定義為具有「VARIANT*」參數的唯一原因在於效能,因為傳遞至 VARIANT 的指標會比將 VARIANT 的所有 4 個 DWORD 推送到堆疊上來得快。因此,你只要將這些參數視為「in」參數即可。 |
類型程式庫含有常數名稱。你可以使用 clr.AddReferenceToTypeLibrary 來載入類型程式庫。
IronPython 并不完全支援不支援 dispinterface 的 COM 物件,因為它們看起來像代理物件 [12]。你可以使用無繫結類別方法語法來存取它們。
[12] | IronPython 1 中支援這項功能,但已於版本 2 中移除。 |
使用 ipy.exe 執行 Python 程式碼時,IronPython 的行為類似 Python,且不會進行任何沙箱處理。所有指令碼均以使用者的權限執行。因此,例如執行從網際網路下載的 Python 程式碼可能潛藏風險。
但是,ipy.exe 只是 IronPython 其中一個實體化。IronPython 也可以用於其他情況中,例如嵌入在應用程式中。所有 IronPython 組件都屬於 security-transparent。因此,IronPython 程式碼可以在沙箱中執行,而主機會控制授予給 Python 程式碼的安全權限。這是 IronPython 建立在 .NET 之上的優點之一。
可以透過下列任何一項技術執行 IronPython 程式碼
因此,IronPython 程式碼的呼叫堆疊與類似 C# 和 VB.Net 這類靜態型別語言的堆疊並不相同。使用下列 API 的 .NET 程式碼必須考量如何處理 IronPython 程式碼
有時取得物件的私有成員很有用。例如:在 IronPython 中撰寫 .NET 程式碼的單元測試時,或者使用互動式命令列觀察某些物件的內部運作時。ipy.exe 支援這一點,方法是透過 -X:PrivateBinding` 命令列選項。也可以在主機情況中透過 待辦事項 內容啟用這一點,這需要 IronPython 以 FullTrust 執行。
IronPython 是在 .NET 之上執行 Python 語言的實作。因此,IronPython 使用各種 .NET 型別來實作 Python 型別。通常,您不必考慮這一點。不過,您有時可能必須知道這一點。
Python 型別 | .NET 型別 |
---|---|
object | System.Object |
int | System.Int32 |
long | System.Numeric.BigInteger [13] |
float | System.Double |
str, unicode | System.String |
bool | System.Boolean |
[13] | 這僅在 CLR 4 中成立。在先前的 CLR 版本中,long 是由 IronPython 本身實作。 |
由於某些 Python 內建型別實作成 .NET 型別,因此問題來了,型別是像 Python 型別還是像 .NET 型別那樣運作?答案是預設情況下,型別像 Python 型別那樣運作。不過,如果模組執行 import clr,則型別像 Python 型別和 .NET 型別一樣運作。例如,預設情況下,object' 沒有 System.Object 方法,稱為 GetHashCode
>>> hasattr(object, "__hash__") True >>> # Note that this assumes that "import clr" has not yet been executed >>> hasattr(object, "GetHashCode") #doctest: +SKIP False
不過,只要執行 import clr,object 就會有 __hash__ 和 GetHashCode
>>> import clr >>> hasattr(object, "__hash__") True >>> hasattr(object, "GetHashCode") True
語言整合查詢 (LINQ) 是一組在 .NET 3.5 中加入的功能。由於這比較像情境,而非特定功能,因此我們會先比較哪些情境可以使用 IronPython
LINQ to 物件
Python 的清單推導式提供相似的功能,而且更 Pythonic。因此,建議使用清單推導式本身。
DLinq - 目前不支援。
LINQ 包含多項語言和 .NET 功能,而且 IronPython 對這些不同的功能的支援程度不同
請注意,某些 Python 型別實作成 .NET 型別,而且在這種情況下不需要轉換。有關對應,請參閱 內建型別對應。
Python 參數類型 | .NET 函式參數類型 |
---|---|
int | System.Byte, System.SByte, System.UInt16, System.Int16 |
具有 __int__ 方法的使用者物件 | 和 int 相同 |
大小為 1 的 str 或 unicode | System.Char |
具有 __str__ 方法的使用者物件 | 和 str 相同 |
float | System.Float |
具有 T 型別元素的組元 | System.Collections.Generic.IEnumerable<T> 或 System.Collections.Generic.IList<T> |
函式、方法 | System.Delegate 及其任何子類別 |
具有 K 型別鍵和 V 型別值的字典 | System.Collections.Generic.IDictionary<K,V> |
種類 | System.Type |
待辦事項:這是舊資訊
大致相等於 VB 11.8.1,並具備更佳的縮小轉換
依引數數量區分的適用成員 – 第 1 階段
依引數類型區分的適用成員 – 第 2 階段
更好的成員 (與 C# 7.4.2.2 相同)
**參數型別**: 給定具有 {A1, A1, ..., An} 型別集合的引數清單 A 以及具有 {P1, P2, ..., Pn} 及 {Q1, Q2, ..., Qn} 型別的型別適用參數清單 P 和 Q,若 P 優於 Q,則
參數變更 : 使用原始方法最少轉換的方法會被視為更好的配對。最棒的成員是適用於方法的轉換清單中,與最早規則相符的那個。如果兩個成員使用相同的規則,那會將轉換最少的參數視為最佳。例如,如果多個參數方法有相同擴充格式,那麼在參數擴充格式前有最多參數的方法會被選取
靜態與執行個體方法 : 當比較靜態方法和執行個體方法且兩者都適用的時候,會將跟呼叫慣例相符的方法視為較佳。如果方法在類型物件上未繫結,則偏好靜態方法;然而,如果方法與執行個體繫結,則偏好執行個體方法。
明確執行的介面方法: 在類別上作為公開方法執行的方法會被視為比在宣告類別中屬於非公開的方法,明確執行介面方法來得更好。
一般化方法: 非一般化方法會被視為比一般化方法來得更好。
更好的轉換(與 C# 7.4.2.3 相同)
ExtensibleFoo 的特殊轉換規則:當 Foo 轉換至該類型有適當轉換時,ExtensibleFoo 會轉換至類型。
隱含轉換
縮小轉換(請見 VB 8.9,但對於 Python 來說限制更嚴格)無法證明總是會成功的轉換、明確地已知會損失資訊的轉換,以及跨越不同類型領域(值得縮小標記)的轉換。以下轉換會被分類為縮小轉換
偏好的縮小轉換
<需要從這裡開始編輯>
縮小轉換
下列所有內容都需要明確的轉換
當 C# 方法被 Python 覆寫或委派在 Python 端執行時,轉換為另一方向的規則
此變更變更我們對覆寫方法和委派的 params 和 ref 參數處理方式的規則。
此變更背後的主要考量是向 Python 程式設計人員呈現對 CLS 簽章最直接的反射,特別是在他們的簽章可能會模稜兩可時。對於呼叫具有 ref 參數的方法,我們支援明確的 Reference 物件和隱含的跳過參數。在覆寫時我們想要支援最直接的簽章以移除模稜兩可性。與 params 方法類似,我們同時支援呼叫具有明確 args 陣列的方法或具有 n 個 args 的方法。為移除覆寫時的模稜兩可性,我們只支援明確陣列。
我對此主要考量原則感到十分滿意。對我來說,很討厭的一點是,這些方法現在無法再從 Python 以非明確形式呼叫。例如,如果我有一個方法 void Foo(params object[] args),那麼我會用 Python 方法 Foo(args) 來覆寫它,而不是 Foo(*args)。這意味著,CLS 基底類型的某個方法可以呼叫成 o.Foo(1,2,3),但 Python 子類別必須呼叫成 o.Foo( (1,2,3) )。這有點難看,但我想不出其他相對簡單且清楚的選項,而且我認為這是因為覆寫過載的方法可能會變得相當複雜,我們應該在簡單性方面有所取捨。