当前位置:网站首页 > 技术博客 > 正文

cython github



原文: http://docs.cython.org/en/latest/src/quickstart/overview.html

[Cython] 是一种编程语言,它使 Python 语言的 C 语言扩展与 Python 本身一样简单。它旨在成为 [Python] 语言的超集,为其提供高级,面向对象,功能和动态编程。它的主要功能是支持可选的静态类型声明作为语言的一部分。源代码被转换为优化的 C / C ++代码并编译为 Python 扩展模块。这允许非常快速的程序执行和与外部 C 库的紧密集成,同时保持 Python 语言众所周知的高程序员生产力。

主要的 Python 执行环境通常被称为 CPython,因为它是用 C 语言编写的。其他主要实现使用 Java(Jython [Jython] ),C#(IronPython [IronPython] )和 Python 本身(PyPy [PyPy] )。用 C 语言编写,CPython 有助于包装许多通过 C 语言接口的外部库。然而,在 C 中编写必要的粘合代码仍然是微不足道的,特别是对于像 Python 这样的高级语言更流利的程序员而不是像 C 这样的接近金属的语言。

最初基于着名的 Pyrex [Pyrex] ,Cython 项目通过源代码编译器将 Python 代码转换为等效的 C 代码来解决这个问题。此代码在 CPython 运行时环境中执行,但是以编译的 C 的速度执行,并且能够直接调用 C 库。同时,它保留了 Python 源代码的原始接口,这使得它可以直接从 Python 代码中使用。这些双重特性使 Cython 的两个主要用例成为可能:使用快速二进制模块扩展 CPython 解释器,以及将 Python 代码与外部 C 库连接。

虽然 Cython 可以编译(大多数)常规 Python 代码,但生成的 C 代码通常可以从 Python 和 C 类型的可选静态类型声明中获得主要(并且有时令人印象深刻)的速度改进。这些允许 Cython 将 C 语义分配给代码的一部分,并将它们转换为非常有效的 C 代码。因此,类型声明可用于两个目的:将代码段从动态 Python 语义转换为静态和快速 C 语义,还用于直接操作外部库中定义的类型。因此,Cython 将这两个世界合并为一种非常广泛适用的编程语言。

| [Cython] | G. Ewing,R。W. Bradshaw,S。Behnel,D。S. Seljebotn 等人,The Cython 编译器, https://cython.org/ 。 |

| [IronPython] | Jim Hugunin 等人, https://archive.codeplex.com/?p=IronPython 。 |

| [Jython] | J. Huginin,B。Warsaw,F.Bock,et al。,Jython:Python for the Java platform, http://www.jython.org 。 |

| [PyPy] | PyPy Group,PyPy:用 Python 编写的 Python 实现, https://pypy.org/ 。 |

| [派热克斯] | G. Ewing,Pyrex:Python 的 C-Extensions, https://www.cosc.canterbury.ac.nz/greg.ewing/python/Pyrex/ |

| [Python] | G. van Rossum 等人,Python 编程语言, https://www.python.org/ 。 |

原文: http://docs.cython.org/en/latest/src/quickstart/install.html

许多科学的 Python 发行版,例如 Anaconda [Anaconda] ,Enthought Canopy [Canopy] 和 Sage [Sage] ,捆绑 Cython 并且不需要设置。但请注意,如果您的发行版发布的 Cython 版本太旧,您仍然可以使用下面的说明更新 Cython。除非脚注另有说明,否则本教程中的所有内容都应与 Cython 0.11.2 及更高版本一起使用。

与大多数 Python 软件不同,Cython 需要在系统上存在 C 编译器。获取 C 编译器的细节因使用的系统而异:

  • Linux GNU C 编译器(gcc)通常存在,或通过包系统轻松获得。例如,在 Ubuntu 或 Debian 上,命令将获取您需要的所有内容。
  • Mac OS X 要检索 gcc,一个选项是安装 Apple 的 XCode,可以从 Mac OS X 的安装 DVD 或 https://developer.apple 中检索.com / 。
  • Windows 一个流行的选择是使用开源 MinGW(Windows 的 gcc 分发版)。有关手动设置 MinGW 的说明,请参阅附录.Enthought Canopy 和 Python(x,y)捆绑 MinGW,但附录中的一些选择是使用 Microsoft 的 Visual C.然后必须使用与编译安装的 Python 相同的版本。

安装 Cython 的最简单方法是使用:

最新的 Cython 版本始终可以从 https://cython.org/ 下载。解压缩 tarball 或 zip 文件,输入目录,然后运行:

对于一次性构建,例如对于 CI /测试,在 PyPI 上提供的其中一个滚轮包未涵盖的平台上,它比完整源代码构建要快得多,以安装未编译(较慢)的 Cython 版本

| [蟒蛇] | https://docs.anaconda.com/anaconda/ |

| [冠层] | https://www.enthought.com/product/canopy/ |

| [Sage] |

  1. Stein 等,Sage 数学软件, https://www.sagemath.org/

    |

原文: http://docs.cython.org/en/latest/src/quickstart/build.html

与 Python 不同,Cython 代码必须编译。这发生在两个阶段:

  • A 文件由 Cython 编译为文件,包含 Python 扩展模块的代码。
  • 文件由 C 编译器编译为文件(或 Windows 上的),可直接直接进入 Python 会话.Distutils 或 setuptools 负责这部分。虽然 Cython 可以在某些情况下为你调用它们。

要完全理解 Cython + distutils / setuptools 构建过程,可能需要阅读更多关于分发 Python 模块的内容。

有几种方法可以构建 Cython 代码:

  • 写一个 distutils / setuptools 。这是正常和推荐的方式。
  • 使用 Pyximport,导入 Cython 文件就像它们是文件一样(使用 distutils 在后台编译和构建)。这种方法比编写更容易,但不是很灵活。因此,如果您需要某些编译选项,则需要编写。
  • 手动运行命令行实用程序,从文件生成文件,然后手动将文件编译成适合从 Python 导入的共享库或 DLL。(这些手动步骤主要用于调试和实验。)
  • 使用 [Jupyter] 笔记本或 [Sage] 笔记本,两者都允许 Cython 代码内联。这是开始编写 Cython 代码并运行它的最简单方法。

目前,使用 distutils 或 setuptools 是构建和分发 Cython 文件的最常用方式。其他方法在参考手册的 源文件和编译 部分中有更详细的描述。

想象一下文件中的一个简单的“hello world”脚本:

以下可能是相应的脚本:

要构建,请运行。然后只需启动一个 Python 会话并执行并根据需要使用导入的函数。

如果您使用 setuptools 而不是 distutils,则需要注意,运行时的默认操作是创建一个压缩的文件,当您尝试从依赖包中使用它们时,这些文件无法与文件一起用于文件。为防止这种情况,请在的参数中包含。

Cython 可以通过 Web 浏览器通过 Jupyter 笔记本方便地和交互式地使用。要安装 Jupyter 笔记本,例如进入 virtualenv,使用 pip:

要启用对 Cython 编译的支持,请按照 安装指南 中的说明安装 Cython,并从 Jupyter 笔记本中加载扩展:

然后,使用标记为单元格添加前缀以进行编译:

您可以通过传递选项来显示 Cython 的代码分析:

https://www.bookstack.cn/_images/jupyter.png

有关魔法参数的更多信息,请参阅 使用 Jupyter 笔记本 进行编译。

https://www.bookstack.cn/_images/sage.png

对于 Sage 数学发行版的用户,Sage 笔记本允许通过在单元格顶部键入并进行评估来透明地编辑和编译 Cython 代码。导入到运行会话中的 Cython 单元格中定义的变量和函数。

| [Jupyter] | https://jupyter.org/ |

原文: http://docs.cython.org/en/latest/src/quickstart/cythonize.html

Cython 是一个 Python 编译器。这意味着它可以在不进行更改的情况下编译普通的 Python 代码(除了一些尚未支持的语言功能的一些明显例外,请参阅 Cython 限制 )。但是,对于性能关键代码,添加静态类型声明通常很有用,因为它们将允许 Cython 脱离 Python 代码的动态特性并生成更简单,更快速的 C 代码 - 有时会快几个数量级。

但必须注意,类型声明可以使源代码更加冗长,从而降低可读性。因此,不鼓励在没有充分理由的情况下使用它们,例如基准测试证明它们在性能关键部分确实使代码更快。通常情况下,正确位置的一些类型会有很长的路要走。

所有 C 类型都可用于类型声明:整数和浮点类型,复数,结构,联合和指针类型。 Cython 可以在分配时自动和正确地转换类型。这还包括 Python 的任意大小整数类型,其中转换为 C 类型时溢出的值将在运行时引发 Python 。 (但是,在进行算术运算时,它不会检查溢出。)在这种情况下,生成的 C 代码将正确且安全地处理 C 类型的平台相关大小。

类型通过 cdef 关键字声明。

考虑以下纯 Python 代码:

在 Cython 中简单地编译它只能提供 35%的加速。这比没有好,但添加一些静态类型可以产生更大的差异。

使用其他类型声明,这可能如下所示:

由于迭代器变量是用 C 语义键入的,因此 for 循环将被编译为纯 C 代码。键入,和非常重要,因为它们涉及 for 循环中的算术运算;键入和会产生较小的差异,但在这种情况下,要保持一致并输入整个函数并不是一件额外的工作。

这导致纯 Python 版本的速度提高了 4 倍。

Python 函数调用可能很昂贵 - 在 Cython 中是双倍的,因为可能需要转换到 Python 对象和从 Python 对象进行调用。在上面的示例中,假设参数在 f()内部和调用它时都是 C double,但是必须围绕参数构造 Python 对象才能传递它。

因此,Cython 提供了声明 C 风格函数的语法,即 cdef 关键字:

通常应该添加某种形式的 except-modifier,否则 Cython 将无法传播函数(或它调用的函数)中引发的异常。 表示如果返回将检查错误(尽管表示也可以用作有效返回值)。或者,较慢的始终是安全的。如果函数返回 Python 对象或者保证在函数调用中不会引发异常,则可以省略 except 子句。

cdef 的副作用是 Python 空间不再提供该函数,因为 Python 不知道如何调用它。也无法再在运行时更改。

使用关键字而不是,还会创建一个 Python 包装器,以便该函数可以从 Cython(快速,直接传递类型值)和 Python(包装 Python 对象中的值)中获得。事实上,不仅提供了一个 Python 包装器,它还安装了逻辑,允许方法被 python 方法覆盖,即使从 cython 中调用也是如此。与方法相比,这确实增加了很小的开销。

