【翻译】Cython教程2_Cython语法扩展

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

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可用作类型名称。

 

原创文章,转载请注明: 转载自勤奋的小青蛙
本文链接地址: 【翻译】Cython教程2_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: