Python __call__特殊メソッドの実用的な例


159

__call__クラスのインスタンスが呼び出されると、クラスのメソッドがトリガーされることを知っています。ただし、この特別なメソッドをいつ使用できるかわかりません。単に新しいメソッドを作成して、メソッドで行われたのと同じ操作を実行__call__でき、インスタンスを呼び出す代わりにメソッドを呼び出すことができるためです。

誰かがこの特別な方法の実用的な使用法を教えてくれれば本当にありがたいです。



8
_call_の機能は、C ++の()のオーバーロードされた演算子と同じです。クラス外で新しいメソッドを作成するだけでは、クラスの内部データにアクセスできない場合があります。
アンディ

2
の最も一般的な使用法__call__は、プレーンビューに隠されています。これx = Foo()は、クラスをインスタンス化する方法です。is は本当にx = type(Foo).__call__(Foo)__call__、はのメタクラスによって定義されますFoo
chepner

回答:


88

Djangoフォームモジュールは__call__メソッドを適切に使用して、フォーム検証用の一貫したAPIを実装します。Djangoのフォームに独自のバリデーターを関数として書くことができます。

def custom_validator(value):
    #your validation logic

Djangoには、メールバリデーター、URLバリデーターなど、デフォルトの組み込みバリデーターがいくつかあります。これらは、広くRegExバリデーターの傘下にあります。これらをきれいに実装するために、Djangoは(関数ではなく)呼び出し可能なクラスに頼っています。RegexValidatorにデフォルトのRegex検証ロジックを実装し、他の検証用にこれらのクラスを拡張します。

class RegexValidator(object):
    def __call__(self, value):
        # validation logic

class URLValidator(RegexValidator):
    def __call__(self, value):
        super(URLValidator, self).__call__(value)
        #additional logic

class EmailValidator(RegexValidator):
    # some logic

これで、カスタム関数と組み込みのEmailValidatorの両方を同じ構文で呼び出すことができます。

for v in [custom_validator, EmailValidator()]:
    v(value) # <-----

ご覧のとおり、Djangoでのこの実装は、他の人が以下の回答で説明したものと似ています。これは他の方法で実装できますか?できますが、私見では、Djangoのような大きなフレームワークに対しては、読みやすく、拡張も容易ではありません。


5
したがって、正しく使用すると、コードが読みやすくなります。間違った場所で使用すると、コードも非常に読みにくくなると思います。
mohi666

15
これはそれがどのように使用されるか例ですが、私の意見では良いものではありません。この場合、呼び出し可能なインスタンスを持つことには利点はありません。.validate();のようなメソッドを持つインターフェース/抽象クラスを用意することをお勧めします。同じことはもっとはっきりしているだけです。__call__の真の価値は、callableが期待される場所でインスタンスを使用できることです。たとえば、デコレータを作成するときは、__ call__を最も頻繁に使用します。
ダニエル

120

この例ではメモ化を使用しており、基本的に値をテーブル(この場合は辞書)に格納しているため、後で再計算する代わりに後で調べることができます。

ここでは、静的変数を含む階乗関数(Pythonでは不可能)の代わりに、__call__呼び出し可能オブジェクトを介して)階乗を計算するメソッドを持つ単純なクラスを使用します

class Factorial:
    def __init__(self):
        self.cache = {}
    def __call__(self, n):
        if n not in self.cache:
            if n == 0:
                self.cache[n] = 1
            else:
                self.cache[n] = n * self.__call__(n-1)
        return self.cache[n]

fact = Factorial()

これでfact、他のすべての関数と同様に、呼び出し可能なオブジェクトができました。例えば

for i in xrange(10):                                                             
    print("{}! = {}".format(i, fact(i)))

# output
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880

そしてそれはまたステートフルです。


2
私はむしろ必要があるだろうfact、あなたのため、インデックス可能であるオブジェクト__call__の機能は基本的にインデックスです。また、dictの代わりにリストを使用しますが、それは私だけです。
Chris Lutz、

4
@delnan-ほとんど何でも、いくつかの異なる方法で実行できます。どちらがより読みやすいかは、読者によって異なります。
Chris Lutz

