杂项之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. C# 条形码操作【源码下载】

    本篇介绍通过C#生成和读取一维码.二维码的操作. 目录 1. 介绍:介绍条形码.条形码的分类以及ZXing.Net类库. 2. 一维码操作:包含对一维码的生成.读取操作. 3. 二维码操作:包含对二维 ...

  2. Go结构体实现类似成员函数机制

    Go语言结构体成员能否是函数,从而实现类似类的成员函数的机制呢?答案是肯定的. package main import "fmt" type stru struct { testf ...

  3. CRL快速开发框架系列教程十三(嵌套查询)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  4. 普通程序员如何转向AI方向

    眼下,人工智能已经成为越来越火的一个方向.普通程序员,如何转向人工智能方向,是知乎上的一个问题.本文是我对此问题的一个回答的归档版.相比原回答有所内容增加. 一. 目的 本文的目的是给出一个简单的,平 ...

  5. (一)开篇—杂谈WebGIS

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.前言 我相信大家对百度地图,谷歌地图等相关应用已经是非常熟悉了.通过 ...

  6. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

  7. Kotlin类:功能更强、而更简洁(KAD 03)

    作者:Antonio Leiva 时间:Dec 7, 2016 原文链接:http://antonioleiva.com/classes-kotlin/ Kotlin类尽可能简单,这样用较少的代码完成 ...

  8. MySQL常用命令

    数据库登陆命令: mysql -uroot -p 2.提示输入密码: 3.登陆成功: 4.数据库修改相关命令: 修改数据库的编码格式: 语法格式为:ALTER {DATABASE|SCHEMA}  [ ...

  9. CSharpGL(25)一个用raycast实现体渲染VolumeRender的例子

    CSharpGL(25)一个用raycast实现体渲染VolumeRender的例子 本文涉及的VolumeRendering相关的C#代码是从(https://github.com/toolchai ...

  10. python的拷贝(深拷贝和浅拷贝)

    今天看了几篇关于python拷贝的博文,感觉不太清楚,所以我就自己做实验试一下,特此记录. 拷贝是针对组合对象说的,比如列表,类等,而数字,字符串这样的变量是没有拷贝这一说的. 实现拷贝有: 1.工厂 ...