【翻译】Cython教程1_Cython语言基础

原创文章,转载请注明: 转载自勤奋的小青蛙
本文链接地址: 【翻译】Cython教程1_Cython语言基础

C语言变量以及类型定义在Cython

使用cdef关键字,用来定义C变量以及各种结构体,枚举类型变量,如下:

cdef int i, j, k
cdef float f, g[42], *h

cdef struct Grail:
    int age
    float volume

cdef union Food:
    char *spam
    float *eggs

cdef enum CheeseType:
    cheddar, edam,
    camembert

cdef enum CheeseState:
    hard = 1
    soft = 2
    runny = 3

目前并没有针对C语言常量的语法,但是你可以使用匿名的enum邓毅方法来实现定义常量:

cdef enum:
    tons_of_spam = 3

这样便声明了一个常量。

注意:cdef只是用来定义类型,并不能用来作为引用对象。比如,声明一个 Grail 变量,你应该这么写

cdef Grail *gp

而不是这么写:

cdef struct Grail *gp #wrong

在Cython的语法中,还有一个关键字  ctypedef ,也用来做类型定义,例如:

ctypedef unsigned long ULong
ctypedef int * IntPtr

Cython中的类型

Cython使用了和C语言语法以及类型大致相同的语法,包括C语言的变量类型,指针。Cython包含了基本上所有标准的C语言类型,例如:char,short,int,long,long long以及各自对应的 unsigned 类型。

特殊的有:

C语言中的布尔类型值,在Cython中是 bint。 bint中的 零值/非零值 对应了布尔类型的 False/True

Py_ssize_t 用于(有符号)大小的Python容器

Cython中指针用数组来进行表示,比如 *x 应该用 x[0] 来表示

对多个C语言变量进行组定义声明

如果有一系列的变量都是通过cdef来声明,那么可以直接把它们放到一个cdef的语句块里,如下:

cdef:
    struct Spam:
        int tons
    int i
    float a
    Spam *p
    void f(Spam *s):
        print(s.tons, "Tons of spam")

Cython中Python函数与C语言函数

由于Cython中可以写python,也可以写C,所以,有关python以及C函数的定义,Cython中有所区别。

Python函数:Python函数使用def语句定义,如在Python中定义函数一样。他们以Python对象作为参数,返回Python对象。

C函数:C函数使用 cdef 语句定义。它们接受Python对象或C值作为参数,并且可以返回Python对象或C值。

在Cython模块中,Python函数和C函数可以自由调用,但只有Python函数可以通过解释Python代码从模块外部调用。因此,要从Cython模块“导出”的任何函数必须使用def声明为Python函数。还有一个混合函数,称为cpdef。 cpdef可以从任何地方调用,但是当从其他Cython代码调用时使用更快的C调用约定。即使从Cython调用,cpdef也可以被子类或实例属性上的Python方法覆盖。如果发生这种情况,大多数性能优势然当会丢失,即使没有,与调用cdef方法相比,从Cython调用cpdef方法的开销很小。

任何类型的函数的参数可以声明为具有C数据类型,使用正常的C声明语法。例如:

def spam(int i, char *s):
    ...

cdef int eggs(unsigned long l, float f):
    ...

当Python函数的参数声明为具有C数据类型时,它将作为Python对象传递,并在可能的情况下自动转换为C值。换句话说,上面的spam函数的定义等同于写作:

def spam(python_i, pytohn_s):
    cdef int i = python_i
    cdef char *s = python_s
    ...

自动转换目前仅可用于数字类型,字符串类型和结构(由这些类型中的任何一种递归构成);尝试使用任何其他类型的Python函数的参数将导致编译时错误。必须注意字符串,以确保在调用后使用指针时引用。 Structs可以从Python映射获得,并且如果在函数返回之后使用字符串属性,也必须小心。

另一方面,C函数可以具有任何类型的参数,因为它们是使用正常的C函数调用直接传递的。

使用cdef声明的函数(如Python函数)将在执行离开函数体而没有显式返回值时返回False值。这与C / C ++相反,C/C++并没有这一特点。

Python对象作为参数和返回值

如果没有为参数或返回值指定类型,则假定它是一个Python对象。 (注意,这不同于C约定,它将默认为int。)例如,以下定义了一个C函数,它使用两个Python对象作为参数并返回一个Python对象:

