まとめへのリンク
はじめに
Pythonの勉強をするために、acopyという群知能ライブラリを写経していました。
acopyでは、多くのPythonの面白い文法・イディオムが使われており、その中でも便利だなぁというのをまとめています。
今回は、クラスのインスタンスを関数のように呼び出す__call__
属性についてみてみます。
__call__
属性メソッド
__call__
属性をクラスに定義すると、
インスタンスを()
演算子で関数のように呼び出すことができます。
class Person:
def __init__(self, age, name):
self.age = age
self.name = name
def __repr__(self):
return f'age = {self.age}, name = {self.name}'
def __call__(self, *args, **kwargs):
return self.name
'''
age = 20, name = ron
ron
'''
p = Person(20, 'ron')
print(p)
print(p())
以上の例では、Personクラスに__call__
属性が定義されています。
これによって、関数呼び出しのようにインスタンスが呼び出されると、pの名前を返します。
このように、関数のように呼び出せるようにするのが、__call__
です。
活用例
それでは、この()
を使うとどう嬉しいことがあるのでしょうか?
単純なインターフェース
あまりするべきで無い気がしますが、単純なインターフェースとして
使用できるのかなと思います。
class Add:
def __init__(self, x):
self.x = x
def __call__(self, y):
return self.x + y
class Sub:
def __init__(self, x):
self.x = x
def __call__(self, y):
return self.x - y
'''
20
10
50
'''
arr = [Add(10), Sub(20), Add(40)]
for ice in arr:
print(ice(10))
なんか違う気がしますね。
関数に状態をもたせる
主にこれの意味があるのかなと思います。
C++では、structに()
を持たす関数オブジェクトというものが存在します。
それについて触れた自分の記事です。(俺のunordered_mapがこんなにpair型をキーにできないわけがない)
関数には状態を持たせづらいですが、クラスには状態を簡単に持たすことができます。
すると、()
演算子の使用回数や、重い処理のDPのような計算結果の取得などが可能になります。
class Count:
cls_count = 0
def __init__(self):
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
Count.cls_count += 1
return self.count, Count.cls_count
c1 = Count()
c2 = Count()
'''
(1, 1)
(1, 2)
(2, 3)
(3, 4)
(2, 5)
'''
print(c1())
print(c2())
print(c1())
print(c1())
print(c2())
以上のコードでは、Countクラスを作り、それを関数呼び出ししています。
内部でcls_count, count
という変数・内部状態を保存しています。
以上は適当な例ですが、()
という関数呼び出しに内部状態を保存することができます。
また、以下はエラトステネスの篩を関数呼び出ししています。
一回計算したあとは、内部キャッシュした値を返しています。
class Era:
def __init__(self, N):
self.N = N
self.primes = []
self.ready = False
def __call__(self, x):
if self.ready:
return self.primes[x]
else:
self.primes = [True] * (self.N + 1)
self.primes[0] = self.primes[1] = False
i = 2
while i * i <= self.N:
if self.primes[i]:
for j in range(i * 2, N + 1, i):
self.primes[j] = False
i += 1
return self.primes[x]
N = 100
era = Era(N)
for i in range(0, N + 1):
print(i, era(i))
このように、外部からは()
で関数のように呼び出したいですが、内部状態をもたせたいときは、__call__
を使用すると良さそうです。