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

有个问题是

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

这里我开始误会了他的意思,以为他是想直接使用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。

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

  1. class ClassPro(object):
  2.  
  3. def __init__(self, function):
  4. self.run = function
  5.  
  6. def __get__(self, instance, owner):
  7. return self.run(owner)
  8.  
  9. class a(object):
  10.  
  11. def pp(self):
  12. print 'say something'
  13.  
  14. pp = ClassPro(pp)
  15.  
  16. a.pp

  17. output:
  18. 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:

  1. class ClassMethod(object):
  2. "Emulate PyClassMethod_Type() in Objects/funcobject.c"
  3.  
  4. def __init__(self, f):
  5. self.f = f
  6.  
  7. def __get__(self, obj, klass=None):
  8. if klass is None:
  9. klass = type(obj)
  10. def newfunc(*args):
  11. return self.f(klass, *args)
  12. return newfunc

上个测试用例子:

  1. class A(object):
  2.  
  3. def pp(self):
  4. return 1
  5.  
  6. print A.__dict__
  7. print A().pp()

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

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

  1. class A(object):
  2.  
  3. def pp(self):
  4. return 1
  5. pp = classmethod(pp)
  6.  
  7. 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.

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

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

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

  1. class ClassMethod(object):
  2. "Emulate PyClassMethod_Type() in Objects/funcobject.c"
  3.  
  4. def __init__(self, f):
  5. self.f = f
  6.  
  7. def __get__(self, obj, klass=None):
  8. if klass is None:
  9. klass = type(obj)
  10. def newfunc(*args):
  11. return self.f(klass, *args)
  12. return newfunc
  13.  
  14. class A(object):
  15.  
  16. def pp(self):
  17. return 1
  18. pp = ClassMethod(pp)
  19.  
  20. print A.__dict__
  21. 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

python 中关于descriptor的一些知识问题的更多相关文章

  1. python中的 descriptor

    学好和用好python, descriptor是必须跨越过去的一个点,现在虽然Python书籍花样百出,但是似乎都是在介绍一些Python库而已,对Python语言本身的关注很少,或者即使关注了,但是 ...

  2. python中基于descriptor的一些概念

    python中基于descriptor的一些概念(上) 1. 前言 2. 新式类与经典类 2.1 内置的object对象 2.2 类的方法 2.2.1 静态方法 2.2.2 类方法 2.3 新式类(n ...

  3. python中利用matplotlib绘图可视化知识归纳

    python中利用matplotlib绘图可视化知识归纳: (1)matplotlib图标正常显示中文 import matplotlib.pyplot as plt plt.rcParams['fo ...

  4. python中基于descriptor的一些概念(上)

    @python中基于descriptor的一些概念(上) python中基于descriptor的一些概念(上) 1. 前言 2. 新式类与经典类 2.1 内置的object对象 2.2 类的方法 2 ...

  5. python中基于descriptor的一些概念(下)

    @python中基于descriptor的一些概念(下) 3. Descriptor介绍 3.1 Descriptor代码示例 3.2 定义 3.3 Descriptor Protocol(协议) 3 ...

  6. python中的线程技术

    #!/user/bin/env python # @Time :2018/7/7 11:42 # @Author :PGIDYSQ #@File :DaemonTest.py import threa ...

  7. Python中正则表达式简介

    目录 一.什么是正则表达式 二.正则表达式的基础知识 1. 原子 1)普通字符作为原子 2)非打印字符作为原子 3) 通用字符作为原子 4) 原子表 2. 元字符 1)任意匹配元字符 2)边界限制元字 ...

  8. 盘点 Python 中的那些冷知识(二)

    上一篇文章分享了 Python中的那些冷知识,地址在这里 盘点 Python 中的那些冷知识(一) 今天将接着分享!! 06. 默认参数最好不为可变对象 函数的参数分三种 可变参数 默认参数 关键字参 ...

  9. python模块 re模块与python中运用正则表达式的特点 模块知识详解

    1.re模块和基础方法 2.在python中使用正则表达式的特点和问题 3.使用正则表达式的技巧 4.简单爬虫例子 一.re模块 模块引入; import re 相关知识: 1.查找: (1)find ...

随机推荐

  1. 监控nginx

    vi nginx_status.sh #!/bin/bash HOST="127.0.0.1" PORT="9222" # 检测nginx进程是否存在 func ...

  2. js == 与 === 的区别[转]

    we文章转自http://blog.sina.com.cn/s/blog_4b32835b01014iv9.html 1.对于string,number等基础类型,==和===是有区别的 1)不同类型 ...

  3. flask 路由和视图

    路由设置的俩种方式 @app.route('/xxx') def index(): return 'index' ------------------------------------------ ...

  4. ubuntu 系统查看opencv 的版本

    有很多的时候 ,我们想知道自己的电脑里面安装的opencv版本是多少 在终端中运行下面的命令. pkg-config --modversion opencv 为什么要知道自己电脑的opencv 版本, ...

  5. Android学习之六种事件响应方法汇总

    java源码如下: 1.MainActivity.java源码 package com.example.responsetest; import android.app.Activity; impor ...

  6. Linux下ftp安装配置及三种用户的验证

    一.原理简介 二.安装配置 三.三种用户的验证 一.简介 FTP即文件传输协议(File Transfer Protocol),完成各主机的文件共享功能,基于客户端-服务器的协议,工作在应用层,tcp ...

  7. 关于PHP程序员技术职业生涯规划

    看到很多PHP程序员职业规划的文章,都是直接上来就提Linux.PHP.MySQL.Nginx.Redis.Memcache.jQuery这些,然后就直接上手搭环境.做项目,中级就是学习各种PHP框架 ...

  8. 讲一下Asp.net core MVC2.1 里面的 ApiControllerAttribute (转载)

    ASP.NET Core MVC 2.1 特意为构建 HTTP API 提供了一些小特性,今天主角就是 ApiControllerAttribute. (注:文章是18年2月份的,所以文章提到了cor ...

  9. BZOJ2154/BZOJ2693/Luogu1829 Crash的数字表格/JZPFAR 莫比乌斯反演

    传送门--Luogu 传送门--BZOJ2154 BZOJ2693是权限题 其中JZPFAR是多组询问,Crash的数字表格是单组询问 先推式子(默认\(N \leq M\),所有分数下取整) \(\ ...

  10. WPF Blend 脑洞大开的问题:如何用Blend得到或画出一个凹槽、曲面。

    原文:WPF Blend 脑洞大开的问题:如何用Blend得到或画出一个凹槽.曲面. 目标图: 步骤一(放置一个矩形,填充蓝色): 步骤二(复制该矩形,并调整边角,填充粉红色): 第三部:让图形部分重 ...