cdef spamobjs(x, y):
    ...

这些对象的引用计数根据标准的Python / C API规则自动执行(即借用的引用作为参数,返回一个新的引用)。

参数也可以用来显式声明某个对象为Python对象。如果要声明的名称将作为类型的名称,则这可能很有用,例如:

cdef ftang(object int):
    ...

声明一个名为int的参数,它是一个Python对象。您还可以使用对象作为函数的显式返回类型,例如:

cdef object ftang(object int):
    ...

为了清楚起见,在C函数中始终明确显示对象参数可能是个好主意。

Cython错误返回值

如果你不做任何特殊的事情,用cdef声明的函数不返回任何Python对象,这样这个cdef函数就没有办法向其调用者报告其Python异常。如果在此类函数中检测到异常,则会打印一条警告消息,并忽略该异常。

如果你想要一个不返回Python对象的C函数能够将异常传播给它的调用者,你需要声明一个异常值。这里是一个例子:

cdef int spam() except -1:
    ...

使用此声明,每当spam()函数内发生异常时,它将立即返回值-1。此外,每当对spam()的调用返回-1时,将假定异常已经发生并将被传播。

当为函数声明异常值时,不应显式或隐式返回该值。特别是,如果异常返回值是一个False值,那么你应该确保函数永远不会通过隐式或空返回终止。

如果所有可能的返回值都是合法的,并且您不能完全保留一个用于信号错误,则可以使用异常值声明的替代形式:

cdef int spam() except? -1:
    ...

"?"号表示-1是个异常值,在这种情况下,Cython通过生成一个函数 PyErr_Occurred() 进行返回,从而知道该函数发生了异常值。

还有第三种定义方式:

cdef int spam() except *:
    ...

这种形式导致Cython在每次调用spam()后生成对PyErr_Occurred()的调用,而不管它返回什么值。如果你有一个函数返回void需要传播错误,你将不得不使用这种形式,因为没有任何返回值来测试。否则这种形式的定义应该尽量少用。

需要注意的是:

  • 异常值只能为返回整数,枚举,浮点或指针类型的函数声明,并且该值必须是常量表达式。 void函数只能使用except *形式。
  • 异常值规范是函数签名的一部分。如果将指针作为参数传递给函数或将其指定给变量,则声明的参数或变量的类型必须具有相同的异常值规范(或缺少)。下面是一个具有异常值的指针到函数声明的示例:
int (*grail)(int, char*) except -1
  • 你不需要(也不应该)为返回Python对象的函数声明异常值。记住没有声明返回类型的函数隐式返回一个Python对象(对这些函数的异常通过返回NULL隐式传播。)

非Cython函数的返回值

当自行定义一个返回值时,Cython并不会真的返回,比如:

cdef extern FILE *fopen(char *filename, char *mode) except NULL # WRONG!

期望如果调用fopen()返回NULL,则会自动引发异常。 except子句不以这种方式工作;它的唯一目的是传播已经提出的Python异常,通过Cython函数或调用Python / C API例程的C函数。要从非Python的函数(如fopen())获取异常,你必须检查返回值并自己提高,例如:

cdef FILE* p
p = fopen("spam.txt", "r")
if p == NULL:
    raise SpamError("Couldn't open the spam file")
C types From Python types To Python types
[unsigned] char, [unsigned] short, int, long int, long int
unsigned int, unsigned long, [unsigned] long long int, long long
float, double, long double int, long, float float
char* str/bytes str/bytes [2]
struct, union dict [3]
[2] The conversion is to/from str for Python 2.x, and bytes for Python 3.x.
[3] The conversion from a C union type to a Python dict will add a value for each of the union fields. Cython 0.23 and later, however, will refuse to automatically convert a union with unsafe type combinations. An example is a union of an int and a char*, in which case the pointer value may or be not be a valid pointer.

在C语境中使用Python字符串时需要注意

在上下文中使用Python字符串时需要小心,尤其遇到C定义声明char *。在这种情况下,使用指向Python字符串内容的指针,只有Python字符串存在,该指针才有效。因此,您需要确保对原始Python字符串的引用持有,只要需要C字符串。如果你不能保证Python字符串将足够长,你将需要复制C字符串。

