python中描述符的学习

什么是描述符

描述符是Python新式类的关键点之一,它为对象属性提供强大的API,你可以认为描述符是表示对象属性的一个代理。当需要属性时,可根据你遇到的情况,通过描述符进行访问他(摘自Python核心编程)。

实例解析

  • 使用类方法创建描述符

就是将某种特殊类型的类的实例指派给另一个类的属性(注意:这里是类属性,而不是对象属性)。而这种特殊类型的类就是实现了__get____set__,__delete__的新式类(即继承object)。

__get__(self, object, type)                  # 用于得到一个属性的值
__set__(self, obj, val)                      # 用于为一个属性赋值
__delete__(self, obj)                        # 删除某个属性时被调用,但很少用到

其中只实现了__set__()方法的被当做方法描述符,或者是非数据描述符。那些同时实现了__set__()__get__()方法的类被称作数据描述符。

首先定义一个数据描述符类

# coding=utf-8
class Descriptor(object):
    def __init__(self, value):
        self.value = value

    def __get__(self, instance, owner):
        print "访问属性"
        return self.value

    def __set__(self, instance, value):
        print "设置属性值"
        self.value = value

再来定义一个调用数据描述符的类

class Myclass(object):
    desc = Descriptor(5)

if __name__ == '__main__':
    print Myclass.desc

访问结果为:

访问属性
5

发现访问Myclass的desc属性时,调用了描述符的__get__()方法。这就达到了描述符的作用(可以改变对象属性的访问)。

调用原理:对于类属性描述符,如果解析器发现属性x是一个描述符的话,在内部通过type.__getattribute__()(访问属性时无条件调用,最先调用),它能把Class.x转换成Class.__dict__[‘x’].__get__(None, Class)来访问

上面把描述符定义成了类属性,那我们要把他定义成对象属性会有什么样的异同呢?

# coding=utf-8
class Descriptor(object):
    def __init__(self, value):
        self.value = value

    def __get__(self, instance, owner):
        print "访问属性"
        return self.value

    def __set__(self, instance, value):
        print "设置属性值"
        self.value = value


class Myclass(object):
    def __init__(self):
        self.desc = Descriptor(5)

if __name__ == '__main__':
    myclass = Myclass()
    print myclass.desc

输出结果为:

<__main__.Descriptor object at 0x0000000002DFAC18>

并没有像我们预期的那样调用__get__()方法,只是说他是Descriptor的一个对象。

这是因为当访问实例描述符对象时,obj.__getattribute__()会将myclass.desc转换为type(myclass).__dict__['desc'].__get__(myclass, type(myclass)),即到类属性中去寻找desc,并调用他的__get__()方法。而Myclass类中没有desc属性,所以无法访调用到__get__方法.
描述符是一个类属性,必须定义在类的层次上, 而不能单纯的定义为对象属性。

那么当定义类属性描述符对象和实例属性名字相同时,会有什么样的效果呢?

# coding=utf-8
class Descriptor(object):
    def __init__(self, value):
        self.value = value

    def __get__(self, instance, owner):
        print "访问属性"
        return self.value

    def __set__(self, instance, value):
        print "设置属性值"
        self.value = value


class Myclass(object):
    desc = Descriptor(5)

    def __init__(self, desc):
        self.desc = desc     # 与类属性同名的属性

if __name__ == '__main__':
    myclass = Myclass(3)
    print myclass.desc

运行结果如下:

设置属性值
访问属性
3

可以看出初始化时访问了描述符的__set__()方法,访问属性值时访问了描述符的__get__()方法。这样为什么又调用描述符的方法了呢?
为了解释这个问题,我们要先说一下在python中访问一个属性的优先级,如下:

  • 类属性
  • 数据描述符
  • 实例属性
  • 非数据描述符
  • 默认为getattr()(找不到的情况下)

然后我们打印出上面代码类和实例的属性列表:

if __name__ == '__main__':
    myclass = Myclass(3)
    print "instance:  ", myclass.__dict__
    print "Class:    ", Myclass.__dict__

结果如下:

