在Python中,一切都是对象。 具体来说是什么意思? 在最基本的层面上,一个对象具有三样东西
- 标识(id):对象的标识将其与其他对象区分开来,并由id内置函数提供
- 属性值(value):对象的值就是与之关联的数据,可以通过点表示法进行访问。通常,Python将对象的数据放在名为__ dict __的内部实例字典中。
- 类型(type):对象指定该类型的对象表现出的行为。 这些行为可以通过称为方法的特殊功能进行访问。 当在对象上调用方法时,类型负责创建和销毁其对象,对其进行初始化以及更新其值。Python允许我们使用class语句在Python代码中创建新类型。
Python类型系统
![v2-c0a9554135605ca051350b4d5682d918_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=b6d35145-c32f-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-c0a9554135605ca051350b4d5682d918_b.jpg)
我们知道Python的面向对象编程中,允许用户自定义的数据类型,在实例化Python的类对象时,该实例的属性值会保存到其内部dict中,即__ dict __属性,具有原生Python语义的类,其对象实例化前由Python解析器执行并经历如下过程(有些Python读物将这个过程步骤2到步骤4叫动态分派(Dynamic Dispatch),这里仅给出总结性的描述)
![v2-57344fedfdde91c9d67cdebb718495ce_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=b6d35145-c32f-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-57344fedfdde91c9d67cdebb718495ce_b.jpg)
(首次加载序列化mashling格式的字节码)→从字节码缓存文件中解析→实例属性的类型动态检查→确定实例属性类型(类属性的尺寸)→为其在实例化分配堆内存空间
- 内置类型(Builtin Type):像object,list,dict,file,int float的数据类型,我们称为Python的内置数据类型,他们是通过Python的底层C接口实现的,并且默认整合到Python的运行时环境。
- 用户定义类型(User Defined Type):在Python中通过使用关键字class定义的用户自定义数据类型。需要强调的该类型是具备Python语义的类(也就是纯Python代码定义的类),因此需要更多的开销。
- 扩展数据类型(Extend Type):通过Pytho底层C接口定义的数据类型,例如C的关键字定义的struct,C++的关键字定义的class/struct定义的类,Cython的cdef class关键字定义的类.
不同类型的组合关系:
- 在原生的Python代码中,不同Builtin Type(或扩展类型)以任意数量且不同类型的组合出包罗万象的User Definded Type,
- 在原生的Python代码中 任意不同的User Defined Type的组合可以组合出规模更大的User Defined Type,组合的规模越大内存开销和实例化的时间开销就越大,并且在所有情况组合中,纯Python定义/组合的User Defined Type是最慢的。
- 由于Builtin Type和Extend Type在C/C++底层定义并且在编译时类型静态绑定,因此初始化时绕过Python解析器的动态分派过程,直接提供高效的C级别内存分配和内存访问,因此以C/C++级别中,以任意不同的Builtin-Type或Extend Type组合出规模更大的Extend Type,性能相比User Defined Type仍然高出许多。
要直接在C/C++中实现Python的扩展类型需要开发者熟悉Python C底层接口,因此并不适合没有C/C++经验的开发者,然而Cython已经高度集成几乎所有C/C++的主流特性。能够以类似Python的语法快速定义出C/C++级别的Python扩展类型。
Cython中的扩展类
下面是一个简单的水果类,每种水果都有名称,数量,价格,Fruit类在纯Python的解析级别上定义(默认在.py文件中),当然也可以由Cython编译为C扩展
class
在纯Python代码定义中,我们Frui对象实例化的时间开销是265ns
![v2-c78b5b90edec25735405b57fc6b13f8e_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=b6d35145-c32f-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-c78b5b90edec25735405b57fc6b13f8e_b.jpg)
在Cython编译后的Fruit2对象(类定义是一样,类名称不一样),时间开销是217ns
![v2-7ceaa4b02264edc95cf286b2897689fe_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=b6d35145-c32f-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-7ceaa4b02264edc95cf286b2897689fe_b.jpg)
当我们使用cython将Fruit类编译为C时,生成的类只是Python在C级别的用户自定义类型,而不是扩展类型。 当Cython将其编译为C时,它仍然可以使用所有操作的动态分派的通用Python对象来实现。 所生成的代码大量使用Python底层C应用接口,并且与使用纯Python代码定义此类时的Python解释器进行相同的调用。
- 由于消除了解释器解读字节码的开销,因此Cython版本的Fruit类仅具有很小的性能提升。
- 它不能从任何静态类型中受益,因为Fruit类的C实现仍然必须依靠动态分派来在运行时解析类型。
![v2-310032d0d723e04768482c8f71a9ab60_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=b6d35145-c32f-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-310032d0d723e04768482c8f71a9ab60_b.jpg)
![v2-9203589e7184fc568e5862300dfc6895_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=b6d35145-c32f-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-9203589e7184fc568e5862300dfc6895_b.jpg)
要将上面的Python类转化为C级别的扩展类,只需对Fruit类做以下代码修改
- 使用cdef关键字修饰class关键字,就是告知Cython编译器,将类编译成C级别的扩展类型
- 在类级别下,使用cdef关键字声明类属性,并且为类属性静态指定C数据类型,目地类实例化时是绕过Python解释其
%%
通过对Cython版本的Fruit类的类属性声明语句进行类型静态化后,我们实例化该实例的时间开销非常棒,最好的成绩是85.5ns
![v2-d49f308bf51fad4d756c9010d0905b50_b.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=b6d35145-c32f-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-d49f308bf51fad4d756c9010d0905b50_b.jpg)
Cython编译的扩展类型比纯Python定义的用户定义类型快3倍多,比Cython编译的用户定义类型快2倍
![v2-f124f71726d1daef64740e4c6cc00e38_b.jpg](http://img-01.proxy.5ce.com/view/image?&type=2&guid=b6d35145-c32f-eb11-8da9-e4434bdf6706&url=https://pic1.zhimg.com/v2-f124f71726d1daef64740e4c6cc00e38_b.jpg)
目前这个Cython版本的Fruit类设计还不是最优的版本,我们以后探讨有关Cython面向对象编程中的更多内容。