专栏首页AstropeakPython 描述符是什么?以及如何实现

Python 描述符是什么?以及如何实现

先看一个例子,@property。被@property修饰的成员函数,将变为一个描述符。这是最简单的创建描述符的方式。

class Foo:
  @property
  def attr(self):
    print('getting attr')
    return 'attr value'

  def bar(self): pass

foo = Foo()

上面这个例子中, attr 是类 Foo 的一个成员函数,可通过语句 foo.attr() 被调用。 但当它被 @property 修饰后,这个成员函数将不再是一个函数,而变为一个描述符。 bar 是一个未被修饰的成员函数。 type(Foo.attr)type(Foo.bar) 的结果分别为:

<type 'property'>
<type 'instancemethod'>

attr 的类型为 property (注:一个 property 类型的对象总是一个描述符), bar 的类型为 instancemethod ,也即一个常规的成员函数。

此时 attr 将无法再被调用,当尝试调用它时,语句 foo.attr() 将抛出错误:

TypeError: 'str' object is not callable

让我们来理解这个错误。

首先来看 foo.attr 的值:

attr value

其类型 type(foo.attr)

str

foo.attr 的类型为 str ,因此便有了以上的错误,一个 str 对象无法被调用。其值为'attr value',正好是原始 attr 函数的返回值。 因此语句 foo.attr 实际上触发了原始 attr 函数的调用,并且将函数的返回值作为其值。实际上语句 print(foo.attr) 的输出为:

getting attr
attr value

进一步验证了执行语句 foo.attr 时,原始的 attr 函数被调用。

发生了什么?当执行一个访问对象属性的语句 foo.attr 时,结果一个函数调用被触发!这便是描述符的作用:将属性访问转变为函数调用,并由这个函数来控制这个属性的值(也即函数的返回值),以及在返回值前做定制化的操作。此时可以给描述符一个简要定义:

描述符是类的一个属性,控制类实例对象访问这个属性时如何返回值及做哪些额外操作

这留给程序员的空间是巨大的。。

描述符协议

任何实现了描述符协议的类都可以作为描述符类。描述符协议为一组成员函数定义,包括:

函数

作用

返回值

是否必须

__get__(self, obj, type)

获取属性值

属性的值

__set__(self, obj, value)

设置属性的值

None

__delete__(self, obj)

删除属性

None

如果一个类实现了以上成员函数,则它便是一个描述符类,其实例对象便是一个描述符

下面是一个自定义的描述符的实现。

class MyDescriptor:
  def __init__(self):
    self.data = None
  def __get__(self, obj, type):
    print('get called')
    return self.data
  def __set__(self, obj, value):
    print('set called')
    self.data = value
  def __delete__(self, obj):
    print('delete called')
    del self.data

class Foo:
  attr = MyDescriptor()

foo = Foo()

示例中 MyDescriptor 实现了描述符协议(也即实现了 __get__, __set__, __delete__ 函数),因此其为一个描述符类。 Fooattr 属性为 MyDescriptor 类的实例对象,因此它是一个描述符。

print(foo.attr) 的输出为:

get called
None

可见当访问 fooattr 属性时, MyDescriptor__get__ 函数被调用。

foo.attr = 'new value' 的输出为:

set called

可见当为 attr 设置一个新值时, MyDescriptor__set__ 函数被调用。

再运行 print(foo.attr) ,输出为:

get called
new value

可见新值已被设置。

del foo.attr 的输出为:

delete called

可见当为删除属性 attr 时, MyDescriptor__delete__ 函数被调用。

再执行 print(foo.attr)AttributeError 被抛出:

get called
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "1.py", line 6, in __get__
    return self.data
AttributeError: 'MyDescriptor' object has no attribute 'data'

可见属性 attr 已被删除。

参数意义

__get__(self, obj, type) 函数各个参数的意义为:

参数

意义

例子中的对应

self

描述符对象本身

Foo.attr

obj

使用描述符的对象实例

foo

type

obj的类型

Foo

__set__(self, obj, value) 函数的self和obj参数的意义同 __get__ ,value的意义为:

参数

意义

例子中的对应

value

属性的新值

'new value'

__delete__(self, obj) 函数的self和obj参数的意义同 __get__

(全文完)

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 基于隐马尔科夫模型的中文分词方法

    本文主要讲述隐马尔科夫模及其在中文分词中的应用。 基于中文分词语料库,建立中文分词的隐马尔科夫模型,最后用维特比方法进行求解。

    用户2176428
  • 掌握Python 装饰器,其实只需要一盏茶的功夫

    用户2176428
  • Spring使用 --- 基本概念(二):AOP,面向方面编程

    用户2176428
  • 【首发】phpmyadmin4.8.1后台getshell

    官网下载的最新版,文件名是phpMyAdmin-4.8.1-all-languages.zip

    ChaMd5安全团队
  • CSS > 高清缩放原理分析

    前端博客 : alili.tech
  • UITableViewCell中添加复制逻辑

    程序员不务正业
  • Python 中几种属性访问的区别

    python的提供一系列和属性访问有关的特殊方法:__get__, __getattr__, __getattribute__, __getitem__。本文阐...

    Python猫
  • 【CSS进阶】试试酷炫的 3D 视角

    Sb_Coco
  • CSS实现水平垂直居中的1010种方式(史上最全)

    要实现上图的效果看似很简单,实则暗藏玄机,本文总结了一下CSS实现水平垂直居中的方式大概有下面这些,本文将逐一介绍一下,我将本文整理成了一个github仓库,欢...

    前端博客 : alili.tech
  • 算法一看就懂之「 堆栈 」

    上一篇咱们聊完了数据结构中最基础的「 数组 」和「 链表 」,今天咱们再来继续看看「 堆栈 」吧,我写技术文章很少 show code,所以经常有人吐槽。好吧,...

    奎哥

扫码关注云+社区

领取腾讯云代金券