杂项之python描述符协议

本节内容

  1. 由来
  2. 描述符协议概念
  3. 类的静态方法及类方法实现原理
  4. 类作为装饰器使用

1. 由来

闲来无事去看了看django中的内置分页方法,发现里面用到了类作为装饰器来使用,由于之前就看到过这一类的用法,但是一直没有明白具体是如何实现的,今天本着打破砂锅问到底的精神去网上搜资料,在这里不得不吐槽下百度搜索的垃圾了。。。。。竞价排名做的那么6,搜一些技术文档。。。。。各种坑爹。。。就是找不到想要的资源。。。
于是翻墙上google搜了搜,找到了python官网的文档。。。里面详细解释了原理以及用法,表示英语渣是硬伤。。。。囫囵吞枣的理解了里面的一部分内容。。。于是就有了本文的诞生。。。。。python官网文档连接在这:
python官方文档地址
英语比较好的小伙伴可以去拜读下。

2. 描述符协议概念

在python中有个概念叫做描述符(descriptors)
描述符在python中是一个协议,协议的定义如下:
如果为某个对象定义了__get__,__set__及__delete__中的任意一种方法,那么就称这个对象是描述符。
描述符是一个功能强大的通用协议,类中的属性,方法,静态方法,类方法甚至super()背后都是基于描述符它是在python2.2中引入的新式类中实现的,描述符使底层的C代码实现更简单,并且为python编程提供了一个灵活的新工具。

官方说明如下:

descr.__get__(self, obj, type=None) --> value

descr.__set__(self, obj, value) --> None

descr.__delete__(self, obj) --> None

如果对象中定义了__get__()和__set__(),它被认为是一个数据描述符。只定义__get__()的描述符称为非数据描述符(它们通常用于方法,但其他用途也是可能的)。
数据和非数据描述符的区别在如何重写对象的字典计算实例。

  • 如果实例字典具有与数据描述符相同的项,则数据描述符将优先。
  • 如果实例字典具有与非数据描述符同名的项,则字典条目优先。

描述符的调用:
描述符在被调用时区别在于调用描述符的是对象还是类:
对于对象:

  • 数据描述符被优先调用
  • 非数据描述符可能会被实例字典所覆盖

解释:
非数据描述符在对象调用中可能会被实例字典覆盖的举例:

#-------------------------------------被实例的字典覆盖的例子-------------------------------------------------------------
class RevealAccess:
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
""" def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val class MyClass:
x=RevealAccess(10,'var"x"') m=MyClass()
m.x=3
print(m.x)
# 3
# 这里的非数据描述符被实例中的字典给覆盖了,当调用实例m的属性x的时候,x对应的是数字3 #---------------------------------------数据描述符被优先调用的例子-------------------------------------------------------
class RevealAccess:
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
""" def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val def __set__(self, obj, val):
print('Updating', self.name)
print(obj,val)
self.val = val class MyClass:
x=RevealAccess(10,'var"x"') m=MyClass()
m.x=3
print(m.x)
# Updating var"x"
# <__main__.MyClass object at 0x0000021E5F33A7B8> 3
# Retrieving var"x"
# 3 # 这里的数据描述符无法被实例中的字典覆盖。。。当实例m给属性x赋值的时候,实际是调用了x对应的RevealAccess对象的__set__方法给属性x赋值,这里的数据描述符的优先级比对象的属性的优先级更高

会将对象b.x 转换成 type(b).__dict__[‘x’].__get__(obj, type(b))
对于类:
会将class.x 转换成 class.__dict__[‘x’].__get__(None, class)

python模拟__getattribute__()的实现

def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v

需要重点记住的点:

  • 描述符只有在__getattribute__()的时候才会被调用
  • 重写__get__将会覆盖默认的__getattribute__()中的调用
  • __getattribute__()只适用于新式类和对象
  • object.__getattribute__() 和 type.__getattribute__()调用__get__()的方式不同
  • 数据描述符总是覆盖实例中的字典
  • 非数据描述符可能被实例字典所覆盖

