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中函数的参数传递
Jun 21 Python
python爬取个性签名的方法
Jun 17 Python
深入flask之异步非堵塞实现代码示例
Jul 31 Python
python中协程实现TCP连接的实例分析
Oct 14 Python
详解python读取和输出到txt
Mar 29 Python
django的auth认证,authenticate和装饰器功能详解
Jul 25 Python
python网络编程:socketserver的基本使用方法实例分析
Apr 09 Python
Python 添加文件注释和函数注释操作
Aug 09 Python
python Polars库的使用简介
Apr 21 Python
python使用tkinter实现透明窗体上绘制随机出现的小球(实例代码)
May 17 Python
PYTHON InceptionV3模型的复现详解
May 06 Python
Python加密与解密模块hashlib与hmac
Jun 05 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
非洲第一个咖啡超凡杯大赛承办国—卢旺达的咖啡怎么样
2021/03/03 咖啡文化
修改php.ini实现Mysql导入数据库文件最大限制的修改方法
2007/12/11 PHP
php中使用Akismet防止垃圾评论的代码
2011/06/10 PHP
PHP CURL CURLOPT参数说明(curl_setopt)
2013/09/30 PHP
eaglephp使用微信api接口开发微信框架
2014/01/09 PHP
windows的文件系统机制引发的PHP路径爆破问题分析
2014/07/28 PHP
php基础设计模式大全(注册树模式、工厂模式、单列模式)
2015/08/31 PHP
php文件上传的两种实现方法
2016/04/04 PHP
解决PHP使用CURL发送GET请求时传递参数的问题
2019/10/11 PHP
Laravel 微信小程序后端实现用户登录的示例代码
2019/11/26 PHP
ExtJS 学习专题(一) 如何应用ExtJS(附实例)
2010/03/11 Javascript
Jquery遍历节点的方法小集
2014/01/22 Javascript
jQuery中toggleClass()方法用法实例
2015/01/05 Javascript
JS遍历数组和对象的区别及递归遍历对象、数组、属性的方法详解
2016/06/14 Javascript
Bootstrap基本样式学习笔记之标签(5)
2016/12/07 Javascript
Bootstrap CSS布局之列表
2016/12/15 Javascript
Bootstrap table两种分页示例
2016/12/23 Javascript
js+html5生成自动排列对话框实例
2017/10/09 Javascript
JavaScript基于面向对象实现的无缝滚动轮播示例
2020/01/17 Javascript
用Javascript实现发送短信验证码间隔功能
2021/02/08 Javascript
[46:47]完美世界DOTA2联赛PWL S2 FTD vs Magma 第二场 11.20
2020/11/23 DOTA
在Python程序中操作文件之isatty()方法的使用教程
2015/05/24 Python
python pandas写入excel文件的方法示例
2019/06/25 Python
python绘图模块matplotlib示例详解
2019/07/26 Python
详解pandas中利用DataFrame对象的.loc[]、.iloc[]方法抽取数据
2020/12/13 Python
CSS3 mask 遮罩的具体使用方法
2017/11/03 HTML / CSS
HTML5页面音视频在微信和app下自动播放的实现方法
2016/10/20 HTML / CSS
Easy Spirit官网:美国休闲鞋履中的代表品牌
2019/04/12 全球购物
俄罗斯EPL钻石珠宝店:ЭПЛ
2019/10/22 全球购物
比较基础的php面试题及答案-编程题
2012/10/14 面试题
会议邀请函范文
2014/01/09 职场文书
党的群众路线教育实践活动批评与自我批评
2014/02/16 职场文书
煤矿安全演讲稿
2014/05/09 职场文书
员工安全生产责任书
2014/07/22 职场文书
Python可视化神器pyecharts绘制地理图表
2022/07/07 Python
JavaScript圣杯布局与双飞翼布局实现案例详解
2022/08/05 Javascript