1
@Chris Lutz:そのような種類の変更を自由に考えることができます。一般的なメモ化では、リストがいっぱいになる順序を保証できないため、辞書がうまく機能します。この場合、リストは機能する可能性がありますが、速くも簡単にもなりません。
S.Lott、2011

8
@delnan:これは最短にすることを意図したものではありません。コードゴルフでは誰も勝てません。これは__call__、を示し、単純であり、それ以上のものではありません。
S.Lott、2011

3
しかし、それは、実証された手法がタスクに理想的でない場合の例を台無しにしますね。(そして、私は「そのための行を保存しましょう」についてではなく、「これを同じように明確な方法で記述し、定型コードを保存する」について話していました。短いので安心してください。可能な限り最短のコードを書こうとしている狂人の1人、私は読者に何も追加しないボイラープレートコードを避けたいだけです。)

40

使いやすく(特定の引数を必要とする呼び出し可能なオブジェクトがいくつかあります)、オブジェクト指向の手法を使用できるので実装が簡単なAPIを作成できるので便利です。

以下は、昨日書いたコードhashlib.fooで、文字列ではなくファイル全体をハッシュするメソッドのバージョンを作成します。

# filehash.py
import hashlib


class Hasher(object):
    """
    A wrapper around the hashlib hash algorithms that allows an entire file to
    be hashed in a chunked manner.
    """
    def __init__(self, algorithm):
        self.algorithm = algorithm

    def __call__(self, file):
        hash = self.algorithm()
        with open(file, 'rb') as f:
            for chunk in iter(lambda: f.read(4096), ''):
                hash.update(chunk)
        return hash.hexdigest()


md5    = Hasher(hashlib.md5)
sha1   = Hasher(hashlib.sha1)
sha224 = Hasher(hashlib.sha224)
sha256 = Hasher(hashlib.sha256)
sha384 = Hasher(hashlib.sha384)
sha512 = Hasher(hashlib.sha512)

この実装により、関数と同様の方法で関数を使用できhashlib.fooます。

from filehash import sha1
print sha1('somefile.txt')

もちろん、別の方法で実装することもできますが、この場合は単純なアプローチのように見えました。


7
繰り返しになりますが、クロージャーはこの例を台無しにします。pastebin.com/961vU0ayは行の 80%で、同じくらい明確です。

8
私はそれが常に誰かと同じくらいはっきりしているとは確信していません(たぶんJavaだけを使用したことがある人など)。ネストされた関数と変数のルックアップ/スコープは混乱するかもしれません。私のポイントは、__call__OO手法を使用して問題を解決できるツールを提供することだったと思います。
bradley.ayers

4
どちらも同等の機能を提供する「なぜXをYよりも使用するのか」という質問は、ひどく主観的だと思います。OOアプローチの方が理解しやすい人もいれば、クロージャーアプローチの人もいます。あなたが持っていたような状況がない限り、他の上で1つを使用するには、何の説得力のある引数はありませんでした使用isinstanceまたは類似した何かが。
bradley.ayers

2
@delnanクロージャの例はコード行が少ないですが、それと同じくらい明確であることは議論するのがより困難です。
デニス

8
__call__クロージャーの代わりにメソッドを使用したい場合の例は、ピクルを使用してプロセス間で情報を渡すマルチプロセッシングモジュールを扱っている場合です。クロージャをピクルすることはできませんが、クラスのインスタンスをピクルすることはできます。
John Peter ThompsonGarcés2013

21

__call__Pythonでデコレータクラスを実装するためにも使用されます。この場合、デコレータ付きのメソッドが呼び出されると、クラスのインスタンスが呼び出されます。

class EnterExitParam(object):

    def __init__(self, p1):
        self.p1 = p1

    def __call__(self, f):
        def new_f():
            print("Entering", f.__name__)
            print("p1=", self.p1)
            f()
            print("Leaving", f.__name__)
        return new_f


@EnterExitParam("foo bar")
def hello():
    print("Hello")


if __name__ == "__main__":
    hello()

9

