扩展类的 property
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