当前位置: 首页 > 后端技术 > Python

通过cdef静态类型声明

时间:2023-03-26 17:43:58 Python

wedge首先,在Python中声明变量的方式在Cython中同样可以使用,因为Python代码也是合法的Cython代码。a=[xforxinrange(12)]b=aa[3]=42.0assertb[3]==42.0a="xxx"assertisinstance(b,list)在Cython中,没有类型化的动态变量行为与Python中完全相同,b和a都通过赋值语句b=a指向同一个列表。在a[3]=42.0之后,b[3]==42.0也为真,因此断言成立。即使后面修改了a,也只是指向了一个新的对象,并调整了相应的引用计数。但是b完全不受影响,所以b还是指向了一个列表。这是完全合法有效的Python代码。对于静态类型变量,我们在Cython中用cdef关键字声明,如:cdefinticdefintjcdeffloatk#我们看到j=0i=jk=12.0j=2就像混合使用Python和C*Kasserti!=j除了上面的变量声明,其他的使用方法和Python一样。当然,对于简单的赋值,基本上所有的语言都是差不多的。但是Python的一些内置函数、类、关键字等是可以直接使用的,因为我们可以直接在Cython中编写Python代码,Cython是Python的一个超集。但是有一点需要注意:我们上面创建的变量i、j、k是C中的类型(int、float比较特殊,后面会解释),它们的含义最终要遵循C的标准。不仅如此,就连使用cdef声明变量的方式也是符合C标准的。cdefinti,j,kcdeffloatx,y#declareandassigncdefinta=1,b=2cdeffloatc=3.0,b=4.1而且在函数内部,cdef也是缩进的,他们声明变量也是局部变量.deffoo():#这里的cdef是函数内部缩进的cdefinticdefintN=2000#a没有初始值,默认为0,即0.0cdeffloata,b=2.1cdef也可以使用类似以Python上下文管理器的方式。deffoo():#这种声明方式也是可以的#完全等同于上面的方式cdef:intiintN=2000floata,b=2.1#但是声明变量的时候要注意缩进#Python是对的缩进是有讲究的,它指定了范围#所以Cython在语法上还是保留了Python的风格,所以使用cdef声明变量非常简单,格式:cdef类型变量名。当然,你也可以同时赋初值。一旦使用了cdef静态声明,后面给变量赋值的时候就不能为所欲为了,比如:#如果是动态声明,下面这些都是合法的#a可以指向任何对象,没有limita=123a=[]#但是如果是静态声明#那么b的类型必须是整数cdefintb=123#给a赋值一个list会导致编译错误b=[]#编译错误也是因为在编译阶段就可以检测到类型并分配内存,所以执行速度会很快。static和const如果你懂C,想一想:假设你想在函数中返回一个指向局部变量的指针,并且在接收到该指针后,你仍然可以访问该指针指向的值,你应该怎么做这次?我们知道C函数中的变量是在栈上分配的(没有使用malloc函数,而是直接创建了一个变量),函数结束后变量对应的值就被销毁了,所以即使返回一个指针在这一次是没有意义的。虽然有时候,指针返回后,指向的内存仍然可以访问,但这只是目前使用的编译器比较笨,在编译时没有检测到。如果是更高级的编译器,访问时会报段错误或打印出错误的值;而更高级的编译器甚至不允许指针返回,因为指针指向的内存已经被回收了,那你要这个指针干什么?所以指针不允许返回。而如果要这样做,只需要在变量声明前加上static关键字,比如staticinti,这样变量i就不会分配到栈区,而是分配到数据区。数据区变量的生命周期不会随着函数的结束而结束,而是随着整个程序的结束而结束。不幸的是,static不是有效的Cython关键字,因此我们不能在Cython中声明C静态变量。除了static,C中还有一个const,用来声明常量。一旦使用了const声明,比如constinti=3,那么这个i以后就不能修改了。Cython中支持const,但是只能在定义函数参数的时候使用,我们在介绍函数的时候再说。所以C的static和const目前在Cython中不需要过多关注。C类型我们在上面声明变量的时候,指定的类型是int和float,而int和float在Python和C中都有使用,那么它用的是谁的呢?其实就是上面说的,用到了C的int和float。至于原因,我们以后再说。Cython可以使用的C类型不仅有int和float,还有short、int、long、unsignedshort、longlong、size_t、ssize_t等基本类型,声明变量的方式是cdef类型变量名。声明时可能会或可能不会分配初始值。除了基本类型外,还支持指针、数组、定义的类型别名、结构体、社区、函数指针等,我们稍后会详细说明。Cython的自动类型推断Cython也会自动对函数体中没有类型声明的变量进行类型推断。比如在for循环中,所有浮点数相加,不涉及其他类型的变量,那么Cython会自动对变量进行类型推断,在推断的时候,你会发现这个变量可以优化为静态类型的double.但是程序显然不能针对动态类型语言进行非常智能的优化。默认情况下,Cython只会在确认这样做不会改变代码块的语义后才执行类型推断。看一个简单的函数:defautomatic_inference():i=1d=2.0c=3+4jr=i*d+creturnr在这个例子中,Cython将标记分配给变量i,c,r的值是一个通用的Python对象。尽管这些对象的类型与C的类型具有高度相似性,但Cython保守地推断i可能无法用C的整数类型表示(C的整数有范围,而Python没有并且可以是无限的),因此它将作为符合Python代码语义的Python对象。对于d=2.0,可以自动推断为Cdouble,因为Python浮点数对应的值存储在最底层的double中。所以最后,对于开发者来说,变量d看起来像是一个Python对象,但是Cython在执行的时候会把它当作一个Cdouble来处理,以提高性能。这就是为什么即使我们编写纯Python代码,Cython编译器也可以优化,因为它会推断。但显然,我们不应该让Cython编译器推断,而应该明确指定变量的类型。当然,如果你非要让Cython编译器去猜测,也是可以的,你也可以使用infer_types编译器指令,让Cython在某些可能改变Python语义的情况下,有更大的空间去推断一个变量的类型代码。cimportcython@cython.infer_types(True)defmore_inference():i=1d=2.0c=3+4jr=i*d+creturnr这里出现了一个新的关键字cimport,它的含义我们后面会说,你只需要知道它和import关键字一样,是用来导入模块的。然后我们通过装饰器@cython.infer_types(True)开始了相应的类型推断,这就给Cython留下了更多的猜测空间。当Cython支持更多推理时,变量i将被类型化为C整数;d和之前一样是double,c和r都是复数变量,复数还是使用Python的复数类型。但请注意:这并不意味着启用infer_types后一切都会好起来的;我们知道,在不指定infer_types的情况下,Cython推断类型显然是在保证程序正确执行的情况下最安全的优化方式,不能为了优化而优化。它会导致程序出错。显然,在正确性和效率之间,正确性是第一位的。但是由于C的整型存在溢出问题,Cython不会擅自使用。但是我们通过infer_types启用更多类型推断,让Cython在不改变语义的情况下使用C的类型。但是它不知道溢出的问题,所以在这种情况下,我们有责任确保不会发生溢出。对于一个函数,如果启用了这种类型推断,我们可以使用infer_types装饰器。但同样,我们应该手动指定类型而不是让Cython编译器猜测,因为我们是代码编写者,我们最了解类型。所以infer_types装饰器在工作中并不常用,如果想提高速度,必须提前显式指定变量的类型。总结以上就是在Cython中如何使用cdef关键字静态声明一个变量。提前指定类型非常重要。一旦确定了类型,生成的机器码数量就会少很多,从而提高速度。C类型变量的计算速度比Python快很多,这也是为什么int和float会选择C类型的原因。除了int和float,Cython也支持其他类型的C,包括复杂结构如作为指针、结构和社区。但是C的整数类型有个问题,就是它有一个范围,我们在使用的时候一定要保证它不会溢出。因此,Cython在自动进行类型推断时,只要可以改变语义,它就不会擅自使用C整数,即使分配的整数很小。这时候可以使用infer_types装饰器,给Cython留出更多的猜测空间。但同样,我们不应该让Cython编译器猜测,是否溢出由我们来决定。如果你能保证整数不会超过int所能表示的最大范围,那么就把变量声明为int;如果无法表示int,则使用longlong;如果不能表示,那就没办法了,只能用PythonInteger。Python整数的使用方式是直接动态声明,不用cdef。所以如果要声明一个变量为整型,可以直接使用ssize_t,相当于longlong。在工作中,很少有整数能超过ssize_t的最大范围。#需要保证赋给a的整数#不会超过ssize_t可以表示的最大范围cdefssize_ta#b可能非常非常大,也可能是负数#连ssize_t都不能表示#此时动态声明是必须的,但是很少遇到这么大的整数b=...同样,提前指定类型对提高速度有非常重要的作用。因此,在声明变量时一定要指定类型,尤其是涉及到数值计算时。只是此时使用的是C的类型,需要额外考虑整数溢出的情况,但如果声明类型为ssize_t,则很少会出现溢出。以上是cdef的用法,以上就是本次分享的全部内容。想了解更多python知识,请前往公众号:Python编程学习圈,发“J”免费领取,每日干货分享