加速:超过纯 Python 的 150 倍。

因为静态打字通常是提高速度的关键所在,所以初学者往往倾向于在视线中输入所有内容。这降低了可读性和灵活性,甚至可以降低速度(例如,通过添加不必要的类型检查,转换或缓慢的缓冲区解包)。另一方面,忘记键入关键循环变量很容易破坏性能。帮助完成此任务的两个基本工具是分析和注释。分析应该是任何优化工作的第一步,并且可以告诉您在哪里花费时间。 Cython 的注释可以告诉你为什么你的代码需要时间。

使用开关到命令行程序(或跟随 Sage 笔记本的链接)会导致 Cython 代码的 HTML 报告与生成的 C 代码交错。线条根据“类型”的级别着色 - 白线转换为纯 C,而需要 Python C-API 的线条为黄色(因为它们转化为更多的 C-API 交互,因此更暗)。转换为 C 代码的行在前面有一个加号(),可以单击以显示生成的代码。

当优化速度函数以及确定何时 释放 GIL时,此报告非常有用:通常,块可能只包含“白色”代码。

https://www.bookstack.cn/_images/htmlreport.png

请注意,Cython 根据其赋值(包括作为循环变量目标)推断出局部变量的类型,这也可以减少在任何地方显式指定类型的需要。例如,将声明为 double 类型是不必要的,就像在最后一个版本中声明的类型一样(其中的返回类型已知为 C double。)一个值得注意的例外然而,算术表达式中使用的是 整数类型,因为 Cython 无法确保不会发生溢出(因此在需要 Python 的 bignums 时会回退到)。要允许推断 C 整数类型,请将 指令 设置为。对于熟悉此语言功能的读者,此指令的工作类似于 C ++中的关键字。减少输入所有内容的需求可能会有很大帮助,但也可能导致意外。特别是如果一个人不熟悉 c 类型的算术表达式。这些可以在中找到的快速概述。

原文: http://docs.cython.org/en/latest/src/tutorial/cython_tutorial.html

Cython 的基本特性可归纳如下:Cython 是具有 C 数据类型的 Python。

Cython 是 Python:几乎任何 Python 代码都是有效的 Cython 代码。 (有一些 限制 ,但这种近似现在将起作用。)Cython 编译器将其转换为 C 代码,它对 Python / C API 进行等效调用。

但 Cython 远不止于此,因为参数和变量可以声明为具有 C 数据类型。操作 Python 值和 C 值的代码可以自由混合,只要有可能就会自动进行转换。 Python 操作的引用计数维护和错误检查也是自动的,并且即使在操作 C 数据的过程中,您也可以使用 Python 的异常处理工具(包括 try-except 和 try-finally 语句)的全部功能。

由于 Cython 几乎可以接受任何有效的 python 源文件,因此入门中最困难的事情之一就是弄清楚如何编译扩展。

所以让我们从规范的 python hello 世界开始:

将此代码保存在名为的文件中。现在我们需要创建,它就像一个 python Makefile(有关更多信息,请参阅 源文件和编译 )。你的看起来像:

要使用它来构建您的 Cython 文件,请使用命令行选项:

这将在您的本地目录中将文件保留在 unix 中的或 Windows 中的中。现在使用这个文件:启动 python 解释器并简单地导入它就好像它是一个普通的 python 模块:

恭喜!您现在知道如何构建 Cython 扩展。但到目前为止,这个例子并没有真正让人感觉为什么会想要使用 Cython,所以让我们创建一个更现实的例子。

如果您的模块不需要任何额外的 C 库或特殊的构建设置,那么您可以使用最初由 Paul Prescod 开发的 pyximport 模块在导入时直接加载.pyx 文件,而无需每个都运行文件你改变代码的时候了。它随 Cython 一起发货和安装,可以像这样使用:

Pyximport 模块还具有对普通 Python 模块的实验性编译支持。这允许您在 Py​​thon 导入的每个.pyx 和.py 模块上自动运行 Cython,包括标准库和已安装的软件包。 Cython 仍然无法编译很多 Python 模块,在这种情况下,导入机制将回退到加载 Python 源模块。 .py 导入机制安装如下:

请注意,建议不要让 Pyximport在最终用户端构建代码,因为它会挂钩到他们的导入系统。满足最终用户的最佳方式是以轮包装格式提供预先构建的二进制包。

从官方 Python 教程中,一个简单的 fibonacci 函数定义为:

现在按照 Hello World 示例的步骤,我们首先将文件重命名为 <cite>.pyx</cite> 扩展名,让我们说,然后我们创建文件。使用为 Hello World 示例创建的文件,您需要更改的是 Cython 文件名的名称,以及生成的模块名称,我们这样做:

使用与 helloworld.pyx 相同的命令构建扩展:

并使用新的扩展名:

这是一个小例子,展示了一些可以做的事情。这是查找素数的例程。你告诉它你想要多少素数,并将它们作为 Python 列表返回。

|

|

|

您将看到它的开始就像普通的 Python 函数定义一样,除了参数被声明为类型。这意味着传递的对象将被转换为 C 整数(如果不能,则会引发)。

现在,让我们深入研究该函数的核心:

