Cython语法扩展
介绍
首先我们要了解如何扩展Cython,除了使用Python类语句创建普通的用户定义类之外,Cython还允许创建新的内置Python类型,称为扩展类型。您可以使用cdef类语句定义扩展类型。例如:
cdef class Shrubbery: cdef int width, height def __init__(self, w, h): self.width = w self.height = h def describe(self): print "This shrubbery is", self.width, \ "by", self.height, "cubits."
如您所见,Cython扩展类型定义看起来很像Python类定义。在其中,你使用def语句来定义可以从Python代码调用的方法。您甚至可以定义许多特殊的方法,如__init __(),就像在Python中一样。
主要区别是,您可以使用cdef语句定义属性。属性可以是Python对象(通用或特定扩展类型),或者它们可以是任何C数据类型。因此,您可以使用扩展类型来包装任意C数据结构,并为它们提供类似Python的接口。
属性
扩展类型的属性直接存储在对象的C struct中。属性集在编译时是固定的;您无法通过分配给扩展类型实例在扩展类型实例上添加属性,就像使用Python类实例一样。(但是,您可以在Python中将扩展类型子类化,并向子类的实例添加属性。)
有两种方法可以访问扩展类型的属性:通过Python属性查找,或通过从Cython代码直接访问C结构。Python代码只能通过第一种方法访问扩展类型的属性,但是Cython代码可以使用任一方法。
默认情况下,扩展类型属性只能通过直接访问而不是Python访问,这意味着它们不能从Python代码访问。使它们可以从Python代码访问,您需要将它们声明为public或readonly。例如:
cdef class Shrubbery: cdef public int width, height cdef readonly float depth
上面的定义可以看得出,宽度和高度属性从Python代码可读可写,深度属性可读但不可写。
注意:
- 您只能公开简单的C类型,如int,浮动和字符串,用于Python访问。您还可以公开Python值属性。
- public和readonly选项也只适用于Python访问,而不是直接访问。扩展类型的所有属性总是由C级访问可读写。
类型声明
在您可以直接访问扩展类型的属性之前,Cython编译器必须知道您具有该类型的实例,而不仅仅是一个通用的Python对象。它知道这已经在该类型的方法的self参数的情况下,但在其他情况下,您将必须使用类型声明。
例如,在以下函数中:
cdef widen_shrubbery(sh, extra_width): # BAD sh.width = sh.width + extra_width
因为sh参数没有给出类型,所以width属性将通过Python属性查找来访问。如果属性已被声明为public或readonly,那么这将工作,但它将是非常低效的。如果属性是私有的,它将根本不工作 - 代码将编译,但是会在运行时产生一个属性错误。
正确的做饭是将sh声明为Shrubbery类型,如下所示:
cdef widen_shrubbery(Shrubbery sh, extra_width): sh.width = sh.width + extra_width
现在Cython编译器知道sh有一个称为width的C属性,并将生成代码来直接有效地访问它。同样的考虑适用于局部变量,例如:
cdef Shrubbery another_shrubbery(Shrubbery sh1): cdef Shrubbery sh2 sh2 = Shrubbery() sh2.width = sh1.width sh2.height = sh1.height return sh2
类型测试和类型转换
假设我有一个方法quest()返回一个类型为Shrubbery的对象。要访问它的宽度,我可以写:
cdef Shrubbery sh = quest() print sh.width
这需要使用局部变量并对赋值执行类型测试。如果你知道quest()的返回值是Shrubbery类型,你可以使用一个强制类型写:
print (<Shrubbery>quest()).width
这可能是危险的,如果quest()实际上不是灌木,因为它会尝试访问宽度作为可能不存在的C结构成员。在C级别,不是提高AttributeError,而是返回一个无意义的结果(将该地址处的任何数据解释为int),或者可能由于尝试访问无效内存而导致segfault。相反,可以写:
print (<Shrubbery?>quest()).width
它在执行转换并允许代码继续之前执行类型检查(可能引发TypeError)。
要显式测试对象的类型,请使用isinstance()方法。默认情况下,在Python中,isinstance()方法检查第一个参数的__class__属性,以确定它是否是必需的类型。然而,这可能是不安全的,因为__class__属性可以被欺骗或改变,但是扩展类型的C结构必须是正确的,以访问其cdef属性并调用其cdef方法。Cython检测第二个参数是否是已知的扩展类型,并进行类型检查,类似于Pyrex的typecheck()。旧的行为总是可以通过传递一个元组作为第二个参数:
print isinstance(sh, Shrubbery) # 检查sh的类型是否是Shrubbery print isinstance(sh, (Shrubbery,)) # Check sh.__class__
Cython扩展类型以及空值
当您将参数或C变量声明为扩展类型时,Cython将允许它接受值None以及其声明类型的值。这类似于C指针可以取值NULL的方式,并且您需要执行相同的警告,因为它。没有问题,只要你对它执行Python操作,因为将应用完整的动态类型检查。但是,当您访问扩展类型的C属性(如上面的widen_shrubbery函数)时,由您决定确保您使用的引用不是无 - 为了效率,Cython不检查这一点。
在暴露以扩展类型为参数的Python函数时,你需要特别小心。如果我们想使widen_shrubbery()一个Python函数,例如,如果我们简单地写:
def widen_shrubbery(Shrubbery sh, extra_width): # This is sh.width = sh.width + extra_width # dangerous!
那么我们的模块的用户可能通过对sh参数传递None来使它崩溃。
解决这个问题的一种方法是:
def widen_shrubbery(Shrubbery sh, extra_width): if sh is None: raise TypeError sh.width = sh.width + extra_width
但是由于这被预期是这样一个常见的要求,Cython提供了一种更方便的方法。声明为扩展类型的Python函数的参数可以具有not None子句:
def widen_shrubbery(Shrubbery sh not None, extra_width): sh.width = sh.width + extra_width
现在函数将自动检查sh不是None,并检查它是否具有正确的类型。
注意:
- not None子句只能用于Python函数(使用def定义)和C函数(使用cdef定义)。如果你需要检查C函数的参数是否为None,你需要自己做。
- 扩展类型的方法的self参数保证永不为None。
- 当将值与None进行比较时,请记住,如果x是一个Python对象,x是None并且x不是None非常有效,因为它们直接转换为C指针比较,而x == None和x!= None,或者简单地使用x作为布尔值(如if x:...)将调用Python操作,因此速度要慢得多。
属性
您可以使用与普通Python代码相同的语法在扩展类中声明属性:
cdef class Spam: @property def cheese(self): # This is called when the property is read. ... @cheese.setter def cheese(self, value): # This is called when the property is written. ... @cheese.deleter def cheese(self): # This is called when the property is deleted.
还有一个特殊的(不推荐使用的)遗留语法用于定义扩展类中的属性:
cdef class Spam: property cheese: "A doc string can go here." def __get__(self): # This is called when the property is read. ... def __set__(self, value): # This is called when the property is written. ... def __del__(self): # This is called when the property is deleted.
__get __(),__set __()和__del __()方法都是可选的;如果省略它们,则当尝试相应的操作时将引发异常。
这里有一个完整的例子。它定义一个属性,它在每次写入列表时添加到列表,在读取时返回列表,并在删除列表时清空列表:
# cheesy.pyx cdef class CheeseShop: cdef object cheeses def __cinit__(self): self.cheeses = [] @property def cheese(self): return "We don't have: %s" % self.cheeses @cheese.setter def cheese(self, value): self.cheeses.append(value) @cheese.deleter def cheese(self): del self.cheeses[:] # Test input from cheesy import CheeseShop shop = CheeseShop() print shop.cheese shop.cheese = "camembert" print shop.cheese shop.cheese = "cheddar" print shop.cheese del shop.cheese print shop.cheese
输出结果:
# Test output We don't have: [] We don't have: ['camembert'] We don't have: ['camembert', 'cheddar'] We don't have: []
继承
扩展类型可以从内置类型或其他扩展类型继承:
cdef class Parrot: ... cdef class Norwegian(Parrot): ...
基本类型的完整定义必须可用于Cython,因此如果基本类型是内置类型,它必须先前已声明为extern扩展类型。如果基类型在另一个Cython模块中定义,则必须声明为extern扩展类型或使用cimport语句导入。
一个扩展类型只能有一个基类(没有多重继承)。
Cython扩展类型也可以在Python中子类化。Python类可以从多个扩展类型继承,只要遵循通常的用于多重继承的Python规则(所有基类的C布局必须兼容)。
从Cython 0.13.1开始,有一种方法可以防止扩展类型在Python中被子类化。这是通过final指令完成的,通常使用装饰器在扩展类型上设置:
cimport cython @cython.final cdef class Parrot: def done(self): pass
尝试从此类型创建一个Python子类将在运行时引发一个TypeError。Cython还将阻止在同一个模块中创建最终类型的子类型,即创建使用最终类型作为其基本类型的扩展类型将在编译时失败。但是,请注意,此限制目前不传播到其他扩展模块,因此即使最终扩展类型仍然可以通过外部代码在C级别子类型。
C函数
扩展类型可以有C方法以及Python方法。像C函数一样,C方法使用cdef或cpdef而不是def来声明。C方法是“虚拟”的,并且可以在派生扩展类型中被覆盖。此外,cpdef方法甚至可以被称为C方法时被python方法覆盖。与cdef方法相比,这增加了一些调用开销:
# pets.pyx cdef class Parrot: cdef void describe(self): print "This parrot is resting." cdef class Norwegian(Parrot): cdef void describe(self): Parrot.describe(self) print "Lovely plumage!" cdef Parrot p1, p2 p1 = Parrot() p2 = Norwegian() print "p1:" p1.describe() print "p2:" p2.describe()
输出结果:
# Output p1: This parrot is resting. p2: This parrot is resting. Lovely plumage!
上面的例子还说明了C方法可以使用通常的Python技术调用继承的C方法,即:
Parrot.describe(self)
cdef方法可以通过使用@staticmethod装饰器声明为静态。这对于构造采用非Python兼容类型的类尤其有用:
cdef class OwnedPointer: cdef void* ptr cdef __dealloc__(self): if ptr != NULL: free(ptr) @staticmethod cdef create(void* ptr): p = OwnedPointer() p.ptr = ptr return ptr
快速实例化
Cython提供了两种方法来加速扩展类型的实例化。第一个是直接调用__new __()特殊静态方法,如从Python中所知。对于扩展类型Penguin,您可以使用以下代码:
cdef class Penguin: cdef object food def __cinit__(self, food): self.food = food def __init__(self, food): print("eating!") normal_penguin = Penguin('fish') fast_penguin = Penguin.__new__(Penguin, 'wheat') # note: not calling __init__() !
注意,通过__new __()的路径不会调用类型的__init __()方法(再次,从Python知道)。因此,在上面的示例中,第一个实例化将打印eat !,但第二个实例化不会。这只是为什么__cinit __()方法比扩展类型的正常__init __()方法更安全和更可取的原因之一。
第二个性能改进适用于经常在一行中创建和删除的类型,以便他们可以从freelist中受益。Cython为此提供了装饰器@ cython.freelist(N),它为给定类型创建了一个静态大小的N个实例的freelist。例:
cimport cython @cython.freelist(8) cdef class Penguin: cdef object food def __cinit__(self, food): self.food = food penguin = Penguin('fish 1') penguin = None penguin = Penguin('fish 2') # does not need to allocate memory!
调用公共和外部扩展
扩展类型可以声明为extern或public。 extern扩展类型声明使得外部C代码中定义的扩展类型可用于Cython模块。公共扩展类型声明使得在Cython模块中定义的扩展类型可用于外部C代码。
外部扩展类型
extern扩展类型允许您访问在Python核心或非Cython扩展模块中定义的Python对象的内部。
这里有一个例子,让你得到C级别的内置复杂对象的成员:
cdef extern from "complexobject.h": struct Py_complex: double real double imag ctypedef class __builtin__.complex [object PyComplexObject]: cdef Py_complex cval # A function which uses the above type def spam(complex c): print "Real:", c.cval.real print "Imag:", c.cval.imag
注意:
- 在本示例中,已使用ctypedef类。这是因为,在Python头文件中,PyComplexObject结构体声明为:
typedef struct { ... } PyComplexObject;
- 除了扩展类型的名称,还指定了其类型对象所在的模块。请参阅下面的隐式导入部分。
- 当声明外部扩展类型时,不要声明任何方法。为了调用它们,不需要声明方法,因为调用是Python方法调用。另外,和struct和union一样,如果你的扩展类声明在一个cdef extern from block中,你只需要声明你想要访问的那些C成员。
隐式导入
Cython要求您在extern扩展类声明中包含一个模块名称,例如:
cdef extern class MyModule.Spam: ...
类型对象将从指定的模块隐式导入,并绑定到此模块中的对应名称。换句话说,在这个例子中隐式:
from MyModule import Spam
语句将在模块加载时执行。
模块名称可以是点名称,以指向包层次结构中的模块,例如:
cdef extern class My.Nested.Package.Spam: ...
您还可以使用as子句指定要导入类型的备用名称,例如:
cdef extern class My.Nested.Package.Spam as Yummy: ...
它对应于隐式import语句:
from My.Nested.Package import Spam as Yummy
类型名称与构造函数名称
在Cython模块中,扩展类型的名称用于两个不同的目的。当在表达式中使用时,它指的是保存类型的构造函数(即其类型对象)的模块级全局变量。然而,它也可以用作一个C类型名称声明变量,参数和返回该类型的值。
当您声明:
cdef extern class MyModule.Spam: ...
Spam名称服务这两个角色。可能有其他名称可以引用构造函数,但只有Spam可以用作类型名称。例如,如果您要显式导入MyModule,则可以使用MyModule.Spam()创建一个Spam实例,但您不能将MyModule.Spam用作类型名称。
当使用as子句时,as子句中指定的名称也接管两个角色。所以如果你声明:
cdef extern class MyModule.Spam as Yummy: ...
然后Yummy成为类型名称和构造函数的名称。再次,还有其他方法可以来自定义构造函数,但只有Yummy可用作类型名称。
文章的脚注信息由WordPress的wp-posturl插件自动生成