はい、オブジェクトを扱っていることがわかっている場合は、明示的なメソッド呼び出しを使用することが完全に可能です(多くの場合は推奨されます)。ただし、呼び出し可能なオブジェクト(通常は機能しますが、__call__場合がありますが、インスタンスデータや、呼び出し可能な繰り返しのタスクなどを委任するメソッドなどを使用して、より複雑なオブジェクトを構築できます。

また、複雑なタスク(専用クラスを書くのが理にかなっている場合)と単純なタスク(関数に既に存在するか、関数としてより簡単に記述できる場合)の両方のオブジェクトを使用している場合もあります。共通のインターフェースを使用するには、これらの関数を期待されるインターフェースでラップする小さなクラスを作成するか、または関数を保持してより複雑なオブジェクトを呼び出し可能にする必要があります。スレッドを例に考えてみましょう。Thread標準ライブラリモジュールthreadingオブジェクトには、target引数として(つまり、新しいスレッドで実行されるアクションとして)呼び出し可能オブジェクトが必要です。呼び出し可能オブジェクトを使用すると、関数に限定されず、他のスレッドからタスクを実行してそれらを順次実行する比較的複雑なワーカーなど、他のオブジェクトも渡すことができます。

class Worker(object):
    def __init__(self, *args, **kwargs):
        self.queue = queue.Queue()
        self.args = args
        self.kwargs = kwargs

    def add_task(self, task):
        self.queue.put(task)

    def __call__(self):
        while True:
            next_action = self.queue.get()
            success = next_action(*self.args, **self.kwargs)
            if not success:
               self.add_task(next_action)

これは頭​​の上の例にすぎませんが、クラスを正当化するのに十分なほど複雑になっていると思います。関数だけでこれを行うのは困難です。少なくとも、2つの関数を返す必要があり、徐々に複雑になっています。一つは、可能性が名前を変更__call__別のものに限定法を渡すが、それはやや明瞭でスレッドを作成するコードを作成し、任意の値を追加しません。


3
ここで「ダックタイピング」という語句(en.wikipedia.org/wiki/Duck_typing#In_Python)を使用すると便利です。この方法で、より複雑なクラスオブジェクトを使用して関数を模倣できます。
Andrew Jaffe

2
関連する例として、__call__WSGIアプリケーションとして(関数の代わりに)クラスインスタンスを使用するのを見てきました。「パイロンの決定的なガイド」の例を次に示します。クラスのインスタンスの使用
ジョシュローゼン

5

クラスベースのデコレータは__call__、ラップされた関数を参照するために使用します。例えば:

class Deco(object):
    def __init__(self,f):
        self.f = f
    def __call__(self, *args, **kwargs):
        print args
        print kwargs
        self.f(*args, **kwargs)

Artima.comには、さまざまなオプションの詳しい説明があります。


ただし、メソッドを操作するためにいくつかの非自明なボイラープレートコードが必要になるため、クラスデコレータはほとんど見ません。

4

IMHO __call__メソッドとクロージャーは、PythonでSTRATEGYデザインパターンを作成する自然な方法を提供します。アルゴリズムのファミリーを定義し、それぞれをカプセル化し、それらを交換可能にして、最終的に、共通の一連のステップを実行し、たとえば、ファイルのハッシュを計算できます。


4

綺麗だと思う__call__()コンサートの使い方を偶然見つけました__getattr__()。オブジェクト内のJSON / HTTP /(however_serialized)APIの複数のレベルを非表示にすることができます。

この__getattr__()パーツは、同じクラスの変更されたインスタンスを繰り返し返すことを担当し、一度に1つ以上の属性を入力します。次に、すべての情報を使い果たした後、__call__()渡した引数を引き継ぎます。

このモデルを使用すると、たとえばのような呼び出しを行うことができapi.v2.volumes.ssd.update(size=20)、最終的にはへのPUTリクエストになりhttps://some.tld/api/v2/volumes/ssd/updateます。

特定のコードは、OpenStackの特定のボリュームバックエンド用のブロックストレージドライバーです。https//github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc.pyで確認できます。

編集:マスターリビジョンを指すようにリンクを更新しました。


それはすばらしい。以前は、同じメカニズムを使用して、属性アクセスを使用して任意のXMLツリーをトラバースしました。
ペトリ

1

メソッドを指定し__metaclass____call__メソッドをオーバーライドし、指定されたメタクラスの__new__メソッドがクラスのインスタンスを返すようにします。ビオラにはメソッドを持つ「関数」があります。


1

__call__methodを使用して、他のクラスメソッドを静的メソッドとして使用できます。

    class _Callable:
        def __init__(self, anycallable):
            self.__call__ = anycallable

    class Model:

        def get_instance(conn, table_name):

            """ do something"""

        get_instance = _Callable(get_instance)

    provs_fac = Model.get_instance(connection, "users")             

0

一つの一般的な例である__call__functools.partialここで、簡略化バージョンである(とはPython> = 3.5)。

class partial:
    """New function with partial application of the given arguments and keywords."""

    def __new__(cls, func, *args, **kwargs):
        if not callable(func):
            raise TypeError("the first argument must be callable")
        self = super().__new__(cls)

        self.func = func
        self.args = args
        self.kwargs = kwargs
        return self

    def __call__(self, *args, **kwargs):
        return self.func(*self.args, *args, **self.kwargs, **kwargs)

使用法:

def add(x, y):
    return x + y

inc = partial(add, y=1)
print(inc(41))  # 42

0

関数呼び出し演算子。

class Foo:
    def __call__(self, a, b, c):
        # do something

x = Foo()
x(1, 2, 3)

__call__方法は/再初期化、同じオブジェクトを再定義するために使用することができます。また、オブジェクトに引数を渡すことで、クラスのインスタンス/オブジェクトを関数として簡単に使用できます。


いつ役立つのでしょうか?Foo(1、2、3)はより明確に見えます。
Yaroslav Nikitenko

0

私は、呼び出し可能オブジェクトを使用するのに適した場所を見つけ、定義しているものが__call__()のような、Pythonで関数型プログラミング機能を使用する場合、ありますmap()filter()reduce()

プレーン関数またはラムダ関数に対して呼び出し可能オブジェクトを使用するのに最適なタイミングは、ロジックが複雑で、状態を保持する必要がある場合や、__call__()関数に渡されない他の情報を使用する場合です。

次に、呼び出し可能なオブジェクトとを使用して、ファイル名拡張子に基づいてファイル名をフィルタリングするコードをいくつか示しますfilter()

呼び出し可能:

import os

class FileAcceptor(object):
    def __init__(self, accepted_extensions):
        self.accepted_extensions = accepted_extensions

    def __call__(self, filename):
        base, ext = os.path.splitext(filename)
        return ext in self.accepted_extensions

class ImageFileAcceptor(FileAcceptor):
    def __init__(self):
        image_extensions = ('.jpg', '.jpeg', '.gif', '.bmp')
        super(ImageFileAcceptor, self).__init__(image_extensions)

使用法:

filenames = [
    'me.jpg',
    'me.txt',
    'friend1.jpg',
    'friend2.bmp',
    'you.jpeg',
    'you.xml']

acceptor = ImageFileAcceptor()
image_filenames = filter(acceptor, filenames)
print image_filenames

出力:

['me.jpg', 'friend1.jpg', 'friend2.bmp', 'you.jpeg']

0

これは遅すぎますが、私は例を挙げています。あなたが持っている想像VectorクラスとPointクラスを。どちらもx, y位置引数として使用します。ベクトル上に配置するポイントを移動する関数を作成したいとしましょう。

4ソリューション

  • put_point_on_vec(point, vec)

  • これをvectorクラスのメソッドにします。例えば my_vec.put_point(point)

  • それをPointクラスのメソッドにします。my_point.put_on_vec(vec)
  • Vector実装__call__するので、次のように使用できますmy_vec_instance(point)

これは実際には、遅かれ早かれリリースするMathsで説明されているdunderメソッドのガイドのために取り組んでいるいくつかの例の一部です。

これはこの質問についてのことではないので、ポイント自体を移動する論理を残しました

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.