第 2 行和第 3 行使用语句定义一些本地 C 变量。结果在处理期间存储在 C 数组中,并将在末尾复制到 Python 列表中(第 22 行)。

注意

您不能以这种方式创建非常大的数组,因为它们是在 C 函数调用堆栈上分配的,这是一个相当珍贵和稀缺的资源。要请求更大的数组,甚至是只在运行时知道长度的数组,您可以学习如何有效地使用 C 内存分配 , Python 数组 或 NumPy 阵列 与 Cython。

与在 C 中一样,声明静态数组需要在编译时知道大小。我们确保用户没有设置大于 1000 的值(或者我们会有一个分段错误,就像在 C 中一样)。

第 7-9 行设置了一个循环,它将测试候选数字的完整性,直到找到所需的素数。

尝试将候选人除以迄今为止发现的所有素数的第 11-12 行特别令人感兴趣。因为没有引用 Python 对象,所以循环完全转换为 C 代码,因此运行速度非常快。你会注意到我们迭代 C 数组的方式。

循环被转换为快速 C 循环,就像迭代 Python 列表或 NumPy 数组一样。如果不使用对 C 数组进行切片,则 Cython 将循环遍历数组的 1000 个元素。

如果没有发生中断,则意味着我们找到了一个素数,并且将执行行 16 之后的代码块。我们添加了找到的素数。如果你发现在 for 循环奇怪之后有,只要知道它是 Python 语言鲜为人知的特性,并且 Cython 会以 C 速度为你执行它。如果 for-else 语法让您感到困惑,请参阅这篇优秀的博客文章。

在第 22 行,在返回结果之前,我们需要将 C 数组复制到 Python 列表中,因为 Python 无法读取 C 数组。 Cython 可以自动将许多 C 类型转换为 Python 类型,如 类型转换 的文档中所述,因此我们可以使用简单的列表解析来复制 C 值为 Python 对象的 Python 列表,Cython 在此过程中自动创建。您也可以在 C 数组上手动迭代并使用,结果将是相同的。

您会注意到我们声明的 Python 列表与 Python 中的完全相同。因为变量尚未使用类型显式声明,所以假定它包含一个 Python 对象,并且从赋值中,Cython 也知道确切的类型是 Python 列表。

最后,在第 18 行,普通的 Python return 语句返回结果列表。

使用 Cython 编译器编译 primes.pyx 会生成一个扩展模块,我们可以在交互式解释器中尝试如下:

看,它有效!如果您对 Cython 为您节省了多少工作感到好奇,请查看为此模块生成的 C 代码。

Cython 有一种方法可视化与 Python 对象和 Python 的 C-API 进行交互的位置。为此,将参数传递给。它生成一个 HTML 文件。让我们来看看:

https://www.bookstack.cn/_images/htmlreport1.png

如果一行为白色,则表示生成的代码不与 Python 交互,因此将以与普通 C 代码一样快的速度运行。黄色越深,该行中的 Python 交互越多。这些黄色线通常可以在 Python 对象上运行,引发异常,或执行其他类型的高级操作,而不是可以轻松转换为简单快速的 C 代码。函数声明和返回使用 Python 解释器,因此这些行是黄色的。列表推导也是如此,因为它涉及创建 Python 对象。但行,为什么?我们可以检查生成的 C 代码来理解:

https://www.bookstack.cn/_images/python_division.png

我们可以看到一些检查发生。因为 Cython 默认使用 Python 行为,所以语言将在运行时执行除法检查,就像 Python 一样。您可以使用 编译器指令 取消激活这些检查。

现在让我们看看,即使我们有分区检查,我们也获得了提升速度。让我们编写相同的程序,但是 Python 风格:

也可以采用普通的文件并使用 Cython 进行编译。让我们拿,将函数名改为并用 Cython 编译(不改变代码)。我们还将文件名更改为,以区别于其他文件。现在看起来像这样:

现在我们可以确保这两个程序输出相同的值:

现在可以比较速度:

的 cythonize 版本比 Python 版本快 2 倍,而不需要更改单行代码。 Cython 版本比 Python 版本快 13 倍!有什么可以解释这个?

Multiple things:

  • 在这个程序中,每行都进行很少的计算。因此 python 解释器的开销非常重要。如果你要在每一行做很多计算,那将会非常不同。以 NumPy 为例。
  • 数据位置。使用 C 时,使用 Python 时可能会有更多的内容适合 CPU 缓存。因为 python 中的所有内容都是一个对象,并且每个对象都是作为字典实现的,所以这不是很容易缓存的。

通常加速比在 2x 到 1000x 之间。这取决于你调用 Python 解释器的程度。与往常一样,请记住在各地添加类型之前进行配置添加类型会降低代码的可读性,因此请谨慎使用它们。

使用 Cython,也可以利用 C ++语言,特别是 C ++标准库的一部分可以直接从 Cython 代码导入。

让我们看看当使用 C ++标准库中的向量时我们的变成了什么。

Note

C ++中的 Vector 是一种数据结构,它基于可调整大小的 C 数组实现列表或堆栈。它类似于标准库模块中的 Python 类型。有一种方法<cite>保留</cite>可用,如果你事先知道你要在矢量中放入多少元素,它将避免复制。有关详细信息,请参阅 cppreference 中的此页面。

|

|

|

