Python 的 property 非常的易用且强大,可以让我们精确地控制某个属性的修改,而 Cython 也是支持 property 的,但是方式有些不一样。不过在介绍 Cython 的 property 之前,我们先来看看 Python 的 property。
class Girl: def __init__(self): self.name = None @property def x(self): # 不需要我们对 x 进行调用 # 直接通过 self.x 即可获取返回值 # 让函数像属性一样直接获取 return self.name @x.setter def x(self, value): # 当执行self.x = "古明地觉"的时候,会调用这个函数 # "古明地觉"就会传递给这里的value self.name = value @x.deleter def x(self): # 执行del self.x的时候,就会调用这个函数 print("被调用了") del self.name girl = Girl() print(girl.x) # None girl.x = "古明地觉" print(girl.x) # 古明地觉 del girl.x # 被调用了
这里是通过装饰器的方式实现的,三个函数都是一样的名字,除了使用装饰器,我们还可以这么做。
class Girl: def __init__(self): self.name = None def fget(self): return self.name def fset(self, value): self.name = value def fdel(self): print("被调用了") del self.name # 传递三个函数即可,除此之外还有一个doc属性 x = property(fget, fset, fdel, doc="这是property") girl = Girl() print(girl.x) # None girl.x = "古明地觉" print(girl.x) # 古明地觉 del girl.x # 被调用了
所以 property 就是让我们像访问属性一样访问函数,那么它内部是怎么做到的呢?不用想,肯定是通过描述符。
下面我们来手动模拟一下。
# 模仿类property,实现与其一样的功能 class MyProperty: def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.doc = doc def __get__(self, instance, owner): return self.fget(instance) def __set__(self, instance, value): return self.fset(instance, value) def __delete__(self, instance): return self.fdel(instance) def setter(self, func): return type(self)(self.fget, func, self.fdel, self.doc) def deleter(self, func): return type(self)(self.fget, self.fset, func, self.doc) class Girl1: def __init__(self): self.name = None @MyProperty def x(self): return self.name @x.setter def x(self, value): self.name = value @x.deleter def x(self): print("被调用了") del self.name class Girl2: def __init__(self): self.name = None def fget(self): return self.name def fset(self, value): self.name = value def fdel(self): print("被调用了") del self.name x = MyProperty(fget, fset, fdel) girl1 = Girl1() print(girl1.x) # None girl1.x = "古明地觉" print(girl1.x) # 古明地觉 del girl1.x # 被调用了 girl2 = Girl2() print(girl2.x) # None girl2.x = "古明地觉" print(girl2.x) # 古明地觉 del girl2.x # 被调用了
我们通过描述符的方式手动实现了 property 的功能,描述符事实上在 Python 解释器的层面也用的非常多。当一个类定义了 __get__ 时,它的实例对象被称为非数据描述符;如果又实现了 __set__,那么它的实例对象被称为数据描述符。
而函数就是一个非数据描述符,我们看一下函数的类型对象在底层的定义:
我们看到函数的类型对象实现了 __get__,那么函数显然就是一个描述符。当我们在调用类的成员函数时,底层就会执行 func_descr_get。
在里面会进行检测,如果 obj 为空,说明是类获取的成员函数,那么直接将 func 返回。所以此时得到的 func 就是函数本身,和定义在全局的普通函数并无二致。
但如果 obj 不为空,说明是实例获取的成员函数,那么会调用 PyMethod_New 将成员函数和实例绑定在一起,封装成一个方法并返回。后续解释器发现调用的不是函数,而是方法时,会将方法内部的实例和我们传递的参数组合起来,一起传给方法内部的成员函数进行调用。
所以都说实例在调用的时候会将自身作为第一个参数自动传给 self,相信这背后的原理你已经了解了,我们用代码验证一下:
class A: def __init__(self): self.a = 1 self.b = 2 def foo(self): pass # 类来获取,拿到的就是函数本身 print(A.foo.__class__) """ <class 'function'> """ a = A() # 实例获取,会将函数和自身绑定在一起 # 封装成一个方法 print(a.foo.__class__) """ <class 'method'> """ # 我们通过方法也可以拿到对应的实例和成员函数 m = a.foo print(m.__self__ is a) # True print(m.__self__.a, m.__self__.b) # 1 2 print(m.__func__ is A.foo) # True
好了,到目前为止算是跑题了,不过也无所谓,想到啥说啥,而且探寻一下 Python 背后的秘密也是很有趣的(其实是害怕文章太水了)。
下面来看一看 Cython 中的 property,针对扩展类的 property,Cython 有着不同的语法,但是实现了相同的结果。
cdef class Girl: cdef str name def __init__(self): self.name = None property x: def __get__(self): return self.name def __set__(self, value): self.name = value # __del__ 在 Cython 中不表示析构,析构是 __dealloc__ # 但是在 __del__ 里面我们并没有 del self.name # 原因就是扩展类的属性不可以删除,删除的话会报出以下错误 # Cannot delete C attribute of extension type # 因此 __del__ 很少使用,一般都是 __get__ 和 __set__ def __del__(self): print("__del___")
下面我们来测试一下:
import pyximport pyximport.install(language_level=3) import cython_test g = cython_test.Girl() print(g.x) # None g.x = "古明地觉" print(g.x) # 古明地觉 del g.x # __del___
所以 Cython 将 property 和描述符结合在一起了,但是实现起来感觉更方便了。
下一篇文章来介绍魔法方法,魔法方法算是 Python 中非常强大的一个特性, Python 将每一个操作符都抽象成了对应的魔法方法,也正因为如此 numpy 才得以很好地实现。那么在 Cython 中,魔法方法是如何体现的呢?下一篇文章来介绍。
E N D