Cython检测并防止这种错误。例如,如果您尝试类似:

cdef char *s
s = pystring1 + pystring2

那么Cython将产生错误消息从临时Python值获取char *。原因是连接两个Python字符串产生一个新的Python字符串对象,它只被Cython生成的临时内部变量引用。一旦语句完成,临时变量将被减少并且Python字符串被释放,从而导致悬挂。由于这个代码不可能工作,Cython拒绝编译它。

解决方案是将连接的结果分配给一个Python变量,然后从中获取char *,即:

cdef char *s
p = pystring1 + pystring2
s = p

这是你的责任,持有参考p,只要有必要。

请记住,用于检测此类错误的示例只是让开发人员注意到这个问题。有时Cython会出现一些错误,有时它却无法检测到错误。所以,不管怎么说,你需要了解问题,并小心你的工作。

Cython的语句和表达式

Cython的控制结构和表达式大部分遵循Python语法。当应用于Python对象时,它们具有与Python中相同的语义(除非另有说明)。大多数Python运算符也可以应用于C值,具有明显的语义。

如果Python对象和C值在表达式中混合使用,则会在Python对象和C数字或字符串类型之间自动执行转换。

对所有Python对象自动维护引用计数,并且将采取适当的操作自动检查所有Python操作的错误。

C和Cython表达式之间的差异

C语言表达式和Cython表达式之间在语法和语义上有一些差异,特别是在Python语言中没有直接对等的C语言结构中。

  • 整数字面值被视为C常量,并且将被截断到C编译器认为合适的任何大小。要获得一个Python整数(任意精度)立即投射到一个对象(例如<object> 100000000000000000000)。 L,LL和U后缀具有与C.中相同的含义。
  • 在Cython中没有 - >运算符。而不是p - > x,使用p.x
  • 在Cython中没有一元的*运算符。而不是* p,使用p [0]
  • &运算符,具有与C中相同的语义。
  • 空C指针被称为NULL,而不是0(和NULL是保留字)。
  • 类型转换写为<type>值,例如:
cdef char* p, float* q
p = <char*>q

Cython内置函数

Cython将对大多数内置函数的调用编译为对相应的Python / C API例程的直接调用,使它们特别快。

只有使用这些名称的直接函数调用已优化。如果你使用这些名称中的一个假设它是一个Python对象,例如将它分配给一个Python变量,然后调用它,那么该调用将作为一个Python函数调用。

Function and arguments Return type Python/C API Equivalent
abs(obj) object, double, ... PyNumber_Absolute, fabs, fabsf, ...
callable(obj) bint PyObject_Callable
delattr(obj, name) None PyObject_DelAttr
exec(code, [glob, [loc]]) object
dir(obj) list PyObject_Dir
divmod(a, b) tuple PyNumber_Divmod
getattr(obj, name, [default]) (Note 1) object PyObject_GetAttr
hasattr(obj, name) bint PyObject_HasAttr
hash(obj) int / long PyObject_Hash
intern(obj) object Py*_InternFromString
isinstance(obj, type) bint PyObject_IsInstance
issubclass(obj, type) bint PyObject_IsSubclass
iter(obj, [sentinel]) object PyObject_GetIter
len(obj) Py_ssize_t PyObject_Length
pow(x, y, [z]) object PyNumber_Power
reload(obj) object PyImport_ReloadModule
repr(obj) object PyObject_Repr
setattr(obj, name) void PyObject_SetAttr

Note 1: Pyrex originally provided a function getattr3(obj, name, default)() corresponding to the three-argument form of the Python builtin getattr(). Cython still supports this function, but the usage is deprecated in favour of the normal builtin, which Cython can optimise in both forms.

Cython中的include

Cython源文件可以包括使用include语句,例如:

include "spamstuff.pxi"

指定文件的内容在文本中包含在该点。包含的文件可以包含任何在包含语句出现的上下文中有效的完整语句或声明,包括其他include语句。包含文件的内容应该从缩进级别零开始,并且将被视为缩进到包含该文件的include语句的级别。

原创文章,转载请注明: 转载自勤奋的小青蛙
本文链接地址: 【翻译】Cython教程1_Cython语言基础

文章的脚注信息由WordPress的wp-posturl插件自动生成



|2|left
打赏

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: