Python类装饰器实现方法详解


Posted in Python onDecember 21, 2018

本文实例讲述了Python类装饰器。分享给大家供大家参考,具体如下:

编写类装饰器

类装饰器类似于函数装饰器的概念,但它应用于类,它们可以用于管理类自身,或者用来拦截实例创建调用以管理实例。

单体类

由于类装饰器可以拦截实例创建调用,所以它们可以用来管理一个类的所有实例,或者扩展这些实例的接口。

下面的类装饰器实现了传统的单体编码模式,即最多只有一个类的一个实例存在。

instances = {} # 全局变量,管理实例
def getInstance(aClass, *args):
  if aClass not in instances:
    instances[aClass] = aClass(*args)
  return instances[aClass]   #每一个类只能存在一个实例
def singleton(aClass):
  def onCall(*args):
    return getInstance(aClass,*args)
  return onCall
为了使用它,装饰用来强化单体模型的类:
@singleton    # Person = singleton(Person)
class Person:
  def __init__(self,name,hours,rate):
    self.name = name
    self.hours = hours
    self.rate = rate
  def pay(self):
    return self.hours * self.rate
@singleton    # Spam = singleton(Spam)
class Spam:
  def __init__(self,val):
    self.attr = val
bob = Person('Bob',40,10)
print(bob.name,bob.pay())
sue = Person('Sue',50,20)
print(sue.name,sue.pay())
X = Spam(42)
Y = Spam(99)
print(X.attr,Y.attr)

现在,当Person或Spam类稍后用来创建一个实例的时候,装饰器提供的包装逻辑层把实例构建调用指向了onCall,它反过来调用getInstance,以针对每个类管理并分享一个单个实例,而不管进行了多少次构建调用。

程序输出如下:

Bob 400
Bob 400
42 42

在这里,我们使用全局的字典instances来保存实例,还有一个更好的解决方案就是使用Python3中的nonlocal关键字,它可以为每个类提供一个封闭的作用域,如下:

def singleton(aClass):
 instance = None
 def onCall(*args):
 nonlocal instance
 if instance == None:
  instance = aClass(*args)
 return instance
 return onCall

当然,我们也可以用类来编写这个装饰器——如下代码对每个类使用一个实例,而不是使用一个封闭作用域或全局表:

class singleton:
 def __init__(self,aClass):
 self.aClass = aClass
 self.instance = None
 def __call__(self,*args):
 if self.instance == None:
  self.instance = self.aClass(*args)
 return self.instance

跟踪对象接口

类装饰器的另一个常用场景是每个产生实例的接口。类装饰器基本上可以在实例上安装一个包装器逻辑层,来以某种方式管理其对接口的访问。

前面,我们知道可以用__getattr__运算符重载方法作为包装嵌入到实例的整个对象接口的方法,以便实现委托编码模式。__getattr__用于拦截未定义的属性名的访问。如下例子所示:

class Wrapper:
 def __init__(self,obj):
 self.wrapped = obj
 def __getattr__(self,attrname):
 print('Trace:',attrname)
 return getattr(self.wrapped,attrname)
>>> x = Wrapper([1,2,3])
>>> x.append(4)
Trace: append
>>> x.wrapped
[1, 2, 3, 4]
>>>
>>> x = Wrapper({'a':1,'b':2})
>>> list(x.keys())
Trace: keys
['b', 'a']

在这段代码中,Wrapper类拦截了对任何包装对象的属性的访问,打印出一条跟踪信息,并且使用内置函数getattr来终止对包装对象的请求。

类装饰器为编写这种__getattr__技术来包装一个完整接口提供了一个替代的、方便的方法。如下:

def Tracer(aClass):
  class Wrapper:
    def __init__(self,*args,**kargs):
      self.fetches = 0
      self.wrapped = aClass(*args,**kargs)
    def __getattr__(self,attrname):
      print('Trace:'+attrname)
      self.fetches += 1
      return getattr(self.wrapped,attrname)
  return Wrapper
@Tracer
class Spam:
  def display(self):
    print('Spam!'*8)
@Tracer
class Person:
  def __init__(self,name,hours,rate):
    self.name = name
    self.hours = hours
    self.rate = rate
  def pay(self):
    return self.hours * self.rate
food = Spam()
food.display()
print([food.fetches])
bob = Person('Bob',40,50)
print(bob.name)
print(bob.pay())
print('')
sue = Person('Sue',rate=100,hours = 60)
print(sue.name)
print(sue.pay())
print(bob.name)
print(bob.pay())
print([bob.fetches,sue.fetches])

通过拦截实例创建调用,这里的类装饰器允许我们跟踪整个对象接口,例如,对其任何属性的访问。

Spam和Person类的实例上的属性获取都会调用Wrapper类中的__getattr__逻辑,由于food和bob确实都是Wrapper的实例,得益于装饰器的实例创建调用重定向,输出如下:

Trace:display
Spam!Spam!Spam!Spam!Spam!Spam!Spam!Spam!
[1]
Trace:name
Bob
Trace:pay
2000
Trace:name
Sue
Trace:pay
6000
Trace:name
Bob
Trace:pay
2000
[4, 2]

示例:实现私有属性

如下的类装饰器实现了一个用于类实例属性的Private声明,也就是说,属性存储在一个实例上,或者从其一个类继承而来。不接受从装饰的类的外部对这样的属性的获取和修改访问,但是,仍然允许类自身在其方法中自由地访问那些名称。类似于Java中的private属性。

traceMe = False
def trace(*args):
  if traceMe:
    print('['+ ' '.join(map(str,args))+ ']')
def Private(*privates):
  def onDecorator(aClass):
    class onInstance:
      def __init__(self,*args,**kargs):
        self.wrapped = aClass(*args,**kargs)
      def __getattr__(self,attr):
        trace('get:',attr)
        if attr in privates:
          raise TypeError('private attribute fetch:'+attr)
        else:
          return getattr(self.wrapped,attr)
      def __setattr__(self,attr,value):
        trace('set:',attr,value)
        if attr == 'wrapped': # 这里捕捉对wrapped的赋值
          self.__dict__[attr] = value
        elif attr in privates:
          raise TypeError('private attribute change:'+attr)
        else: # 这里捕捉对wrapped.attr的赋值
          setattr(self.wrapped,attr,value)
    return onInstance
  return onDecorator
