8000 pythonnet.github.io · lidanger/pythonnet Wiki · GitHub
[go: up one dir, main page]

Skip to content

pythonnet.github.io

LiJundang edited this page May 9, 2019 · 8 revisions

Python for .NET

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

使用 COM 组件

使用微软提供的工具,如 aximp.exetlbimp.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

注意: 因为在 Python for .NET 中运行的 Python 代码本质上是不可验证的,它完全在 CLR 的安全设施监视下运行,因此您应该将对 Python 程序集的使用限制为受信任的代码。

Python 运行时程序集定义了许多公共类,它们提供 Python C-API 所提供功能的子集。

这些包括 PyObject, PyList, PyDict, PyTuple, 等等。你可以在 "Console.csproj" 项目中回顾 nPython.exe 源代码,它是在 .NET 控制台应用中嵌入 CPython 的例子。有关新的简化嵌入 API,请参阅 GitHub README 页面:

README.md

在非常高的级别上,要将 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_EnsurePyGILState_Release 的简单封装,所以这些 API 的文档适用于托管版本。

将 C# 对象传递给 Python 引擎

本节演示如何将 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 许可。


From: http://pythonnet.github.io/

Clone this wiki locally
0