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统计文件行数示例分享
Feb 21 Python
Python采集腾讯新闻实例
Jul 10 Python
Python线程中对join方法的运用的教程
Apr 09 Python
详解Python的Django框架中的中间件
Jul 24 Python
python日志记录模块实例及改进
Feb 12 Python
单利模式及python实现方式详解
Mar 20 Python
Python实现的质因式分解算法示例
May 03 Python
使用python爬取B站千万级数据
Jun 08 Python
Python使用字典的嵌套功能详解
Feb 27 Python
解析python实现Lasso回归
Sep 11 Python
Python 类,property属性(简化属性的操作),@property,property()用法示例
Oct 12 Python
Python安装第三方库攻略(pip和Anaconda)
Oct 15 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
那些年一起学习的PHP(二)
2012/03/21 PHP
PHP7新增函数
2021/03/09 PHP
获取URL地址中的文件名和参数的javascript代码
2009/09/02 Javascript
写了10年的Javascript也未必全了解的连续赋值运算
2011/03/25 Javascript
遍历jquery对象的代码分享
2011/11/02 Javascript
定时器(setTimeout/setInterval)调用带参函数失效解决方法
2013/03/26 Javascript
纯Javascript实现Windows 8 Metro风格实现
2013/10/15 Javascript
影响jQuery使用的14个方面
2014/09/01 Javascript
jQuery如何防止这种冒泡事件发生
2015/02/27 Javascript
jQuery下拉友情链接美化效果代码分享
2015/08/26 Javascript
理解javascript中try...catch...finally
2015/12/25 Javascript
jQuery中 $ 符号的冲突问题及解决方案
2016/11/04 Javascript
Nodejs之http的表单提交
2017/07/07 NodeJs
Bootstrap popover 实现鼠标移入移除显示隐藏功能方法
2018/01/24 Javascript
Angularjs实现控制器之间通信方式实例总结
2018/03/27 Javascript
微信小程序使用蓝牙小插件
2019/09/23 Javascript
jQuery弹框插件使用方法详解
2020/05/26 jQuery
Python发送email的3种方法
2015/04/28 Python
Django uwsgi Nginx 的生产环境部署详解
2019/02/02 Python
Python使用sax模块解析XML文件示例
2019/04/04 Python
使用python实现男神女神颜值打分系统(推荐)
2019/10/31 Python
基于pytorch的lstm参数使用详解
2020/01/14 Python
html5理解head_动力节点Java学院整理
2017/07/13 HTML / CSS
a标签下载链接的简单实现
2016/09/13 HTML / CSS
阿里健康官方海外旗舰店:阿里健康国际自营
2017/11/24 全球购物
一些Solaris面试题
2013/03/22 面试题
英语专业职业生涯规划范文
2014/03/05 职场文书
个人合作协议书范本
2014/04/18 职场文书
公司承诺书怎么写
2014/05/24 职场文书
课外科技活动总结
2014/08/27 职场文书
师德师风的心得体会
2014/09/02 职场文书
2014年售后服务工作总结
2014/11/18 职场文书
2015年度服装销售工作总结
2015/03/31 职场文书
golang 定时任务方面time.Sleep和time.Tick的优劣对比分析
2021/05/05 Golang
SQLServer中JSON文档型数据的查询问题解决
2021/06/27 SQL Server
MySQL数据库如何查看表占用空间大小
2022/06/10 MySQL