instance:   {}
Class:     {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Myclass' objects>, '__weakref__': <attribute '__weakref__' of 'Myclass' objects>, '__doc__': None, '__init__': <function __init__ at 0x00000000030F9438>, 'desc': <__main__.Descriptor object at 0x00000000030E8B70>}

可以发现实例对象的属性中并没有desc,而相反,类属性中却有它。这是为什么呢?

按照上面的属性访问优先级的理论,数据描述符 > 实例属性。当python发现实例对象的字典中有与定义的描述符有相同名字的对象时,描述符优先,会覆盖掉实例属性。python会改写默认的行为,去调用描述符的方法来代替。

我们来验证一下上面的理论,优先级实例属性 > 非数据描述符。首先我们定义一下非数据描述符(只有__get__()方法)

# coding=utf-8
class Descriptor(object):
    def __init__(self, value):
        self.value = value

    def __get__(self, instance, owner):
        print "访问属性"
        return self.value

    # def __set__(self, instance, value):
    #     print "设置属性值"
    #     self.value = value


class Myclass(object):
    desc = Descriptor(5)

    def __init__(self, desc):
        self.desc = desc     # 与类属性同名的属性

if __name__ == '__main__':
    myclass = Myclass(3)
    print myclass.desc
    print "instance:  ", myclass.__dict__
    print "Class:    ", Myclass.__dict__

运行一下:

3
instance:   {'desc': 3}
Class:     {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Myclass' objects>, '__weakref__': <attribute '__weakref__' of 'Myclass' objects>, '__doc__': None, '__init__': <function __init__ at 0x0000000002E393C8>, 'desc': <__main__.Descriptor object at 0x0000000002E28B00>}

可以看出,这种情况下访问实例属性,并没有调用描述符的__get__()方法。而是调用了本身的属性。可以看出理论是正确的。

  • 使用属性类型创建描述符

属性是一种有用的特殊类型的描述符。他们是用来处理所有对实例属性的访问,其工作方法和前面说过的描述符类似。通过使用 property(),可以轻松地为任意属性创建可用的描述符。

property内建函数有四个参数:property(fget=None, fset=None, fdel=None, doc=None)

fget:属性获取方法
fset:属性设置方法
fdel:属性删除方法
doc:文档描述

来看一下实现:

class PropertyDesc(object):
    def __init__(self):
        self._name = ''

    def fget(self):
        print "Getting: %s" % self._name
        return self._name
    
    def fset(self, value):
        print "Setting: %s" % value
        self._name = value

    def fdel(self):
        print "Deleting: %s" %self._name
        del self._name
    name = property(fget, fset, fdel, "I'm the property.")
if __name__ == '__main__':
    pro = PropertyDesc()
    pro.name = "haha"
    print pro.name
    del pro.name

结果如下:

Setting: haha
Getting: haha
haha
Deleting: haha

这样实现描述符,虽然简单。但属性多的话就造成代码臃肿不堪。

  • 使用属性修饰符创建描述符
class PropertyDesc(object):
    def __init__(self):
        self._name = ''

    @property
    def name(self):
        print "Getting: %s" % self._name
        return self._name

    @name.setter
    def name(self, value):
        print "Setting: %s" % value
        self._name = value

    @name.deleter
    def name(self):
        print "Deleting: %s" %self._name
        del self._name

if __name__ == '__main__':
    pro = PropertyDesc()
    pro.name = "haha"
    print pro.name
    del pro.name

运行结果如下:

Setting: haha
Getting: haha
haha
Deleting: haha

看,代码运行如初。具体原理就不再赘述。可以打印出PropertyDesc类和实例pro的属性列表进行思考。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,849评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 要点: 函数式编程:注意不是“函数编程”,多了一个“式” 模块:如何使用模块 面向对象编程:面向对象的概念、属性、...
    victorsungo阅读 1,459评论 0 6
  • 之前写了关于买房,保险的文章。不过,我是程序猿呀,还是写一点工作相关的文章吧。不要那么“不务正业”,哈哈。在麻瓜编...
    9abda844c1aa阅读 242评论 0 0
  • 不是520的表白日,也不是5月份的母亲节,而是“大学生心理健康节”。 2000年,由北京师范大学心...
    psychology凡心阅读 920评论 0 8