python实现装饰器、描述符


Posted in Python onFebruary 28, 2018

概要

本人python理论知识远达不到传授级别,写文章主要目的是自我总结,并不能照顾所有人,请见谅,文章结尾贴有相关链接可以作为补充

全文分为三个部分装饰器理论知识、装饰器应用、装饰器延申

  • 装饰理基础:无参装饰器、有参装饰器、functiontools、装饰器链
  • 装饰器进阶:property、staticmethod、classmethod源码分析(python代码实现)

装饰器基础

无参装饰器

'''
假定有一个需求是:打印程序函数运行顺序
此案例打印的结果为:
  foo1 function is starting
  foo2 function is starting
'''
from functools import wraps

def NoParamDec(func):
  #函数在被装饰器装时后,其函数属性也会改变,wraps作用就是保证被装饰函数属性不变
  @wraps(func)
  def warpper(*args, **kwargs):
    print('{} function is starting'.format(func.__name__))
    return func(*args, **kwargs)
  
  return warpper

#python黑魔法省略了NoParamDec=NoParamDec(foo1)
@NoParamDec
def foo1():
  foo2()

@NoParamDec
def foo2():
  pass

if __name__ == "__main__":

  foo1()

有参装饰器

'''
假定有一个需求是:检查函数参数的类型,只允许匹配正确的函数通过程序
此案例打印结果为:
('a', 'b', 'c')
-----------------------分割线------------------------
ERROS!!!!b must be <class 'str'> 
ERROS!!!!c must be <class 'str'> 
('a', 2, ['b', 'd'])

  
'''
from functools import wraps
from inspect import signature


def typeAssert(*args, **kwargs):
  deco_args = args
  deco_kwargs = kwargs
  
  def factor(func):
    #python标准模块类,可以用来检查函数参数类型,只允许特定类型通过
    sig = signature(func)
    #将函数形式参数和规定类型进行绑定
    check_bind_args = sig.bind_partial(*deco_args, **deco_kwargs).arguments
    
    @wraps(func)
    def wrapper(*args, **kwargs):
      #将实际参数值和形式参数进行绑定
      wrapper_bind_args = sig.bind(*args, **kwargs).arguments.items()
      for name, obj in wrapper_bind_args:
        #遍历判断是否实际参数值是规定参数的实例
        if not isinstance(obj, check_bind_args[name]):
          try:
            raise TypeError('ERROS!!!!{arg} must be {obj} '.format(**{'arg': name, 'obj': check_bind_args[name]}))
          except Exception as e:
            print(e)
      return func(*args, **kwargs)
    
    return wrapper
  
  return factor

@typeAssert(str, str, str)
def inspect_type(a, b, c):
  return (a, b, c)

if __name__ == "__main__":
  print(inspect_type('a', 'b', 'c'))
  print('{:-^50}'.format('分割线'))
  print(inspect_type('a', 2, ['b', 'd']))

装饰器链

'''
假定有一个需求是:
输入类似代码:
@makebold
@makeitalic
def say():
  return "Hello"

输出:
<b><i>Hello</i></b>
'''
from functools import wraps

