扩展类的 property

简介: 扩展类的 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


相关文章
|
2月前
|
存储 算法 编译器
c++--类(上)
c++--类(上)
|
2月前
|
开发框架 .NET 编译器
总结一下 C# 如何自定义特性 Attribute 并进行应用
总结一下 C# 如何自定义特性 Attribute 并进行应用
|
编译器 程序员 C++
Qt之自定义属性Q_PROPERTY专题(1)充分理解其概念以及用途
Qt之自定义属性Q_PROPERTY专题(1)充分理解其概念以及用途
2357 0
Qt之自定义属性Q_PROPERTY专题(1)充分理解其概念以及用途
|
数据库 数据安全/隐私保护 Python
property、魔法方法和继承
property、魔法方法和继承
.NET Core反射获取带有自定义特性的类,通过依赖注入根据Attribute元数据信息调用对应的方法
.NET Core反射获取带有自定义特性的类,通过依赖注入根据Attribute元数据信息调用对应的方法
161 0
CLASSPATH环境属性
CLASSPATH环境属性自制脑图
65 0
CLASSPATH环境属性
|
存储 分布式计算 自然语言处理
Hadoop序列化、概述、自定义bean对象实现序列化接口(Writable)、序列化案例实操、编写流量统计的Bean对象、编写Mapper类、编写Reducer类、编写Driver驱动类
Hadoop序列化、概述、自定义bean对象实现序列化接口(Writable)、序列化案例实操、编写流量统计的Bean对象、编写Mapper类、编写Reducer类、编写Driver驱动类
Hadoop序列化、概述、自定义bean对象实现序列化接口(Writable)、序列化案例实操、编写流量统计的Bean对象、编写Mapper类、编写Reducer类、编写Driver驱动类
|
C#
C#--Linkeddlist类
C#--Linkeddlist类
71 0
|
Java 开发者
CLASSPATH环境属性 | 学习笔记
快速学习CLASSPATH环境属性
160 0
CLASSPATH环境属性 | 学习笔记
|
Python
Python高级语法4:类对象和实例对象访问属性的区别和property属性
Python高级语法4:类对象和实例对象访问属性的区别和property属性
149 0