168

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

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

4

14 に答える 14

129

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

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

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

また、ステートフルでもあります。

于 2011-04-28T20:56:34.367 に答える
92

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

def custom_validator(value):
    #your validation logic

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

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 のような大きなフレームワークでは、読みやすく、簡単に拡張することはできません。

于 2011-04-28T23:33:48.793 に答える
41

オブジェクト指向のプラクティスを使用できるため、使いやすく (特定の引数を必要とする呼び出し可能なオブジェクトがある場合)、簡単に実装できる 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')

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

于 2011-04-28T21:05:21.313 に答える
34

__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()

プログラム出力:

Entering hello
p1= foo bar
Hello
Leaving hello
于 2014-09-15T09:08:47.920 に答える
11

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

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

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__場合、スレッドを作成するコードが少しわかりにくくなり、何の価値もありません。

于 2011-04-28T21:06:04.160 に答える
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には、さまざまなオプションの詳細な説明があります。

于 2011-04-28T21:12:11.153 に答える
4

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

于 2012-11-02T16:23:16.233 に答える
1

a を指定し__metaclass__てメソッドをオーバーライドし__call__、指定されたメタ クラスの__new__メソッドがクラスのインスタンスを返すようにします。viola には、メソッドを含む「関数」があります。

于 2013-09-12T13:34:31.527 に答える