内容

IronPython 旨在成为 Python 语言的完全兼容实现。同时,与 CPython 不同的实现的价值在于使 .NET 库生态系统可用。IronPython 通过将 .NET 概念公开为 Python 实体来实现这一点。现有的 Python 语法和新的 Python 库(如 clr)用于使 .NET 功能可用于 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 程序集都有一个唯一的版本号,允许使用给定程序集的特定版本。以下代码将加载随 .NET 2.0 和 .NET 3.5 一起提供的 System.Xml.dll 版本

>>> 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 将不会知道这些程序集。

默认加载的程序集

默认情况下,核心程序集(.NET Framework 上的 mscorlib.dllSystem.dll / .NET Core 上的 System.Private.CoreLib.dll)由 DLR 自动加载。这使您能够开始使用这些程序集(IronPython 本身依赖于这些程序集),而无需调用 clr.AddReference-methods

使用 .NET 类型

加载程序集后,程序集包含的命名空间和类型可以从 IronPython 代码访问。

导入 .NET 命名空间

.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 <namespace> 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)>

加载新程序集时,它们可以向现有的命名空间模块对象添加属性。

相对于 Python 模块的导入优先级

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 成员

.NET 类型作为 Python 类公开。与 Python 类一样,您通常不能使用 from <name> import * 导入 .NET 类型的所有属性

>>> 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 成员

某些 .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 ...>

类型系统统一 (typeSystem.Type)

.NET 使用 System.Type 来表示类型。但是,当你在 Python 代码中访问 .NET 类型时,你得到的是一个 Python type 对象 [2]

>>> from System.Collections import BitArray
>>> ba = BitArray(5)
>>> isinstance(type(ba), type)
True

这允许对 Python 和 .NET 类型进行统一(Pythonic)的视图。例如,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 类型

.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 方法

.NET 方法作为 Python 方法公开。调用 .NET 方法就像调用 Python 方法一样。

调用 .NET 实例方法

调用 .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

有关详细的转换规则,请参阅 appendix-type-conversion-rules。请注意,某些 Python 类型是在 .NET 类型中实现的,在这种情况下不需要转换。有关映射,请参阅 builtin-type-mapping

支持的一些转换包括:

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),即使有两个接受一个参数的构造函数,并且它们都不接受 float 作为参数。

>>> 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])

如果你想控制要调用的确切重载,可以使用 method 对象上的 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)

TODO - 使用数组、byref 等对 Overloads 进行索引的示例,使用 Type.MakeByrefType

使用未绑定的类实例方法

有时需要使用未绑定的类实例方法调用实例方法,并将显式的 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 方法

调用静态 .NET 方法类似于调用 Python 静态方法

>>> System.GC.Collect()

与 Python 静态方法一样,.NET 静态方法也可以作为子类型的属性访问

>>> System.Object.ReferenceEquals is System.GC.ReferenceEquals
True

TODO 如果子类型具有相同名称但签名不同的静态方法会发生什么?两种重载都可用还是不可用?

调用泛型方法

泛型方法被公开为属性,这些属性可以用 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 及更高版本的一部分。

refout 参数

Python 语言通过值传递所有参数。没有语法来指示参数应该像在 .NET 语言(如 C# 和 VB.NET)中通过 refout 关键字一样通过引用传递。IronPython 支持两种将 ref 或 out 参数传递给方法的方式,一种是隐式方式,另一种是显式方式。

在隐式方式中,参数通常传递给方法调用,并且其(可能)更新的值与正常返回值(如果有)一起从方法调用返回。这与 Python 的多个返回值功能很好地结合在一起。 System.Collections.Generic.Dictionary 具有一个方法 bool TryGetValue(K key, out value)。它可以从 IronPython 中只用一个参数调用,并且调用返回一个 元组,其中第一个元素是布尔值,第二个元素是值(如果第一个元素是 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)

在显式方式中,您可以为 ref 或 out 参数传递 clr.Reference[T] 的实例,并且其 Value 字段将通过调用设置。如果有多个带有 ref 参数的重载,则显式方式很有用

>>> import clr
>>> r = clr.Reference[float]()
>>> d.TryGetValue("b", r)
True
>>> r.Value
200.2

扩展方法

扩展方法 目前不受 IronPython 的原生支持。因此,它们不能像实例方法一样调用。相反,它们必须像静态方法一样调用。

访问 .NET 索引器

.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

非默认 .NET 索引器

请注意,默认索引器只是一个属性(通常称为 Item),带有一个参数。如果声明类型使用 DefaultMemberAttribute 将属性声明为默认成员,则它被视为索引器。

有关非默认索引器的信息,请参见 property-with-parameters

访问 .NET 属性

.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()"

要使用未绑定的类实例方法语法调用 get 或 set 方法,IronPython 在属性描述符上公开名为 GetValueSetValue 的方法。上面的代码等效于以下代码

>>> ba = BitArray(5)
>>> BitArray.Length.GetValue(ba)
5
>>> BitArray.Length.SetValue(ba, 10)

带参数的属性

COM 和 VB.NET 支持带参数的属性。它们也被称为非默认索引器。C# 不支持声明或使用带参数的属性。

IronPython 支持带参数的属性。例如,上面的默认索引器也可以使用非默认格式访问,如下所示

>>> ba.Item[0]
False

访问 .NET 事件

.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 对象,则应考虑使用它。

特殊 .NET 类型

.NET 数组

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))

