python:类与装饰器

  • 2019 年 11 月 21 日
  • 筆記

简单装饰

在python中,装饰器可以是一个类。就是这么任性。 要把一个类做为装饰器是很简单的。这里借鉴一个很有意思的代码,python管道语法糖的实现,作者JulienPalard 项目

class Pipe:      def __init__(self, function):          self.function = function          functools.update_wrapper(self, function)        def __ror__(self, other):          return self.function(other)        def __call__(self, *args, **kwargs):          return Pipe(lambda x: self.function(x, *args, **kwargs))

可以这么用:

@Pipe  def average(iterable):      """      Build the average for the given iterable, starting with 0.0 as seed      Will try a division by 0 if the iterable is empty...      """      total = 0.0      qte = 0      for x in iterable:          total += x          qte += 1      return total / qte    [1, 3, 6] | average

神奇啊,神奇啊。我一看这份代码,就爱上了这个简洁的小项目。真是太有BG了。 但是,这个项目有一个小问题。那就是,它不能装饰类方法。

装饰类方法

类的函数和普通函数非常相似,在调用的时候会以self关键字传入当前实例作为参数。这是大家都明白的。 但值得一提的是,类函数有比较复杂的调用机制,当执行 cls.method的一瞬间,系统会调用该方法(注意不是类)的__get__(self, instance, cls)函数。instance就是所属类的实例,cls是类的签名。 所以当用上面Pipe类直接装饰一个类方法,将会丢失类的self关键字。要实现功能,必须获取运行时的instance实例,要获取instance,就必须hack__get__函数。 所以,代码变化成了下面这样:

import functools      class Pipe:      def __init__(self, function):          self.function = function          functools.update_wrapper(self, function)        def __ror__(self, other):          return self.function(other)        def __get__(self, instance, cls):          return Pipe(lambda x: self.function(instance, x))

要点如下: 1. 在get中是一个经过实例包装的pipe类实例。这个实例包装了instance。 2. 这个成员必须是Pipe类型,否则,普通的Lambda方法或者函数,没有实现xor,自然也不能和|连用。

这个类装饰器,也兼容普通的函数,因为普通函数不会触发__get__方法。所以,行为与原项目一致。