第一行是编译器指令。它告诉 Cython 将您的代码编译为 C ++。这将允许使用 C ++语言功能和 C ++标准库。请注意,使用 <cite>pyximport</cite> 无法将 Cython 代码编译为 C ++。您应该使用或笔记本来运行此示例。

您可以看到向量的 API 类似于 Python 列表的 API,有时可以用作 Cython 中的替代品。

有关在 Cython 中使用 C ++的更多详细信息,请参阅 在 Cython中使用 C ++。

有关 Cython 语言的更多信息,请参阅 语言基础知识 。要在数值计算环境中直接使用 Cython,请参阅 类型记忆视图 。

原文: http://docs.cython.org/en/latest/src/tutorial/external.html

本教程简要介绍了从 Cython 代码调用 C 库函数时需要了解的内容。有关使用外部 C 库,包装它们和处理错误的更长更全面的教程,请参阅 使用 C 库 。

为简单起见,让我们从标准 C 库中的函数开始。这不会为您的代码添加任何依赖项,并且它还具有 Cython 已经为您定义了许多此类函数的额外优势。所以你可以直接使用它们。

例如,假设您需要一种低级方法来解析值中的数字。您可以使用头文件定义的功能。这可以按如下方式完成:

您可以在 Cython 的源代码包 Cython / Includes / 中找到这些标准 cimport 文件的完整列表。它们存储在文件中,这是提供可以在模块之间共享的可重用 Cython 声明的标准方法(参见 在 Cython 模块之间共享声明 )。

Cython 还为 CPython 的 C-API 提供了一整套声明。例如,要在 C 编译时测试您的代码正在编译的 CPython 版本,您可以这样做:

Cython 还提供 C 数学库的声明:

libc 数学库的特殊之处在于它在某些类 Unix 系统(如 Linux)上没有默认链接。除了导入声明之外,还必须将构建系统配置为链接到共享库。对于 distutils,将它添加到设置的参数就足够了:

如果要访问 Cython 未提供即用型声明的 C 代码,则必须自行声明。例如,上面的函数定义如下:

这声明了函数,使其可用于 Cython 代码并指示 Cython 生成包含头文件的 C 代码。 C 编译器将在编译时在中看到原始声明,但 Cython 不解析“math.h”并需要单独的定义。

就像数学库中的函数一样,只要 Cython 生成的模块与共享库或静态库正确链接,就可以声明并调用任何 C 库。

请注意,通过将其声明为,可以轻松地从 Cython 模块导出外部 C 函数。这会为它生成一个 Python 包装器并将其添加到模块 dict 中。这是一个 Cython 模块,可以直接访问 Python 代码的 C 函数:

当此声明出现在属于 Cython 模块的文件中时(即具有相同名称的 在 Cython 模块之间共享声明 ),会得到相同的结果。这允许 C 声明在其他 Cython 模块中重用,同时仍然在此特定模块中提供自动生成的 Python 包装器。

C 和 Cython 都支持没有参数名称的签名声明,如下所示:

但是,这会阻止 Cython 代码使用关键字参数调用它。因此,最好像这样编写声明:

您现在可以清楚地说明两个参数中的哪一个在您的调用中执行了哪些操作,从而避免了任何歧义并且通常使您的代码更具可读性:

请注意,稍后更改现有参数名称是向后不兼容的 API 修改,就像 Python 代码一样。因此,如果您为外部 C 或 C ++函数提供自己的声明,那么通常需要额外的努力来选择其参数的名称。

原文: http://docs.cython.org/en/latest/src/tutorial/clibraries.html

除了编写快速代码之外,Cython 的一个主要用例是从 Python 代码调用外部 C 库。由于 Cython 代码编译为 C 代码本身,因此直接在代码中调用 C 函数实际上是微不足道的。下面给出了在 Cython 代码中使用(和包装)外部 C 库的完整示例,包括适当的错误处理和有关为 Python 和 Cython 代码设计合适 API 的注意事项。

想象一下,您需要一种有效的方法来将整数值存储在 FIFO 队列中。由于内存非常重要,并且值实际上来自 C 代码,因此您无法在列表或双端队列中创建和存储 Python 对象。所以你要注意 C 中的队列实现。

经过一些网络搜索,你会发现 C 算法库 [CAlg] 并决定使用它的双端队列实现。但是,为了使处理更容易,您决定将其包装在可以封装所有内存管理的 Python 扩展类型中。

| [CAlg] | Simon Howard,C 算法库, http://c-algorithms.sourceforge.net/ |

你可以在这里下载 CAlg 。

队列实现的 C API,在头文件中定义,基本上如下所示:

首先,第一步是在文件中重新定义 C API,例如:

请注意这些声明与头文件声明几乎完全相同,因此您通常可以将它们复制过来。但是,您不需要像上面那样提供 所有 声明,只需要在代码或其他声明中使用那些声明,这样 Cython 就可以看到它们的足够和一致的子集。然后,考虑对它们进行一些调整,以使它们在 Cython 中更舒适。

具体来说,您应该注意为 C 函数选择好的参数名称,因为 Cython 允许您将它们作为关键字参数传递。稍后更改它们是向后不兼容的 API 修改。立即选择好的名称将使这些函数更适合使用 Cython 代码。