多维数组

待办事项

.NET 异常

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...
>>>

底层的 .NET 异常对象

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 语句时,.NET 异常对象是实际被 IronPython 运行时抛出的对象。当 Python 代码使用 except 关键字捕获 Python 异常时,会使用 Python 异常对象。但是,如果异常被调用 Python 代码的 C#(例如)代码捕获,那么 C# 代码自然会捕获 .NET 异常对象。

可以通过使用 clsException 属性访问与 Python 异常对象对应的 .NET 异常对象(如果模块已执行 import clr)。

>>> 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]

与 .NET 异常对象对应的 Python 异常对象可以通过 System.Exception.Data 属性访问(对 IronPython 运行时而言)。请注意,这是一个实现细节,可能会发生变化。

>>> e.clsException.Data["PythonExceptionInfo"] #doctest: +ELLIPSIS
<IronPython.Runtime.Exceptions.PythonExceptions+ExceptionDataWrapper object at ...>
[6]... 除了通过 DLR 托管 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
LookupError   IP.O.LookupException
IndexError System.IndexOutOfRangeException  
KeyError S.C.G.KeyNotFoundException  
ArithmeticError System.ArithmeticException  
OverflowError System.OverflowException  
ZeroDivisionError System.DivideByZeroException  
FloatingPointError   IP.O.PythonFloatingPointError
ValueError ArgumentException  
UnicodeError   IP.O.UnicodeException
UnicodeEncodeError System.Text.EncoderFallbackException  
UnicodeDecodeError System.Text.DecoderFallbackException  
UnicodeTranslateError   IP.O.UnicodeTranslateException
ReferenceError   IP.O.ReferenceException
SystemError   IP.O.PythonSystemError
MemoryError System.OutOfMemoryException  
警告 System.ComponentModel.WarningException  
UserWarning   IP.O.PythonUserWarning
DeprecationWarning   IP.O.PythonDeprecationWarning
PendingDeprecationWarning   IP.O.PythonPendingDeprecationWarning
SyntaxWarning   IP.O.PythonSyntaxWarning
OverflowWarning   IP.O.PythonOverflowWarning
RuntimeWarning   IP.O.PythonRuntimeWarning
FutureWarning   IP.O.PythonFutureWarning

重新审视 rescue 关键字

鉴于 `raise` 会同时创建 Python 异常对象和 .NET 异常对象,并且鉴于 `rescue` 可以捕获 Python 异常和 .NET 异常,因此出现了一个问题,即 `rescue` 关键字将使用哪一个异常对象。答案是它将使用 `rescue` 子句中使用的类型。即,如果 `rescue` 子句使用 Python 异常,则将使用 Python 异常对象。如果 `rescue` 子句使用 .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 托管 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 值类型。**TODO** - 示例。

例如,考虑以下 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# 中编写类似的代码(尝试修改从属性调用中返回的值类型的字段),C# 会产生编译时错误。

