Python类装饰器实现方法详解

作者:KLeonard 时间:2021-11-15 20:01:05 

本文实例讲述了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程序设计有所帮助。

来源:https://blog.csdn.net/gavin_john/article/details/50923988

标签:Python,类装饰器
0
投稿

猜你喜欢

  • Oracle 的入门心得 强烈推荐

    2009-05-24 19:55:00
  • 如何提升JavaScript的运行速度(DOM篇)

    2010-05-17 13:32:00
  • PHP邮箱验证示例教程

    2023-06-20 12:01:47
  • php中的登陆login

    2023-10-08 10:49:30
  • 网站设计配色方案教程

    2007-10-10 19:38:00
  • Div+CSS布局入门教程

    2007-09-13 12:52:00
  • 解决python2中unicode()函数在python3中报错的问题

    2023-07-15 03:15:12
  • 使用 TRUNCATE TABLE 删除所有行

    2008-04-24 19:20:00
  • php动态函数调用方法

    2023-11-15 00:18:30
  • php生成4位数字验证码的实现代码

    2023-11-20 15:00:12
  • Oracle merge合并更新函数实例详解

    2023-07-23 02:45:02
  • Python3.5字符串常用操作实例详解

    2023-08-31 00:25:53
  • 利用Django-environ如何区分不同环境

    2022-01-10 10:52:24
  • PHP错误Warning: Cannot modify header information - headers already sent by解决方法

    2023-11-15 11:53:16
  • 怎么写好一份图形界面设计师简历

    2009-04-16 13:10:00
  • 为什么视觉设计师需要懂HTML

    2009-06-25 14:16:00
  • Oracle如何直接运行OS命令(上)第1/2页

    2010-07-30 12:54:00
  • 抛砖:如何进行互联网项目开发

    2010-01-25 12:25:00
  • SQL Server数据库查询优化的常用方法总结

    2008-12-10 14:43:00
  • 使用字符串建立查询能加快服务器的解析速度吗?

    2010-07-14 21:03:00
  • asp之家 网络编程 m.aspxhome.com