我们上面使用的头文件的一个值得注意的差异是第一行中结构的声明。在这种情况下,用作 不透明手柄 ;只有被调用的库才知道里面是什么。由于没有 Cython 代码需要知道结构的内容,我们不需要声明它的内容,所以我们只提供一个空的定义(因为我们不想声明 C 头中引用的类型) [1] 。

| [1] | 和之间存在细微差别。前者声明一种在 C 代码中引用为的类型,而后者在 C 中引用为。这是 Cython 无法隐藏的 C 语言怪癖。大多数现代 C 库使用类型的结构。 |

另一个例外是最后一行。 函数的整数返回值实际上是一个 C 布尔值,即关于它的唯一有趣的事情是它是非零还是零,表明队列是否为空。这最好用 Cython 的类型表示,它在 C 中使用时是普通的类型,但在转换为 Python 对象时映射到 Python 的布尔值和。这种在文件中收紧声明的方法通常可以简化使用它们的代码。

最好为您使用的每个库定义一个文件,如果 API 很大,有时甚至为每个头文件(或功能组)定义。这简化了它们在其他项目中的重用。有时,您可能需要使用标准 C 库中的 C 函数,或者想直接从 CPython 调用 C-API 函数。对于像这样的常见需求,Cython 附带了一组标准的文件,这些文件以易于使用的方式提供这些声明,以适应它们在 Cython 中的使用。主要包装是,和。 NumPy 库还有一个标准的文件,因为它经常在 Cython 代码中使用。有关提供的文件的完整列表,请参阅 Cython 的源包。

在声明我们的 C 库的 API 之后,我们可以开始设计应该包装 C 队列的 Queue 类。它将存在于名为的文件中。 [2]

| [2] | 请注意,文件的名称必须与带有 C 库声明的文件不同,因为两者都没有描述相同的代码。具有相同名称的文件旁边的文件定义文件中代码的导出声明。由于文件包含常规 C 库的声明,因此不得有与 Cython 关联的文件具有相同名称的文件。 |

这是 Queue 类的第一个开始:

请注意,它表示而不是。虽然也可用,但不保证可以运行(例如,可以创建子类并忘记调用祖先的构造函数)。因为没有初始化 C 指针经常导致 Python 解释器的硬崩溃,所以在 CPython 甚至考虑调用之前,Cython 提供, 始终 在构造时立即被调用,因此它是正确的位置初始化新实例的字段。但是,在对象构造期间调用时,尚未完全构造,并且必须避免对执行任何操作,而是分配给字段。

另请注意,上述方法不带参数,但子类型可能需要接受一些参数。无参数方法是一种特殊情况,它只是不接收传递给构造函数的任何参数,因此它不会阻止子类添加参数。如果参数在的签名中使用,则它们必须与用于实例化类型的类层次结构中任何声明的类方法相匹配。

在我们继续实施其他方法之前,重要的是要了解上述实现并不安全。如果在调用中出现任何问题,此代码将简单地吞下错误,因此我们稍后可能会遇到崩溃。根据功能的文档,上述可能失败的唯一原因是内存不足。在这种情况下,它将返回,而它通常会返回指向新队列的指针。

Python 的方法是提出 [3] 。因此我们可以更改 init 函数,如下所示:

| [3] | 在的特定情况下,为了引发它而创建一个新的异常实例实际上可能会失败,因为我们的内存不足。幸运的是,CPython 提供了一个 C-API 函数,可以安全地为我们提出正确的例外。只要您编写或,Cython 就会自动替换此 C-API 调用。如果您使用的是旧版本,则必须从标准软件包中导入 C-API 函数并直接调用它。 |

接下来要做的是在不再使用 Queue 实例时清理(即删除了对它的所有引用)。为此,CPython 提供了 Cython 作为特殊方法提供的回调。在我们的例子中,我们所要做的就是释放 C Queue,但前提是我们在 init 方法中成功初始化它:

在这一点上,我们有一个可以测试的工作 Cython 模块。要编译它,我们需要为 distutils 配置一个脚本。这是编译 Cython 模块的最基本脚本:

要构建针对外部 C 库,我们需要确保 Cython 找到必要的库。存档有两种方法。首先,我们可以告诉 distutils 在哪里找到 c-source 来自动编译实现。或者,我们可以构建和安装 C-Alg 作为系统库并动态链接它。如果其他应用也使用 C-Alg,后者是有用的。

要自动构建 c 代码,我们需要在 <cite>queue.pyx</cite> 中包含编译器指令:

编译器指令给出了 distutils 将编译和链接(静态)到生成的扩展模块的 C 文件的路径。通常,所有相关的头文件都应该在中找到。现在我们可以使用以下方法构建项目

并测试我们的构建是否成功:

如果我们要打包的库已经安装在系统上,则动态链接很有用。要执行动态链接,我们首先需要构建和安装 c-alg。

要在您的系统上构建 c 算法:

安装 CAlg 运行:

之后文件应该存在。

注意

此路径适用于 Linux 系统,在其他平台上可能有所不同,因此您需要根据系统中或的路径调整本教程的其余部分。

在这种方法中,我们需要告诉安装脚本与外部库链接。为此,我们需要扩展安装脚本以安装更改扩展设置

现在我们应该能够使用以下方法构建项目:

如果 <cite>libcalg</cite> 未安装在“普通”位置,用户可以通过传递适当的 C 编译器标志在外部提供所需的参数,例如:

