• Python 中关于descriptor的一些知识问题


    这个问题从早上日常扫segmentfault上问题开始

    有个问题是

    class C(object):
        @classmethod
        def m():
            pass
    m()是类方法,调用代码如下:
    
    C.m()
    但我想当成属性的方式调用,像这样:
    
    C.m
    请问该怎么弄呢? 请最好提供个简单的例子, 多谢!

    这里我开始误会了他的意思,以为他是想直接使用C().m调用这个方法,如果是这样,直接将装饰器@classmathod改成@property就可以达到效果了。

    但是这里他想要达到的效果是C.m 也就是说在不实例化C对象的情况下去调用m方法 有点类似即使用classmathod然后在这个基础上又调用property方法

    我想了很久,没有在以前的编码中遇到过,所以来了兴致。

    在广泛查找资料之后,我发现跟一个叫descriptor protocol的概念有关。

    什么是descriptor?官方文档给的简介是:

    In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol. Those methods are __get__()__set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor

    通常来说,一个descriptor就是一个绑定行为的对象,一个对属性的访问方法会被描述协议中的方法覆盖。那些方法包括__get__(), __set__()和__delete__().如果任何上面的那些方法出现在了你定义的object中,那么我们就说他是一个descriptor。

    换句话来说,如果你定义一个class方法,然后class方法中包含了__get__,__set__和__delete__方法中的任何一个,那么这个类就是一个descriptor。

    相信绝大多数人都是用过上面提到的property方法,但是不知道有没有盆友仔细看过property的源码,其实property就是一个典型的descriptor。

    说这么多也不明白来复现一下上面的问题怎么解决

    class ClassPro(object):
    
        def __init__(self, function):
            self.run = function
    
        def __get__(self, instance, owner):
            return self.run(owner)
    
    class a(object):
    
        def pp(self):
            print 'say something'
    
        pp = ClassPro(pp)
    
    a.pp
    
    output: say something

    可以看到上面我实现了一个自定义的新式类ClassPro用来装饰下面a类里面的函数pp 。CP实现了__get__方法所以他是一个descriptor,descriptor一旦实现,会覆盖掉原有的从__dict__里面寻找属性的方式。这里我引用别人博客里面翻译过来的doc

    通常对一个实例的属性的访问操作,如get, set, delete是通过实例的__dict__字典属性进行的,
    例如,对于操作a.x,会一个查找链从a.__dict['x'](实例的字典),再到type(a).__dict__['x'](类的
    字典),再到type(a)的父类的字典等等。
    如果一个对象同时定义了__get__,__set__方法,被看作是data descriptor;只定义了__get__,被称
    为non-data descriptor。如果实例字典中有一个key和data descriptor同名,那么查找时优先采用
    data descriptor;如果实例字典中有一个key和non-data descriptor同名,那么优先采用实例字典的
    方法。

    所以上面我们写的例子,实现a.pp可以访问,其实就是在a类调用pp函数的时候,本来是一个未绑定方法,但是这里是调用到了被ClassPro这个descriptor装饰的pp,重写了属性访问方法。之后会执行ClassPro, 而ClassPro定义了属性访问会执行该传入并运行被包装函数,入参是cls,所以就运行成功了。

    同样应该说一下 property classmethod staticmethod super等都是利用了descriptor的原理实现的,这里拿classmethod的纯python实现再讲解一下

    Using the non-data descriptor protocol, a pure Python version of classmethod() would look like this:

    class ClassMethod(object):
        "Emulate PyClassMethod_Type() in Objects/funcobject.c"
    
        def __init__(self, f):
            self.f = f
    
        def __get__(self, obj, klass=None):
            if klass is None:
                klass = type(obj)
            def newfunc(*args):
                return self.f(klass, *args)
            return newfunc

    上个测试用例子:

    class A(object):
    
        def pp(self):
            return 1
    
    print A.__dict__
    print A().pp()

    一般正常调用pp要这样调用 

    但是如果我们是用上面定义的classmethod相当于成了这样的调用

    class A(object):
    
        def pp(self):
            return 1
        pp = classmethod(pp)
    
    
    print A.__dict__
    print A.pp

    中间发生了什么呢, 当我们是用classmethod方法包装pp的时候,其实就相当于使用descriptor重写了属性访问。

    1. A.pp 访问相当于 A.__dict__['pp']如果没有descriptor的情况下,但是现在我们包装了descriptor classmethod就变成了 A.__dict__['pp'].__get__(None,A)

    2. A.pp的使用调用了descriptor的__get__方法,传入了空object, 以及A本身。

    3. 

    def newfunc(*args):
                return self.f(klass, *args)
            return newfunc

    返回了newfunc而newfunc 返回了 self.f也就是 pp(cls, *args) 最后返回了这个闭包。这就是运行A.pp 干的事情最后再执行() 就得到了结果。

    觉得上面有点迷 可以 看下面这个应该就明白了。

    class ClassMethod(object):
        "Emulate PyClassMethod_Type() in Objects/funcobject.c"
    
        def __init__(self, f):
            self.f = f
    
        def __get__(self, obj, klass=None):
            if klass is None:
                klass = type(obj)
            def newfunc(*args):
                return self.f(klass, *args)
            return newfunc
    
    
    
    class A(object):
    
        def pp(self):
            return 1
        pp = ClassMethod(pp)
    
    
    print A.__dict__
    print A.__dict__['pp'].__get__(None, A)()

    大概就是这样。

    Reference:

    http://stackoverflow.com/questions/17330160/how-does-the-property-decorator-work  how-does-the-property-decorator-work 

    http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html  python中基于descriptor的一些概念(上)

    http://www.cnblogs.com/btchenguang/archive/2012/09/18/2690802.html#3416935  python中基于descriptor的一些概念(下)

    https://docs.python.org/2.7/howto/descriptor.html  Descriptor HowTo Guide

    http://stackoverflow.com/questions/128573/using-property-on-classmethods  Using property() on classmethods

  • 相关阅读:
    Kbuild文件
    patch与diff的恩怨
    依据linux Oops信息准确定位错误代码所在行
    理解嵌入式开发中的一些硬件相关的概念
    linux内核中经常用到的设备初始化宏
    如何实例化i2c_client(四法)
    设计和编写设备驱动的一般方法
    [转] rtp h264注意点(FU-A分包方式说明)
    c语言的label后面不能直接跟变量申明
    互联网目前最有影响力的流量统计网站
  • 原文地址:https://www.cnblogs.com/piperck/p/5958895.html
Copyright © 2020-2023  润新知