例子:

class RevealAccess:
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
""" def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val def __set__(self, obj, val):
print('Updating', self.name)
print(obj,val)
self.val = val class MyClass:
x=RevealAccess(10,'var"x"') m=MyClass()
print(m.x)
# Retrieving var"x" 通过对象调用属性x,将会执行RevealAccess对象的__get__方法(描述符)。
# 10
m.x = 20 # 将20赋值给MyClass的对象m的属性x-->RevealAccess对象-->调用RevealAccess对象的__set__方法
# Updating var"x"
# <__main__.MyClass object at 0x0000013064DAA7F0> 20 # __set__方法接收的第一个参数是MyClass对象,第二个参数是=后面的值
print(m.x)
# Retrieving var"x"
# 20

3. 类的静态方法及类方法实现原理

类的静态方法及类方法作用可以参考我之前的博客:
python 高级之面向对象初级

类的静态方法实现原理:

class StaticMethod(object):
"Emulate PyStaticMethod_Type() in Objects/funcobject.c" def __init__(self, f):
self.f = f def __get__(self, obj, objtype=None):
return self.f class E(object):
@staticmethod
def f(x):
print x
# 上面的装饰器相当于 f = staticmethod(f) print E.f(3)
# 3
print E().f(3)
# 3

类方法实现原理:

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 Dict(object): @classmethod
def fromkeys(klass, iterable, value=None):
"Emulate dict_fromkeys() in Objects/dictobject.c"
d = klass()
for key in iterable:
d[key] = value
return d
# 上面的装饰器相当于 fromkeys = classmethod(fromkeys) print(Dict.fromkeys('abracadabra'))
# {'a': None, 'r': None, 'b': None, 'c': None, 'd': None} #执行Dict.fromkeys相当创建了classmethod(fromkeys)对象,并执行了该对象的__get__方法,该方法返回一个新的函数,而后面的('abracadabra')相当于传递参数'abracadabra'执行了这个新函数,返回的是fromkeys的执行结果。

4. 类作为装饰器使用

代码如下:

class MyClassDescriptor:
def __init__(self, func, name=None):
self.func = func
self.name = name or func.__name__
self.__doc__=getattr(func, '__doc__') def __get__(self,instance,cls=None):
if instance is None:
print(instance,cls)
return self
print(instance,cls)
res = instance.__dict__[self.name] = self.func(instance)
return res class MyClass:
@MyClassDescriptor
def foo(self):
print("MyClass foo")
# foo=MyClassDescriptor(foo) m.foo
# <__main__.MyClass object at 0x000001BE9597A828> <class '__main__.MyClass'> # 传递进__get__方法的两个参数,第一个是MyClass对象m,第二个是MyClass类的内存地址,在__get__方法中返回的是foo函数的执行结果,也就打印出来了'MyClass foo'
# MyClass foo
print(MyClass.foo)
# None <class '__main__.MyClass'> # 当是类调用foo时,传递进__get__方法中的第一个参数为None,第二个参数是MyClass类的内存地址
# <__main__.MyClassDescriptor object at 0x000001BE9597A7F0>

杂项之python描述符协议的更多相关文章

  1. Iterator Protocol - Python 描述符协议

    Iterator Protocol - Python 描述符协议 先看几个有关概念, iterator 迭代器, 一个实现了无参数的 __next__ 方法, 并返回 '序列'中下一个元素,在没有更多 ...

  2. Descriptor - Python 描述符协议

    描述符(descriptor) descriptor 是一个实现了 __get__. __set__ 和 __delete__ 特殊方法中的一个或多个的. 与 descriptor 有关的几个名词解释 ...

  3. 【转载】Python 描述符简介

    来源:Alex Starostin 链接:www.ibm.com/developerworks/cn/opensource/os-pythondescriptors/ 关于Python@修饰符的文章可 ...

  4. 一文掌握 Python 的描述符协议

    描述符介绍 描述符本质就是一个新式类,在这个新式类中,至少要实现了__get__(),__set__(),__delete__()中的一个.这也被称为描述符协议. class Myclass(obje ...

  5. python描述符理解

    Python中的描述符是一个相对底层的概念 descriptor Any object which defines the methods get(), set(), or delete(). Whe ...

  6. python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解

     1.前言 Python的描述符是接触到Python核心编程中一个比较难以理解的内容,自己在学习的过程中也遇到过很多的疑惑,通过google和阅读源码,现将自己的理解和心得记录下来,也为正在为了该问题 ...

  7. python描述符descriptor(一)

    Python 描述符是一种创建托管属性的方法.每当一个属性被查询时,一个动作就会发生.这个动作默认是get,set或者delete.不过,有时候某个应用可能会有 更多的需求,需要你设计一些更复杂的动作 ...

  8. Python描述符的使用

    Python描述符的使用 前言 作为一位python的使用者,你可能使用python有一段时间了,但是对于python中的描述符却未必使用过,接下来是对描述符使用的介绍 场景介绍 为了引入描述符的使用 ...

  9. 11.python描述符---类的装饰器---@property

    描述符1.描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()这三个内置方法中的一个,描述符也被称为描述符协议(1):__ ...

随机推荐

  1. 用dubbo时遇到的一个序列化的坑

    首先,这是标题党,问题并不是出现在序列化上,这是报错的一部分: Caused by: com.alibaba.dubbo.remoting.RemotingException: Failed to s ...

  2. Python 爬虫模拟登陆知乎

    在之前写过一篇使用python爬虫爬取电影天堂资源的博客,重点是如何解析页面和提高爬虫的效率.由于电影天堂上的资源获取权限是所有人都一样的,所以不需要进行登录验证操作,写完那篇文章后又花了些时间研究了 ...

  3. vmware里面的名词 vSphere、vCenter Server、ESXI、vSphere Client

    vmware里面的名词 vSphere.vCenter Server.ESXI.vSphere Client vSphere.vCenter Server.ESXI.vSphere Client VS ...

  4. mybatis_映射查询

    一.一对一映射查询: 第一种方式(手动映射):借助resultType属性,定义专门的pojo类作为输出类型,其中该po类中封装了查询结果集中所有的字段.此方法较为简单,企业中使用普遍. <!- ...

  5. 自定义搭建PHP开发环境

    学习了一段时间php了,因为之前是刚接触php,所以用的是集成安装包(wamp).现在想进一步了解apache.mysql.php之间的关系以及提升自己所以进行自定义搭建PHP开发环境.废话不多说,请 ...

  6. Android -- 真正的 高仿微信 打开网页的进度条效果

    (本博客为原创,http://www.cnblogs.com/linguanh/) 目录: 一,为什么说是真正的高仿? 二,为什么要搞缓慢效果? 三,我的实现思路 四,代码,内含注释 五,使用方法与截 ...

  7. C#创建、安装、卸载、调试Windows Service(Windows 服务)的简单教程

    前言:Microsoft Windows 服务能够创建在它们自己的 Windows 会话中可长时间运行的可执行应用程序.这些服务可以在计算机启动时自动启动,可以暂停和重新启动而且不显示任何用户界面.这 ...

  8. c++ pair 使用

    1. 包含头文件: #include <utility> 2. pair 的操作: pair<T1,T2> p; pair<T1,T2> p(v1,v2); pai ...

  9. 高性能 TCP/UDP/HTTP 通信框架 HP-Socket v4.1.1

    HP-Socket 是一套通用的高性能 TCP/UDP/HTTP 通信框架,包含服务端组件.客户端组件和 Agent 组件,广泛适用于各种不同应用场景的 TCP/UDP/HTTP 通信系统,提供 C/ ...

  10. iOS之延时执行(睡眠)的几种方法

    1. 最直接的方法: [self performSelector:@selector(deleyMethod) withObject:nil afterDelay:1.0]; 此方式要求必须在主线程中 ...