更糟糕的是,当尝试通过 Line 公开的 start 字段直接修改值类型时(即 “`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 实例不支持反射。

您可以使用 unbound-class-instance-method 语法来调用此类代理对象上的方法。

委托

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]>)

如果委托的返回类型为 void,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 参数的委托。

子类化 .NET 类型

使用 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 子类继承。请参阅 type-mapping

重写方法

可以通过定义具有相同名称的 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 作为参数传入。

具有 refout 参数的方法

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 调用

当您从 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 类的 GetGetMethodGetSetMethods 访问这些名称。

>>> 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.GetAddMethodEventInfo.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 子类型的 protected 成员的访问。相反,它始终使受保护的成员像公共成员一样可用。

>>> class MyClass(System.Object):
...     pass
>>> o = MyClass()
>>> o.MemberwiseClone() #doctest: +ELLIPSIS
<MyClass object at ...>

声明 .NET 类型

Python 代码中的类与普通 .NET 类型之间的关系

Python 中的类定义不会直接映射到唯一的 .NET 类型。这是因为 Python 和 .NET 之间的类语义不同。例如,在 Python 中,可以通过将 __bases__ 属性分配给类型对象来更改基类型。但是,.NET 类型无法做到这一点。因此,IronPython 在不直接将 Python 类映射到 .NET 类型的情况下实现 Python 类。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

另请参阅 Type-system unification (type and System.Type)

[7]这些字段名称是实现细节,可能会发生变化。

__clrtype__

有时需要控制为 Python 类生成的 .NET 类型。这是因为某些 .NET API 期望用户定义具有特定属性和成员的 .NET 类型。例如,要定义一个 pinvoke 方法,用户需要定义一个 .NET 类型,该类型具有用 DllImportAttribute 标记的 .NET 方法,并且其中 .NET 方法的签名准确地描述了目标平台方法。

从 IronPython 2.6 开始,IronPython 支持一个低级钩子,允许自定义与 Python 类对应的 .NET 类型。如果 Python 类的元类具有名为 __clrtype__ 的属性,则会调用该属性来生成 .NET 类型。这允许用户控制生成的 .NET 类型的详细信息。但是,这是一个低级钩子,用户需要在其基础上构建。

IronPython 网站上提供的 ClrType 示例展示了如何在 __clrtype__ 钩子的基础上构建。

从其他 .NET 代码访问 Python 代码

像 C# 和 VB.Net 这样的静态类型语言可以编译成程序集,然后可以被其他 .NET 代码使用。但是,IronPython 代码使用 ipy.exe 动态执行。如果你想从其他 .NET 代码运行 Python 代码,有几种方法可以做到。

使用 DLR 托管 API

DLR 托管 API 允许 .NET 应用程序嵌入 DLR 语言,如 IronPython 和 IronRuby,加载和执行 Python 和 Ruby 代码,并访问由 Python 或 Ruby 代码创建的对象。

将 Python 代码编译成程序集

pyc 示例 可用于将 IronPython 代码编译成程序集。该示例建立在 clr-CompileModules 之上。然后可以使用 Python-ImportModule 加载和执行程序集。但是,请注意,程序集中的 MSIL 不是 CLS 兼容 的,不能直接从其他 .NET 语言访问。

dynamic

从 .NET 4.0 开始,C# 和 VB.Net 支持使用 dynamic 关键字访问 IronPython 对象。这使得访问 IronPython 对象更加简洁。请注意,你需要使用 hosting-apis 加载 IronPython 代码并从中获取根对象。

Python 和 .NET 功能的集成

对 Python 类型的扩展

import clr 在某些 Python 类型上公开额外的功能,以使 .NET 功能可访问

  • 任何内置或 .NET 类型的 method 对象
    • 实例方法
      • Overloads(t1 [, t2...])
  • type 对象
    • 实例方法
      • __getitem__(t1 [, t2...]) - 创建泛型实例化

对 .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

    • 类方法
      • 使用类型对象对类型对象进行索引以访问特定数组类型
      • __new__(l) 其中 l 是 IList<T>(或支持 __getitem__?)
    • __getitem__、__setitem__、__slice__

    System.Delegate

    • 类方法:__new__(type, function_or_bound_method)
    • __call__

    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 .NET 代码比较和哈希 Python 对象,并且 __eq__ 和 __hash__ 也可在 Python 代码中用于非 Python 对象。

当 Python 代码调用 __eq__ 和 __hash__ 时

  • 如果对象是 Python 对象,则会调用 __eq__ 和 __hash__ 的默认实现。默认实现分别调用 System.Object.ReferenceEquals 和 System.Runtime.CompileServices.RuntimeHelpers.GetHashCode。
  • 如果对象是 CLR 对象,则分别在 .NET 对象上调用 System.Object.Equals 和 System.Object.GetHashCode。
  • 如果对象是继承自 CLR 类的 Python 子类对象,如果 Python 子类没有定义 __eq__ 和 __hash__,则会调用 CLR 类的 System.Object.Equals 和 System.Object.GetHashCode 实现。如果 Python 子类定义了 __eq__ 和 __hash__,则会改为调用它们。

当静态 MSIL 代码调用 System.Object.Equals 和 System.Object.GetHashCode 时

  • 如果对象是 Python 对象,Python 对象将直接调用 __eq__ 和 __hash__。如果 Python 对象为这些方法实现了方法,则将调用它们。否则,将调用上面提到的默认实现。
  • 如果对象是继承自 CLR 类的 Python 子类对象,如果 Python 子类没有定义 __eq__ 和 __hash__,则会调用 CLR 类的 System.Object.Equals 和 System.Object.GetHashCode 实现。如果 Python 子类定义了 __eq__ 和 __hash__,则会改为调用它们。

可变对象的散列

CLR 期望 System.Object.GetHashCode 始终为给定对象返回相同的值。如果未维护此不变性,则使用对象作为 System.Collections.Generic.Dictionary<K,V> 中的键将导致错误行为。Python 允许 __hash__ 返回不同的结果,并依赖用户处理将对象用作哈希中键的情况。Python 和 CLR 关于相等性和哈希的概念之间的上述映射意味着处理 Python 对象的 CLR 代码必须意识到这个问题。如果静态 MSIL 代码使用 Python 对象作为 Dictionary<K,V> 中的键,则可能会发生意外行为。

为了减少使用常见 Python 类型时发生这种情况的可能性,IronPython 不会将 __hash__ 映射到 Array 和 Hash 的 GetHashCode。对于其他 Python 类,如果 Python 类是可变的,但还需要用作 Dictionary<K,V> 中的键,则用户可以为 __eq__ 和 Equals 以及 __hash__ 和 GetHashCode 提供单独的实现。

System.Object.ToString, __repr__ 和 __str__

Python 对象上的 ToString

在 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_...'

__repr__/__str__ 在 .NET 对象上

所有 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 跟踪

OleAutomation 和 COM 互操作

IronPython 支持访问 OleAutomation 对象(支持 dispinterfaces 的 COM 对象)。

IronPython 不支持 win32ole 库,但使用 win32ole 的 Python 代码只需进行少量修改即可在 IronPython 上运行。

创建 COM 对象

不同的语言有不同的方法来创建 COM 对象。VBScript 和 VBA 有一种名为 CreateObject 的方法来创建 OleAut 对象。JScript 有一种名为 TODO 的方法。在 IronPython 中有多种方法可以实现相同的功能。

  1. 第一种方法是使用 System.Type.GetTypeFromProgIDSystem.Activator.CreateInstance。此方法适用于任何已注册的 COM 对象

    >>> import System
    >>> t = System.Type.GetTypeFromProgID("Excel.Application")
    >>> excel = System.Activator.CreateInstance(t)
    >>> wb = excel.Workbooks.Add()
    >>> excel.Quit()
    
  2. 第二种方法是使用 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()
    
  3. 最后,您还可以使用 互操作程序集。这可以使用 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 对象

一旦您可以访问 COM 对象,就可以像使用任何其他对象一样使用它。属性、方法、默认索引器和事件都按预期工作。

属性

有一个重要的细节值得指出。IronPython 尝试使用 OleAut 对象的类型库(如果可以找到),以便在访问方法或属性时进行名称解析。这样做的原因是,IDispatch 接口在属性和方法调用之间没有太大区别。这是因为 Visual Basic 6 语义,其中“excel.Quit”和“excel.Quit()”具有完全相同的语义。但是,IronPython 在属性和方法之间有严格的区别,并且方法是一级对象。为了让 IronPython 知道“excel.Quit”应该调用 Quit 方法,还是只返回一个可调用对象,它需要检查类型库。如果类型库不可用,IronPython 假设它是一个方法。因此,如果 OleAut 对象有一个名为“prop”的属性,但它没有类型库,则需要在 IronPython 中编写“p = obj.prop()”来读取属性值。

具有 out 参数的方法

调用带有“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 加载类型库。

非自动化 COM 对象

IronPython 不完全支持不支持 dispinterfaces 的 COM 对象,因为它们看起来像是代理对象 [12]。你可以使用非绑定类方法语法来访问它们。

[12]这在 IronPython 1 中得到支持,但在版本 2 中被删除了。

其他

安全模型

在使用 ipy.exe 运行 Python 代码时,IronPython 的行为类似于 Python,不会进行任何沙箱化。所有脚本都以用户的权限执行。因此,例如,运行从互联网下载的 Python 代码可能会存在潜在的危险。

但是,ipy.exe 只是 IronPython 的一种表现形式。IronPython 也可以用于其他场景,例如嵌入到应用程序中。所有 IronPython 程序集都是 安全透明的。因此,IronPython 代码可以在沙箱中运行,并且主机可以控制授予 Python 代码的安全权限。这是 IronPython 在 .NET 之上构建的优势之一。

执行模型和调用帧

IronPython 代码可以通过以下任何一种技术执行

  1. 解释
  2. 使用 DynamicMethod 随时编译
  3. 使用 DynamicMethod 随时编译
  4. 使用 ipyc 预先编译到磁盘上的程序集
  5. 上述方法的组合 - 例如,一个方法最初可能被解释,并且可以在被调用多次后被编译。

因此,IronPython 代码的调用帧不像 C# 和 VB.Net 等静态类型语言的帧。使用以下列出的 API 的 .NET 代码需要考虑如何处理 IronPython 代码

  • StackTrace.__new__
  • GetExecutingAssembly
  • Exception.ToString

访问非公共成员

有时访问对象的私有成员很有用。例如,在 IronPython 中为 .NET 代码编写单元测试时,或者使用交互式命令行观察某个对象的内部工作原理时。ipy.exe 通过 -X:PrivateBinding` 命令行选项支持此功能。它也可以在托管场景中通过 TODO 属性启用;这需要 IronPython 以 FullTrust 权限执行。

Python 内置类型与 .NET 类型之间的映射

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 本身实现。

import clr 和内置类型

由于一些 Python 内置类型是用 .NET 类型实现的,因此出现了这些类型是像 Python 类型还是像 .NET 类型工作的问题。答案是,默认情况下,这些类型像 Python 类型工作。但是,如果一个模块执行 import clr,这些类型既像 Python 类型又像 .NET 类型工作。例如,默认情况下,object' 没有名为 GetHashCodeSystem.Object 方法。

>>> hasattr(object, "__hash__")
True
>>> # Note that this assumes that "import clr" has not yet been executed
>>> hasattr(object, "GetHashCode") #doctest: +SKIP
False

但是,一旦你执行 import clrobject 既有 __hash__ 也有 GetHashCode

>>> import clr
>>> hasattr(object, "__hash__")
True
>>> hasattr(object, "GetHashCode")
True

LINQ

语言集成查询 (LINQ) 是在 .NET 3.5 中添加的一组功能。由于它是一种场景而不是特定功能,因此我们将首先比较哪些场景适用于 IronPython。

  • LINQ to 对象

    Python 的列表推导提供了类似的功能,并且更符合 Python 风格。因此,建议使用列表推导本身。

  • DLinq - 目前不支持。

逐项功能比较

LINQ 包含许多语言和 .NET 功能,IronPython 对不同功能的支持程度不同。

  • C# 和 VB.NET lambda 函数 - Python 已经支持 lambda 函数。
  • 匿名类型 - Python 有元组,可以像匿名类型一样使用。
  • 扩展方法 - 请参阅
  • 泛型方法类型参数推断 - 请参阅
  • 表达式树 - 不支持。这是 DLinq 不起作用的主要原因。

附录 - 类型转换规则

请注意,某些 Python 类型是作为 .NET 类型实现的,在这种情况下不需要转换。有关映射,请参阅 builtin-type-mapping

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 相同)

