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插件自动生成
微信扫一扫,打赏作者吧~