def html_deco(tag):
  def decorator(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
      return '<{tag}>{fn_result}<{tag}>'.format(**{'tag': tag, 'fn_result': fn(*args, **kwargs)})
    
    return wrapped
  
  return decorator

@html_deco('b')
@html_deco('i')
def greet(whom=''):
  # 等价于 geet=html_deco('b')(html_deco('i)(geet))
  return 'Hello' + (' ' + whom) if whom else ''

if __name__ == "__main__":
  print(greet('world')) # -> <b><i>Hello world</i></b>

装饰器进阶

property 原理

通常,描述符是具有“绑定行为”的对象属性,其属性访问已经被描述符协议中的方法覆盖。这些方法是__get__()、__set__()和__delete__()。如果一个对象定义这些方法中的任何一个,它被称为一个描述符。如果对象定义__get__()和__set__(),则它被认为是数据描述符。仅定义__get__()的描述器称为非数据描述符(它们通常用于方法,但是其他用途也是可能的)。

属性查找优先级为:

  • 类属性
  • 数据描述符
  • 实例属性
  • 非数据描述符
  • 默认为__getattr__()
class Property(object):
  '''
  内部property是用c实现的,这里用python模拟实现property功能
  代码参考官方doc文档
  '''

  def __init__(self, fget=None, fset=None, fdel=None, doc=None):
    self.fget = fget
    self.fset = fset
    self.fdel = fdel
    self.__doc__ = doc

  def __get__(self, obj, objtype=None):
    if obj is None:
      return self
    if self.fget is None:
      raise (AttributeError, "unreadable attribute")
    print('self={},obj={},objtype={}'.format(self,obj,objtype))
    return self.fget(obj)

  def __set__(self, obj, value):
    if self.fset is None:
      raise (AttributeError, "can't set attribute")
    self.fset(obj, value)

  def __delete__(self, obj):
    if self.fdel is None:
      raise (AttributeError, "can't delete attribute")
    self.fdel(obj)

  def getter(self, fget):
    return type(self)(fget, self.fset, self.fdel, self.__doc__)

  def setter(self, fset):
    return type(self)(self.fget, fset, self.fdel, self.__doc__)

  def deleter(self, fdel):
    return type(self)(self.fget, self.fset, fdel, self.__doc__)


class Student( object ):
  @Property
  def score( self ):
    return self._score
  @score.setter
  def score( self, val ):
    if not isinstance( val, int ):
      raise ValueError( 'score must be an integer!' )
    if val > 100 or val < 0:
      raise ValueError( 'score must between 0 ~ 100!' )
    self._score = val


if __name__ == "__main__":
  s = Student()
  s.score = 60  
  s.score

staticmethod 原理

@staticmethod means: when this method is called, we don't pass an instance of the class to it (as we normally do with methods). This means you can put a function inside a class but you can't access the instance of that class (this is useful when your method does not use the instance).

class StaticMethod(object):
  "python代码实现staticmethod原理"
  
  def __init__(self, f):
    self.f = f
  
  def __get__(self, obj, objtype=None):
    return self.f


class E(object):
  #StaticMethod=StaticMethod(f)
  @StaticMethod
  def f( x):
    return x

if __name__ == "__main__":
  print(E.f('staticMethod Test'))

classmethod

@staticmethod means: when this method is called, we don't pass an instance of the class to it (as we normally do with methods). This means you can put a function inside a class but you can't access the instance of that class (this is useful when your method does not use the instance).

class ClassMethod(object):
  "python代码实现classmethod原理"
  
  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 E(object):
  #ClassMethod=ClassMethod(f)
  @ClassMethod
  def f(cls,x):
    return x
  
if __name__ == "__main__":
  print(E().f('classMethod Test'))
Python 相关文章推荐
Python版的文曲星猜数字游戏代码
Sep 02 Python
Python socket.error: [Errno 98] Address already in use的原因和解决方法
Aug 25 Python
利用一个简单的例子窥探CPython内核的运行机制
Mar 30 Python
Python爬虫包 BeautifulSoup  递归抓取实例详解
Jan 28 Python
如何优雅地改进Django中的模板碎片缓存详解
Jul 04 Python
Windows下Anaconda2安装NLTK教程
Sep 19 Python
python3实现点餐系统
Jan 24 Python
python实现弹窗祝福效果
Apr 07 Python
对python3 Serial 串口助手的接收读取数据方法详解
Jun 12 Python
python将类似json的数据存储到MySQL中的实例
Jul 12 Python
详解解Django 多对多表关系的三种创建方式
Aug 23 Python
Python字符串的转义字符
Apr 07 Python
python安装教程
Feb 28 #Python
深入理解Python 关于supper 的 用法和原理
Feb 28 #Python
TensorFlow实现RNN循环神经网络
Feb 28 #Python
python使用TensorFlow进行图像处理的方法
Feb 28 #Python
使用Python搭建虚拟环境的配置方法
Feb 28 #Python
Python OpenCV获取视频的方法
Feb 28 #Python
python实现多线程行情抓取工具的方法
Feb 28 #Python
You might like
phpmyadmin 常用选项设置详解版
2010/03/07 PHP
采用thinkphp自带方法生成静态html文件详解
2014/06/13 PHP
PHP开发制作一个简单的活动日程表Calendar
2016/06/20 PHP
图片之间的切换
2006/06/26 Javascript
Jquery+WebService 校验账号是否已被注册的代码
2010/07/12 Javascript
JavaScript随机排序(随即出牌)
2010/09/17 Javascript
jQuery对表单元素的取值和赋值操作代码
2011/05/19 Javascript
javascript数组的使用
2013/03/28 Javascript
JavaScript绑定事件监听函数的通用方法
2016/05/14 Javascript
JS实现间歇滚动的运动效果实例
2016/12/22 Javascript
利用iscroll4实现轮播图效果实例代码
2017/01/11 Javascript
全面解析vue中的数据双向绑定
2017/05/10 Javascript
老生常谈js中的MVC
2017/07/25 Javascript
详解Require.js与Sea.js的区别
2018/08/05 Javascript
jQuery解析json格式数据示例
2018/09/01 jQuery
vue中的router-view组件的使用教程
2018/10/23 Javascript
微信小程序实现点击图片旋转180度并且弹出下拉列表
2018/11/27 Javascript
highCharts提示框中显示当前时间的方法
2019/01/18 Javascript
Vue 处理表单input单行文本框的实例代码
2019/05/09 Javascript
关于微信小程序获取小程序码并接受buffer流保存为图片的方法
2019/06/07 Javascript
VUE实现强制渲染,强制更新
2019/10/29 Javascript
vue实现数字滚动效果
2020/06/29 Javascript
vue中v-model对select的绑定操作
2020/08/31 Javascript
python使用三角迭代计算圆周率PI的方法
2015/03/20 Python
Python使用Tkinter实现机器人走迷宫
2018/01/22 Python
Python 执行矩阵与线性代数运算
2020/08/01 Python
解决tensorflow模型压缩的问题_踩坑无数,总算搞定
2021/03/02 Python
深入浅析css3 border-image边框图像详解
2015/11/24 HTML / CSS
html5视频播放_动力节点Java学院整理
2017/07/13 HTML / CSS
开普敦通行证:Cape Town Pass
2019/07/18 全球购物
工作自我评价怎么写
2014/01/29 职场文书
会员卡清退活动总结
2014/08/27 职场文书
财务总监岗位职责范本
2015/04/03 职场文书
幼儿园教师个人工作总结2015
2015/05/12 职场文书
python实现简易名片管理系统
2021/04/11 Python
Redis 哨兵机制及配置实现
2022/03/25 Redis