if __name__ == '__main__':
  traceMe = True
  @Private('data','size')
  class Doubler:
    def __init__(self,label,start):
      self.label = label
      self.data = start
    def size(self):
      return len(self.data)
    def double(self):
      for i in range(self.size()):
        self.data[i] = self.data[i] * 2
    def display(self):
      print('%s => %s'%(self.label,self.data))
  X = Doubler('X is',[1,2,3])
  Y = Doubler('Y is',[-10,-20,-30])
  print(X.label)
  X.display()
  X.double()
  X.display()
  print(Y.label)
  Y.display()
  Y.double()
  Y.label = 'Spam'
  Y.display()
  # 这些访问都会引发异常
  """
  print(X.size())
  print(X.data)
  X.data = [1,1,1]
  X.size = lambda S:0
  print(Y.data)
  print(Y.size())

这个示例运用了装饰器参数等语法,稍微有些复杂,运行结果如下:

[set: wrapped <__main__.Doubler object at 0x03421F10>]
[set: wrapped <__main__.Doubler object at 0x031B7470>]
[get: label]
X is
[get: display]
X is => [1, 2, 3]
[get: double]
[get: display]
X is => [2, 4, 6]
[get: label]
Y is
[get: display]
Y is => [-10, -20, -30]
[get: double]
[set: label Spam]
[get: display]
Spam => [-20, -40, -60]

更多关于Python相关内容可查看本站专题:《Python数据结构与算法教程》、《Python Socket编程技巧总结》、《Python函数使用技巧总结》、《Python字符串操作技巧汇总》及《Python入门与进阶经典教程》

希望本文所述对大家Python程序设计有所帮助。

Python 相关文章推荐
一个计算身份证号码校验位的Python小程序
Aug 15 Python
python和ruby,我选谁?
Sep 13 Python
python实现决策树
Dec 21 Python
Python数据分析之获取双色球历史信息的方法示例
Feb 03 Python
Python将图片转换为字符画的方法
Jun 16 Python
使用Python读取安卓手机的屏幕分辨率方法
Mar 31 Python
python爬虫自动创建文件夹的功能
Aug 01 Python
Python Pandas 获取列匹配特定值的行的索引问题
Jul 01 Python
Python Django的安装配置教程图文详解
Jul 17 Python
python常用数据重复项处理方法
Nov 22 Python
pycharm 代码自动补全的实现方法(图文)
Sep 18 Python
python Django框架快速入门教程(后台管理)
Jul 21 Python
Python实现的字典排序操作示例【按键名key与键值value排序】
Dec 21 #Python
Python简单获取二维数组行列数的方法示例
Dec 21 #Python
python进行TCP端口扫描的实现
Dec 21 #Python
Python实现将多个空格换为一个空格.md的方法
Dec 20 #Python
python解析json串与正则匹配对比方法
Dec 20 #Python
Django2.1集成xadmin管理后台所遇到的错误集锦(填坑)
Dec 20 #Python
python将一个英文语句以单词为单位逆序排放的方法
Dec 20 #Python
You might like
PHPEXCEL 使用小记
2013/01/06 PHP
自己写的兼容低于PHP 5.5版本的array_column()函数
2014/10/24 PHP
PHP实现全角字符转为半角方法汇总
2015/07/09 PHP
php抽象方法和抽象类实例分析
2016/12/07 PHP
PHP+Ajax实现的博客文章添加类别功能示例
2018/03/29 PHP
Javascript Math对象
2009/08/13 Javascript
javascript 动态调整图片尺寸实现代码
2009/12/28 Javascript
js 兼容多浏览器的回车和鼠标焦点事件代码(IE6/7/8,firefox,chrome)
2010/04/14 Javascript
JQuery AJAX 中文乱码问题解决
2013/06/05 Javascript
通过action传过来的值在option获取进行验证的方法
2013/11/14 Javascript
JS刷新当前页面的几种方法总结
2013/12/24 Javascript
JavaScript获取页面上被选中文字的方法技巧
2015/03/13 Javascript
JS获取随机数和时间转换的简单实例
2016/07/10 Javascript
微信小程序 图片绝对定位(背景图片)
2017/04/05 Javascript
详解RequireJS按需加载样式文件
2017/04/12 Javascript
Angular js 实现添加用户、修改密码、敏感字、下拉菜单的综合操作方法
2017/10/24 Javascript
原生JS实现 MUI导航栏透明渐变效果
2017/11/07 Javascript
axios拦截设置和错误处理方法
2018/03/05 Javascript
微信小程序实现通过js操作wxml的wxss属性示例
2018/12/06 Javascript
微信小程序实现的日期午别医生排班表功能示例
2019/01/09 Javascript
JavaScript键盘事件响应顺序详解
2019/09/30 Javascript
JavaScript手写数组的常用函数总结
2020/11/22 Javascript
[42:32]完美世界DOTA2联赛PWL S2 LBZS vs FTD.C 第二场 11.27
2020/12/01 DOTA
简述Python2与Python3的不同点
2018/01/21 Python
python cv2读取rtsp实时码流按时生成连续视频文件方式
2019/12/25 Python
Python操作Word批量生成合同的实现示例
2020/08/28 Python
python中的yield from语法快速学习
2020/11/06 Python
H5 video poster属性设置视频封面的方法
2020/05/25 HTML / CSS
购买瑞典当代设计的腕表和太阳眼镜:TRIWA
2016/10/30 全球购物
Foot Locker意大利官网:全球领先的运动鞋和服装零售商
2017/05/30 全球购物
英国网上自行车商店:Tredz Bikes
2019/10/29 全球购物
优秀信贷员先进事迹
2014/01/31 职场文书
产品发布会策划方案
2014/05/12 职场文书
2015年大学生实习评语
2015/03/25 职场文书
2016七夕情人节广告语
2016/01/28 职场文书
Redis官方可视化工具RedisInsight安装使用教程
2022/04/19 Redis