IT狗工作室

第6篇:Cython的面向对象编程(中篇)

2020-04-21  本文已影响0人  铁甲万能狗

我们在前一篇已经说过Python版本实现的类和Cython版本的类的区别,其中一个最为显著的特征是Python类实例的属性数据存放一个内部字典中即__ dict__,我们了解dict的底层是基于哈系表

例如 Fruit的实例下,如下图查看所有实例属性的数据

ss8.png
另外我在前面的随笔说过,纯Python版本的面向对象编程中,不存在封装一说,因为Python的类是基于解析语言构建,Python运行时系统并没有像C++/JAVA提供对类实例的属性/方法的访问控制

我们先来查看原生Python定义的Fruit类,我们可以通电点号访问符实访问Fruit实例的属性,同时可以修改其属性


因此Python定义的类默认所有属性是公开,并且可修改的,甚至Python运行时还允许我们动态添加新的类属性变量,如下操作,动态添加weight属性

Python类性能问题

上面的例子,其实就突出一点Python类强调运行时的动态性,因为Python类的实例属性存储内部dict中,而dict的本质就是哈系表,但纯Python类实例的属性数量规模不宜过大,因为哈系表元素(类实例属性的引用)的数量超过负载系数,会导致字典重散列,在典型的哈希表的内存模型中,重散列(Rehashing)意味着

  1. 带来巨大的内存消耗,因为重散列意味着,运行时频繁添加或删除类实例属性,可能导致已分配内存利用率低下
  2. 字典内部若类实例属性存在散列冲突,即访问或修改某些实例属性的效率时间复杂度可能为O(n)

扩展阅读:如果你对哈希表基本原理不熟悉,可以阅读我之前写的随笔《C++哈希表-负载系数与重散列》,那篇是非收费文章..

Cython类的访问控制

接下来,我们讨论Cython版本的类Fruit,这是一个纯Cython的扩展类,位于一个cy_fruit.pyx的文件中

#cython:language_level=3

cdef class Fruit(object):
    '''Fruit Type'''
    
    cdef str name
    cdef double qty
    cdef double price
    
    def __init__(self,nm,qt,pc):
          self.name=nm
          self.qty=qt
          self.price=pc

    def amount(self):
          return  self.qty*self.price

下面是一个错误示例的演示



当我们尝试对一个C实现的扩展类的实例调用一个类似Python类的内部字典属性__ dict__会发生报错,会提示 no attribute '__ dict __'错误,


ss8.png
由于Cython关键字 cdef class就是告知Cython编译器将Fruit类的定义编译为C版本的类Fruit,若你了解C的话,C版本的Fruit类定义可以类比下面的C代码,
typedef struct{
    char* name;
    double qty;
    double price;
    
}    Fruit;

double amount(Fruit* self){
      return self->qty*self->price;
}

没错,cdef class 在底层就做了这些底层的操作:

  1. 对于C来说,Fruit类的本体就是一个具备以上字段的结构体,
  2. Fruit类在实例化时,必须明确告知编译器它自己内部字段(属性)的类型,编译器根据字段的类型能计算出整个Fruit结构体实例化时该为它分配多大的内存量。

当然上面不是完全的描述,我们在通过例子去进一步分析cdef关键字的对Cython类的操作。

反例演示:当我们尝试通过点号访问符访问C级别的Fruit类实例属性,哦~!一一报错,这又是何解呢?你可以自行思考一下。


此时,你应该想到Cython的关键字cdef class集成了C++ class的特性就是类成员访问控制,C++的关键字class就是提供了访问控制的struct的加强版。这里需要说一下:Cython与C++访问控制修饰符的对应关系。

再多说一句,我们知道C的struct是简单的聚合类型,也就是对基本数据类型聚合扩展新的用户自定义类型而已,且不提供任何访问限制。而Python 的C底层的API实现中使用最为频繁的函数参数就是struct PyObject*,只要Python解析器在运行时解析任何动态变量都会首先指向PyObject这个C版本的结构体指针。这恰好说明Python运行时系统对类实例属性没有访问控制的根本原因。

Ok,我们通过一些例子加深并巩固上面的概念,请看下面添加访问控制修饰符后的Cython类

#cython:language_level=3

cdef class Fruit(object):
    '''Fruit Type'''
    
    cdef readonly str name
    cdef public double qty
    cdef readonly double price
    
    def __init__(self,nm,qt,pc):
          self.name=nm
          self.qty=qt
          self.price=pc

    cdef public double amount(self):
          return  self.qty*self.price

下面的调用Cython版本的Fruit类,类实例属性name和类实例属性qty都能被外部的Python代码调用,如下图,因为readonly和public允许外部Python代码访问其类属性

值得一提的是,当readonly关键字修饰类属性时,其实很想Java中的final关键字的原理一样:类实例属性值一旦在初始化之后便不能更改,但允许调用代码访问。我们可以通过下面示例来演示。

public关键字允许外部Python代码调用被public修饰的类实例属性,并且可以自由修改类实例属性的值

更新中.....

上一篇下一篇

猜你喜欢

热点阅读