-
Notifications
You must be signed in to change notification settings - Fork 0
pythonnet.github.io
Python for .NET (pythonnet
) 是一个包,它使 Python 程序员几乎无缝地集成了 Windows 上的 .NET 4.0+ 公共语言运行时
8000
(CLR) 以及 Linux 和 OSX 上的 Mono 运行时。Python for .NET 为 .NET 开发人员提供了一个强大的应用程序脚本工具。使用这个包,您可以让 .NET 应用程序使用脚本,或者使用 .NET 服务和以 CLR (C#,VB.NET,F#,C++ /CLI) 为目标的任何语言编写的组件通过 Python 构建整个应用程序。
注意,这个包没有将 Python 实现为一流的 CLR 语言——它没有从 Python 代码生成托管代码 (IL)。相反,它将 CPython 引擎与 .NET 或 Mono 运行时集成。这种方法允许您使用 CLR 服务并继续使用现有的 Python 代码和 C-API 扩展,同时维护 Python 代码的本机执行速度。如果您对 Python 语言的纯托管代码实现感兴趣,您应该查看 IronPython 项目,该项目正在积极开发中。
Python for .NET 目前兼容 Python 发行版 2.7
, 3.3
, 3.4
, 3.5
和 3.6
。当前版本可以在 Python for .NET 网站上找到。要订阅 Python for .NET 邮件列表或阅读列表的在线档案,查看邮件列表信息页面。使用 Python for .NET 问题跟踪器来报告问题。
- 本页面提供了 Python for .NET 的详细概述,以及一些基本的使用示例。在包的演示和单元测试中可以找到许多其他的例子。
- 从 github 检出 PythonNet 代码。
- 下载各种版本的 Python 和 CLR 的发行版。
Python for .NET 可以作为 GitHub 上的源代码版本使用,也可以作为 Python Package Index 中所有受支持的 Python 版本和公共语言运行时的二进制分发版使用。
源版本是一个自包含的 “私有” 程序集。只需将包解压缩到您想要的任何位置,cd 到该目录,构建解决方案 python setup.py build_ext --inplace
。一旦在此目录中启动 Python 或 IPython 解释器或将此目录附加到 sys.path
,那么在 import clr
语句之后就可以使用 .NET 程序集了。你也可以运行 nPython.exe
(在 *nix
上为 mono nPython.exe
) 来查看Python 是如何嵌入到 .NET 控制台应用程序中的。注意,源版本不包含 CPython 运行时的副本,所以在使用源版本之前,您需要在您的机器上安装Python。
在 Linux/Mono 上运行: 单元测试显示 PythonNet 可以在 Mono 下运行,但是 Mono 运行时缺乏支持,所以仍然可能存在问题。
这个项目的一个关键目标是,Python for .NET 应该 “按照你在 Python 中期望的方式工作”,除了特定于 .NET 的情况(在这种情况下,目标是 “按照您在 C# 中期望的方式工作”)。此外,随着IronPython 项目已经建立了一个社区,我的目标是让为 IronPython 编写的代码可以在 Python for .NET 下运行而无需修改。
如果您已经了解 Python,那么您可以完成这篇自述,然后参考 .NET 文档来找出您需要做的任何事情。相反,如果你熟悉 C# 或其他 .NET 语言,那么您可能只需要从许多优秀的 Python 书籍中挑选一本,或者在线阅读 Python 教程就可以开始了。
一种好的开始方法是通过跟随本文档中的示例交互式地探索 .NET 在 Python 解释器中的用法。如果您卡住了,在发行版的源目录中还有许多演示和单元测试,可以作为示例使用。
Python for .NET 使 CLR 命名空间本质上可以被当作 Python 包。
from System import String
from System.Collections import *
可以以这种方式导入和使用来自任何加载程序集的类型。要加载程序集,请使用 clr
模块中的 AddReference
函数:
import clr
clr.AddReference("System.Windows.Forms")
from System.Windows.Forms import Form
注意 早期版本的 Python for .NET 依赖于 “隐式加载” 来支持程序集的自动加载,这些程序集的名称对应于导入的名称空间。为了向后兼容,隐式加载仍然适用,但是在将来的版本中将被删除,因此建议使用 clr.AddReference
方法。
除了通常的应用程序库和 GAC 之外,Python for .NET 还使用 PYTHONPATH (sys.path) 来查找要加载的程序集。要确保可以隐式导入程序集,请将包含程序集的目录放在 sys.path
中。
Python for .NET 使你可以使用来自 Python 的任何非私有类、结构、接口、枚举或委托。要创建托管类的实例,可以使用标准实例化语法,传递一组匹配托管类的其中一个公共构造函数的参数:
from System.Drawing import Point
p = Point(5, 5)
在大多数情况下,Python for .NET 可以根据参数自动确定要调用的正确构造函数。在某些情况下,可能需要在类上调用一个特殊的重载构造函数,该构造函数由一个特殊的 __overloads__
特性支持,该属性将很快被弃用,转而支持与 iPy 兼容的 "重载":
from System import String, Char, Int32
s = String.Overloads[Char, Int32]('A', 10)
s = String.__overloads[__Char, Int32]('A', 10)
Pythonnet 还支持泛型类型。在实例化泛型类型之前,必须绑定泛型类型以创建具体类型。泛型类型支持下标语法来创建绑定类型:
from System.Collections.Generic import Dictionary
from System import *
dict1 = Dictionary[String, String]()
dict2 = Dictionary[String, Int32]()
dict3 = Dictionary[String, Type]()
当使用下标语法传递类型列表时,还可以传递与 .NET 类型直接对应的Python 类型子集:
dict1 = Dictionary[str, str]()
dict2 = Dictionary[str, int]()
dict3 = Dictionary[str, Decimal]()
当显式选择泛型方法或特定版本的重载方法和构造函数时,这种简写也可以工作 (稍后解释)。
您还可以在 Python 中子类化托管类,尽管 Python 子类的成员对 .NET 代码是不可见的。查看分发版 /demo
目录中的 helloform.py
文件,获取一个演示托管类的子类化的简单 Windows Form 示例。
您可以获取和设置 CLR 对象的字段和属性,就像它们是常规属性一样:
from System import Environment
name = Environment.MachineName
Environment.ExitCode = 1
如果托管对象实现了一个或多个索引器,则可以使用标准 Python 索引语法调用索引器:
from System.Collections import Hashtable
table = Hashtable()
table["key 1"] = "value 1"
支持重载索引器,使用与 C# 相同的表示法:
items[0, 2]
items[0, 2, 3]
CLR 对象方法的行为类似于普通的 Python 方法。静态方法可以通过类调用,也可以通过类的实例调用。Python 可以访问 CLR 对象的所有公共和受保护的方法:
from System import Environment
drives = Environment.GetLogicalDrives()
也可以像使用 Python 方法一样调用托管方法 unbound
(将实例作为第一个参数传递)。这通常用于显式调用基类的方法。
注意 有一个警告与调用 unbound 方法有关: 托管类可以声明具有相同名称的静态方法和实例方法。由于在调用未绑定方法时,运行时不可能知道你的意图,所以总是会调用静态方法。
可以使用 CLR 方法的 docstring (doc
) 查看方法的签名,包括对 CLR 方法时的重载。你还可以使用 Python help
方法来检查托管类:
from System import Environment
print(Environment.GetFolderPath.__doc__)
help(Environment)
虽然 Python for .NET 通常能够自动找出要调用的重载方法的正确版本,但是在某些情况下,需要显式地选择特定的方法重载。
CLR 对象的方法有一个可用于此目的的 _overloads__
特性,但它很快就会被弃用,转而支持与 iPy 兼容的重载:
from System import Console
Console.WriteLine.Overloads[bool](true)
Console.WriteLine.Overloads[str]("true")
Console.WriteLine.__overloads[__int](42)
类似地,可以在运行时使用下标语法直接在方法上绑定泛型方法:
someobject.SomeGenericMethod[int](10)
someobject.SomeGenericMethod[str]("10")
托管代码中定义的委托可以在 Python 实现。可以实例化委托类型并传递可调用的 Python 对象来获取委托实例。带来的委托实例是一个真正的托管委托,当它被调用时,将调用给定的 Python 可调用对象:
def my_handler(source, args):
print('my_handler called!')
# 实例化一个委托
d = AssemblyLoadEventHandler(my_handler)
# 将它用作事件处理程序
AppDomain.CurrentDomain.AssemblyLoad += d
多播委托可以通过向委托实例添加更多可调用对象来实现:
d += self.method1
d += self.method2
d()
在 Python 中,事件被视为一类对象,其行为在很多方面类似于方法。Python 回调可以通过事件特性注册,也可以调用事件来触发事件。
注意,事件支持类似于 C# 中使用的方便拼写。您不需要将显式实例化的委托实例传递给事件 (尽管如果需要,可以这样做)。事件支持 +=
和 -=
操作符,这与 C# 习惯用法非常相似:
def handler(source, args):
print('my_handler called!')
# 注册事件处理程序
object.SomeEvent += handler
# 注销事件处理程序
object.SomeEvent -= handler
# 触发事件
result = object.SomeEvent(...)
你可以像处理纯 Python 异常一样引发和捕获托管异常:
from System import NullReferenceException
try:
raise NullReferenceException("aiieee!")
except NullReferenceException as e:
print(e.Message)
print(e.Source)
类型 System.Array
支持下标语法,以便在 Python 中轻松创建托管数组:
from System import Array
myarray = Array[int](10)
托管数组支持标准 Python 序列协议:
items = SomeObject.GetArray()
# 获取第一项
v = items[0]
items[0] = v
# 获取最后一项
v = items[-1]
items[-1] = v
# 获取长度
l = len(items)
# 内容测试
test = v in items
多维数组支持使用与 C# 中使用的表示法相同的方法进行索引:
items[0, 2]
items[0, 2, 3]
实现 IEnumerable 接口的托管数组和托管对象可以使用标准的 Python 迭代习惯用法进行迭代:
domain = System.AppDomain.CurrentDomain
for item in domain.GetAssemblies():
name = item.GetName()
使用微软提供的工具,如 aximp.exe
和 tlbimp.exe
,可以为 COM 库生成托管包装器。在生成这样一个包装器之后,您可以像使用其他托管代码一样在 Python 中使用这些库。
注意: 目前,您需要将生成的包装器放在 GAC、PythonNet 程序集目录或 PYTHONPATH 中,以便加载它们。
在 Python for .NET 下进行的类型转换相当简单——大多数基本的 Python 类型 (string、int、long等) 自动转换为兼容的对等托管类型 (string、Int32 等),反之亦然。注意,从 CLR 返回的所有字符串都是 unicode 类型。
在 Python 中没有逻辑等同物的类型被公开为托管类或结构体 (System.Decimal 就是一个例子)的实例。
.NET 架构区分了 值类型
和 引用类型
。引用类型在堆上分配,值类型要么在堆栈上分配,要么在对象中内联分配。
在 .NET 中使用了一个名为 装箱
的过程,允许代码将值类型当作引用类型。装箱导致在堆上创建值类型对象的单独副本,这样就会具有引用类型语义。
在 Python for .NET 时,理解装箱以及值类型和引用类型之间的区别可能非常重要,因为 Python 语言没有值类型语义或语法——在 Python 中 “一切都是引用”。
下面是一个演示问题的简单示例。如果你是一个经验丰富的 C# 程序员,你可能会编写以下代码:
items = System.Array.CreateInstance(Point, 3)
for i in range(3):
items[i] = Point(0, 0)
items[0].X = 1 # won't work!!
虽然 items[0].X = 1
的拼写在 C# 和 Python 中是相同的,但还是有一个重要的和微妙的语义差异。在 C# (和其他编译到 IL 的语言) 中,编译器知道 Point 是值类型,在这里可以做正确的事情,那就是在适当的位置更改值。
然而,在 Python 中,“一切都是引用”,实际上没有拼写或语义来允许它动态地做正确的事情。items[0]
本身没有变化的具体原因是,当你说到 items[0]
时,getitem 操作创建了一个 Python 对象,该对象通过 GCHandle 保存对 items[0]
对象的引用。这会导致 ValueType (类似 Point) 被装箱,因此接下来的 setattr (.X = 1
) 更改已装箱值的状态,而不是原始未装箱值的状态。
Python 中的规则实质上是:
任何属性或项访问的结果都是一个已装箱的值
而这对于处理代码很重要。
因为 Python 中没有值类型语义或语法,所以您可能需要修改您的方法。为了重温前面的例子,我们可以确保我们想要对数组项进行的更改不会 “丢失”,方法是在对数组项进行更改后重置数组成员:
items = System.Array.CreateInstance(Point, 3)
for i in range(3):
items[i] = Point(0, 0)
# 这 _将会_ 工作。我们将 “item” 作为 Point 对象的
# 装箱副本实际存储在数组中。进行更改后,我们重新
# 设置数组项以更新数组。
item = items[0]
item.X = 1
items[0] = item
这与你在 C# 中发现的一些情况类似,您必须了解装箱行为,以避免类似 更新丢失
的问题 (通常是因为代码中没有考虑到隐式装箱)。
这是一样的,只是在 Python 中表现有点不同。有关装箱以及值类型和引用类型之间的区别的更多细节,请参阅 .NET 文档。
注意: 因为在 Python for .NET 中运行的 Python 代码本质上是不可验证的,它完全在 CLR 的安全设施监视下运行,因此您应该将对 Python 程序集的使用限制为受信任的代码。
Python 运行时程序集定义了许多公共类,它们提供 Python C-API 所提供功能的子集。
这些包括 PyObject, PyList, PyDict, PyTuple, 等等。你可以在 "Console.csproj" 项目中回顾 nPython.exe 源代码,它是在 .NET 控制台应用中嵌入 CPython 的例子。有关新的简化嵌入 API,请参阅 GitHub README 页面:
在非常高的级别上,要将 Python 嵌入到应用程序中,您需要这样做:
- 在构建环境中引用 Python.Runtime.dll
- 调用 PythonEngine.Intialize() 来初始化 Python
- 调用 PythonEngine.ImportModule(name) 来加载模块
导入的模块可以在导入时就开始与托管应用程序环境一起工作,否则的话,你也可以显式地查找和调用导入模块中的对象。
有关在应用程序中嵌入 Python 的通用信息,请使用 www.python.org 或谷歌查找(C)示例。因为 Python for .NET 与托管环境紧密集成,所以通常最好导入模块并尽早遵从Python代码,而不是编写大量托管嵌入代码。
对嵌入用户的重要提示: Python 不是线程自由的,它使用一个全局解释器锁来确保多线程应用程序与 Python 安全交互。有关这方面的更多信息可以在 www.python.org 网站的 Python C-API 文档中找到。
在托管应用程序中嵌入 Python 时,必须像在 C 或 C++ 应用程序中嵌入 Python 那样管理 GIL。
在与 Python.Runtime 命名空间提供的任何对象或 api 交互之前,调用代码必须通过调用 PythonEngine.AcquireLock
方法获得 Python 全局解释器锁。这条规则的唯一例外是 PythonEngine.Initialize
方法,它可以在启动时调用,而不需要获取 GIL。
使用完 Python API 后,托管代码必须调用对应的 PythonEngine.ReleaseLock
来释放 GIL 并允许其他线程使用 Python。
必须使用 using
语句来获取和释放 GIL:
using (Py.GIL())
{
PythonEngine.Exec("doStuff()");
}
AcquireLock 和 ReleaseLock 方法是对 Python API 中的非托管函数 PyGILState_Ensure
和 PyGILState_Release
的简单封装,所以这些 API 的文档适用于托管版本。
本节演示如何将 C# 对象传递给 Python 运行时。示例使用下面的 Person
类:
public class Person
{
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
为了将 C# 对象传递给 Python 运行时,必须将其转换为 PyObject
。这是使用 ToPython()
扩展方法完成的。然后可以将 PyObject
设置为 PyScope
中的一个变量。从作用域执行的代码将有权访问这个变量:
// 创建一个 person 对象
Person person = new Person("John", "Smith");
// 在使用 Python 解释器之前获取 GIL
using (Py.GIL())
{
// 创建 Python 作用域
using (PyScope scope = Py.CreateScope())
{
// 将 Person 对象转换为 PyObject
PyObject pyPerson = person.ToPython();
// 创建 Python 变量 "person"
scope.Set("person", pyPerson);
// person 对象现在可以在 Python 中使用了
string code = "fullName = person.FirstName + ' ' + person.LastName";
scope.Exec(code);
}
}
Python for .NET 基于开源 MIT 许可证发布。许可证的副本包含在发行版中,或者你也可以找到 license online 的副本。
这个包的一些发行版包括 C Python 动态库和标准库的副本,它们由 Python license 许可。