在运行模块之前,我们还需要确保 <cite>libcalg</cite> 在 <cite>LD_LIBRARY_PATH</cite> 环境变量中,例如通过设置:

一旦我们第一次编译模块,我们现在可以导入它并实例化一个新的队列:

但是,这是我们所有 Queue 类到目前为止所能做到的,所以让它更有用。

在实现此类的公共接口之前,最好先查看 Python 提供的接口,例如在或类中。由于我们只需要 FIFO 队列,因此足以提供方法,和,另外还有方法一次添加多个值。此外,由于我们已经知道所有值都来自 C,因此最好现在只提供方法,并为它们提供直接的 C 接口。

在 C 中,数据结构通常将数据作为存储到任何数据项类型。由于我们只想存储通常适合指针类型大小的值,我们可以通过一个技巧避免额外的内存分配:我们将值转换为,反之亦然,并存储值直接作为指针值。

这是方法的简单实现:

同样,适用与方法相同的错误处理注意事项,因此我们最终会使用此实现:

现在添加方法应该是直截了当的:

例如,当从 C 数组读取值时,这变得很方便。

到目前为止,我们只能将数据添加到队列中。下一步是编写两个方法来获取第一个元素:和,它们分别提供只读和破坏性读访问。为了避免在直接将转换为时出现编译器警告,我们使用的中间数据类型足以容纳。在这里,:

通常,在 C 中,当我们将较大的整数类型转换为较小的整数类型而不检查边界时,我们冒着丢失数据的风险,并且可能是比更大的类型。但由于我们控制了如何将值添加到队列中,我们已经知道队列中的所有值都适合,因此上面的转换从到到(返回类型) )设计安全。

现在,当队列为空时会发生什么?根据文档,函数返回指针,该指针通常不是有效值。但由于我们只是简单地向内和外输入,我们无法区分返回值是否为,因为队列为空或者队列中存储的值为。在 Cython 代码中,我们希望第一种情况引发异常,而第二种情况应该只返回。为了解决这个问题,我们需要特殊情况下这个值,并检查队列是否真的是空的:

请注意我们如何在希望常见的情况下有效地创建了通过该方法的快速路径,返回值不是。如果队列为空,则只有该特定情况需要额外检查。

方法签名中的声明属于同一类别。如果函数是返回 Python 对象值的 Python 函数,CPython 将在内部返回而不是 Python 对象来指示异常,该异常将立即由周围的代码传播。问题是返回类型是并且任何值都是有效的队列项值,因此无法向调用代码显式地发出错误信号。实际上,如果没有这样的声明,Cython 就没有明显的方法可以知道在异常上返回什么以及调用代码甚至知道这个方法 可能 以异常退出。

调用代码可以处理这种情况的唯一方法是在从函数返回时调用以检查是否引发了异常,如果是,则传播异常。这显然有性能损失。因此,Cython 允许您声明在异常的情况下它应隐式返回的值,以便周围的代码只需在接收到此精确值时检查异常。

我们选择使用作为异常返回值,因为我们期望它是一个不太可能被放入队列的值。 声明中的问号表示返回值不明确(毕竟, 可能 可能是队列中的值)并且需要使用进行额外的异常检查在调用代码。没有它,调用此方法并接收异常返回值的 Cython 代码将默默地(有时不正确地)假定已引发异常。在任何情况下,所有其他返回值将几乎没有惩罚地通过,因此再次为“正常”值创建快速路径。

既然实现了方法,方法也需要适应。但是,由于它从队列中删除了一个值,因此仅在删除后测试队列是否为空 是不够的。相反,我们必须在进入时测试它:

异常传播的返回值与完全相同。

最后,我们可以通过实现特殊方法以正常的 Python 方式为 Queue 提供空白指示符(注意 Python 2 调用此方法,而 Cython 代码可以使用任一名称):

请注意,此方法返回或,因为我们在中将函数的返回类型声明为。

既然实现已经完成,您可能需要为它编写一些测试以确保它正常工作。特别是 doctests 非常适合这个目的,因为它们同时提供了一些文档。但是,要启用 doctests,您需要一个可以调用的 Python API。从 Python 代码中看不到 C 方法,因此无法从 doctests 中调用。

为类提供 Python API 的一种快速方法是将方法从更改为。这将让 Cython 生成两个入口点,一个可以使用 Python 调用语义和 Python 对象作为参数从普通 Python 代码调用,另一个可以使用快速 C 语义从 C 代码调用,不需要从 Python 进行中间参数转换。类型。请注意,方法确保 Python 方法可以适当地覆盖它们,即使从 Cython 调用它们也是如此。与方法相比,这增加了很小的开销。

现在我们已经为我们的类提供了 C 接口和 Python 接口,我们应该确保两个接口都是一致的。 Python 用户期望方法接受任意迭代,而 C 用户希望有一个允许传递 C 数组和 C 内存的方法。两个签名都不兼容。

