欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python类装饰器实现方法详解

程序员文章站 2022-05-17 14:48:17
本文实例讲述了python类装饰器。分享给大家供大家参考,具体如下: 编写类装饰器 类装饰器类似于函数装饰器的概念,但它应用于类,它们可以用于管理类自身,或者用来拦截实...

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