Pythonは類を使って装飾器の小さい技巧を書きます。


最近面白い飾り付けの書き方を勉強しました。メモしてください。
装飾器は戻り関数の関数です。装飾器を書きます。最も一般的な関数で関数を定義する以外に、クラスを使って装飾器を定義することもできます。
1、クラスで装飾器を書きます。
ここでは一般的な書き方でキャッシュデコレーションを実現しました。

def cache(func):
  data = {}
  def wrapper(*args, **kwargs):
    key = f'{func.__name__}-{str(args)}-{str(kwargs)})'
    if key in data:
      result = data.get(key)
      print('cached')
    else:
      result = func(*args, **kwargs)
      data[key] = result
      print('calculated')
    return result
  return wrapper
キャッシュの効果を見てください。

@cache
def rectangle_area(length, width):
  return length * width
rectangle_area(2, 3)
# calculated
# 6
rectangle_area(2, 3)
# cached
# 6
装飾器の@cacheは文法飴で、func = cache(func)に相当します。ここのcacheは関数ではなく、一つの種類はどうなりますか?クラスクラスクラスクラスクラスのクラスのcacheを定義すると、func = Cache(func)を呼び出して一つのオブジェクトを得ることができます。このとき戻ってくるfuncは実はCacheのオブジェクトです。定義__コールバック方法は、クラスのインスタンスを呼び出し可能なオブジェクトに変更してもよく、関数を呼び出すようにオブジェクトを呼び出すことができます。そしてグウグウコールバックメソッドに元々の機能を呼び出すと装飾器ができます。だからCache類も装飾器として使用でき、@Cacheの形で使用できます。
次にcache関数をCacheクラスに書き換えます。

class Cache:
  def __init__(self, func):
    self.func = func
    self.data = {}
  def __call__(self, *args, **kwargs):
    func = self.func
    data = self.data
    key = f'{func.__name__}-{str(args)}-{str(kwargs)})'
    if key in data:
      result = data.get(key)
      print('cached')
    else:
      result = func(*args, **kwargs)
      data[key] = result
      print('calculated')
    return result
キャッシュの結果を見ても、効果は同じです。

@Cache
def rectangle_area(length, width):
  return length * width
rectangle_area(2, 3)
# calculated
# 6
rectangle_area(2, 3)
# cached
# 6
2、装飾類の方法
装飾器は装飾関数だけでなく、装飾類の方法にもよく使われていますが、類で書いた装飾器は直接装飾類の方法には使えないことが分かりました。ちょっと回りくどいです
まず、関数で書いた装飾器の種類の飾り方を見てください。

class Rectangle:
  def __init__(self, length, width):
    self.length = length
    self.width = width
  @cache
  def area(self):
    return self.length * self.width
r = Rectangle(2, 3)
r.area()
# calculated
# 6
r.area()
# cached
# 6
しかし、直接Cacheクラスに変えると、エラーが発生します。このエラーの原因は、araが装飾された後、クラスの属性になりました。方法ではありません。

class Rectangle:
  def __init__(self, length, width):
    self.length = length
    self.width = width
  @Cache
  def area(self):
    return self.length * self.width
r = Rectangle(2, 3)
r.area()
# TypeError: area() missing 1 required positional argument: 'self'
Rectangle.area
# <__main__.Cache object at 0x0000012D8E7A6D30>
r.area
# <__main__.Cache object at 0x0000012D8E7A6D30>
振り返ってみます。飾りがない場合は、Pythonは対象を実用化して関数を方法に変えました。

class Rectangle:
  def __init__(self, length, width):
    self.length = length
    self.width = width

  def area(self):
    return self.length * self.width

Rectangle.area
# <function Rectangle.area at 0x0000012D8E7B28C8>
r = Rectangle(2, 3)
r.area
# <bound method Rectangle.area of <__main__.Rectangle object
したがって、解決方法は簡単です。クラスで書かれた装飾器でクラスを飾る方法は、オブジェクトを関数として包装するだけでいいです。

#           ,     ,              
def method(call):
  def wrapper(*args, **kwargs):
    return call(*args, **kwargs)
  return wrapper
class Rectangle:
  def __init__(self, length, width):
    self.length = length
    self.width = width
  @method
  @Cache
  def area(self):
    return self.length * self.width
r = Rectangle(2, 3)
r.area()
# calculated
# 6
r.area()
# cached
# 6
あるいは@propertyで直接に方法を属性に変えられます。

class Rectangle:
  def __init__(self, length, width):
    self.length = length
    self.width = width
  @property
  @Cache
  def area(self):
    return self.length * self.width
r = Rectangle(2, 3)
r.area
# calculated
# 6
r.area
# cached
# 6
締め括りをつける
クラスで装飾器を書くのは特別なテクニックではありません。普通はこのように書く必要はありませんが、いくつかの種類の特性で装飾器を書くことができます。