我们将通过考虑在 C 中,API 也可能想要支持其他输入类型来解决此问题,例如, 或的数组,通常支持不同名称的 C API 函数,如,,extend_chars()extend()` duck typed Python 方法,可以接受任意迭代。

以下清单显示了尽可能使用方法的完整实现:

现在我们可以使用 python 脚本测试我们的 Queue 实现,例如这里:

作为在作者的机器上使用 10000 个数字的快速测试表明,使用来自 Cython 代码的 C 值的这个队列大约是使用 Cython 代码和 Python 对象值的速度的五倍,几乎是使用它的速度的八倍。 Python 循环中的 Python 代码,仍然比使用 Python 整数的 Cython 代码中的 Python 高度优化的类型快两倍。

假设您希望为用户提供一种方法,可以将队列中的值弹出到某个用户定义的事件。为此,您希望允许它们传递一个判断何时停止的谓词函数,例如:

现在,让我们假设为了参数,C 队列提供了一个将 C 回调函数作为谓词的函数。 API 可能如下所示:

C 回调函数具有通用参数是正常的,该参数允许通过 C-API 将任何类型的上下文或状态传递到回调函数中。我们将使用它来传递我们的 Python 谓词函数。

首先,我们必须定义一个带有预期签名的回调函数,我们可以将其传递给 C-API 函数:

主要思想是将指针(a.k.a。借用的引用)作为用户上下文参数传递给函数对象。我们将调用 C-API 函数如下:

通常的模式是首先将 Python 对象引用转换为以将其传递给 C-API 函数,然后将其转换回 C 谓词回调函数中的 Python 对象。对的强制转换创建了借用的引用。在转换为时,Cython 递增对象的引用计数,从而将借用的引用转换回拥有的引用。在谓词函数结束时,拥有的引用再次超出范围,Cython 丢弃它。

上面代码中的错误处理有点简单。具体而言,谓词函数引发的任何异常将基本上被丢弃,并且只会导致在事实之后引发普通。这可以通过将异常存储在通过 context 参数传递的对象中并在 C-API 函数返回以指示错误之后重新引发它来改进。

原文: http://docs.cython.org/en/latest/src/tutorial/cdef_classes.html

为了支持面向对象的编程,Cython 支持编写与 Python 完全相同的普通 Python 类:

然而,基于 Python 所谓的“内置类型”,Cython 支持第二种类: 扩展类型 ,由于用于声明的关键字,有时也称为“cdef 类”。与 Python 类相比,它们受到一定限制,但通常比通用 Python 类更高效,速度更快。主要区别在于它们使用 C 结构来存储它们的字段和方法而不是 Python 字典。这允许他们在他们的字段中存储任意 C 类型,而不需要 Python 包装器,并且可以直接在 C 级访问字段和方法,而无需通过 Python 字典查找。

普通的 Python 类可以从 cdef 类继承,但不能从其他方面继承。 Cython 需要知道完整的继承层次结构,以便布局它们的 C 结构,并将其限制为单继承。另一方面,普通的 Python 类可以从 Cython 代码和纯 Python 代码中继承任意数量的 Python 类和扩展类型。

到目前为止,我们的集成示例并不是非常有用,因为它只集成了一个硬编码函数。为了解决这个问题,我们将使用 cdef 类来表示浮点数上的函数:

指令 cpdef 提供了两种版本的方法;一个快速使用从 Cython 和一个较慢的使用从 Python。然后:

这比为 cdef 方法提供 python 包装稍微多一点:与 cdef 方法不同,cpdef 方法可以被 Python 子类中的方法和实例属性完全覆盖。与 cdef 方法相比,它增加了一点调用开销。

为了使类定义对其他模块可见,从而允许在实现它们的模块之外进行有效的 C 级使用和继承,我们在文件中定义它们:

使用它,我们现在可以更改我们的集成示例:

这几乎与前面的代码一样快,但是由于可以更改集成功能,因此它更加灵活。我们甚至可以传入 Python 空间中定义的新函数:

这比原始的仅使用 Python 的集成代码慢大约 20 倍,但仍然快 10 倍。这显示了当整个循环从 Python 代码移动到 Cython 模块时,加速可以很容易地变大。

关于新实施的一些注意事项:

  • 此处的快速方法调度仅起作用,因为中声明了。如果在中引入,代码仍然可以工作,但 Cython 会使用较慢的 Python 方法调度机制。
  • 以同样的方式,如果参数没有被输入,但只是作为 Python 对象传递,那么将使用较慢的 Python 调度。
  • 由于参数是打字的,我们需要检查它是否是。在 Python 中,当查找方法时,这会导致,但 Cython 会尝试访问的(不兼容的)内部结构,就像它是一样,导致崩溃或数据损坏。

有一个 编译器指令 ,它会以降低速度为代价启用此检查。以下是编译器指令用于动态打开或关闭的方法:

cdef 类中的属性与常规类中的属性的行为不同:

  • 所有属性必须在编译时预先声明
  • 属性默认只能从用 Cython 访问(类型化访问)
  • 属性可以声明为暴露的 Python 空间的动态属性

版权声明


相关文章:

  • python调用pyd文件2025-06-26 10:30:06
  • js防抖节流应用场景2025-06-26 10:30:06
  • 扫描到wsd不可用怎么办2025-06-26 10:30:06
  • openvswitch命令2025-06-26 10:30:06
  • 人和风景美图2025-06-26 10:30:06
  • 单片机c语言编程实例2025-06-26 10:30:06
  • 移位运算符可以作用于浮点数吗2025-06-26 10:30:06
  • js文件的作用2025-06-26 10:30:06
  • ce认证查询网站2025-06-26 10:30:06
  • c++三维数组怎么表示2025-06-26 10:30:06