参数类型:给定一个参数列表 A,它有一组类型 {A1, A1, ..., An} 和类型适用的参数列表 P 和 Q,它们分别具有类型 {P1, P2, ..., Pn} 和 {Q1, Q2, ..., Qn},如果 P 比 Q 更好,则

参数修改:使用最少转换次数从原始方法转换来的方法被认为是最佳匹配。最佳成员是与适用于方法的转换列表中的最早规则匹配的成员。如果两个成员使用相同的规则,则转换参数最少的那个方法被认为是最佳的。例如,如果多个参数方法具有相同的扩展形式,则在参数扩展形式之前具有最多参数的方法将被选中。

静态方法与实例方法:当比较一个静态方法和一个都适用的实例方法时,与调用约定匹配的方法被认为是更好的。如果方法在类型对象上未绑定调用,则首选静态方法;但是,如果方法绑定到实例调用,则首选实例方法。

显式实现的接口方法:在类上实现为公共方法的方法被认为比在声明类上是私有的且显式实现接口方法的方法更好。

泛型方法:非泛型方法被认为比泛型方法更好。

更好的转换(与 C# 7.4.2.3 相同)

ExtensibleFoo 的特殊转换规则:只要存在从 Foo 到该类型的适当转换,ExtensibleFoo 就具有转换为该类型的转换。

隐式转换

缩窄转换(参见 VB 8.9 但对 Python 限制更多)是无法证明始终成功的转换,已知可能丢失信息的转换,以及跨类型域的转换,这些类型域足够不同,需要缩窄表示法。以下转换被归类为缩窄转换

首选缩窄转换

<需要从这里向下编辑>

缩窄转换

以下所有内容都需要显式转换

当 C# 方法被 Python 覆盖或委托在 Python 端实现时,反向规则

附录 - 调用泛型方法时的类型参数推断规则

待办事项