web-dev-qa-db-ja.com

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

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

誰かが私にこの特別な方法の実用的な使い方を教えてくれたら本当に感謝しています。

147
mohi666

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

84

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

ここでは、静的変数を含む階乗関数(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

Andまた、ステートフルです。

115
S.Lott

使いやすい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')

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

38
bradley.ayers

__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 __== "__main__":
    hello()
20
Kris

はい、オブジェクトを処理していることがわかっている場合、明示的なメソッド呼び出しを使用することは完全に可能です(多くの場合、お勧めします)。ただし、呼び出し可能なオブジェクト(通常は機能する)を予期するコードを扱う場合もありますが、__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つの関数を返す必要があり、ゆっくりと複雑になっています。 1つcould__call__を別の名前に変更し、バインドされたメソッドを渡しますが、それによりスレッドを作成するコードがわずかにわかりにくくなり、 t値を追加します。

9
user395760

クラスベースのデコレータは、__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 にさまざまなオプションの説明があります。

5
rorycl

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

3
ady

美しいと思う__call__()と一緒に__getattr__()の使用法を見つけました。オブジェクト内のJSON/HTTP /(however_serialized)APIの複数のレベルを隠すことができます。

__getattr__()部分は、同じクラスの変更されたインスタンスを繰り返し返して、一度にもう1つの属性を入力します。次に、すべての情報が使い果たされた後、__call__()が渡された引数を引き継ぎます。

このモデルを使用すると、たとえば、https://some.tld/api/v2/volumes/ssd/updateへのPUT要求で終わるapi.v2.volumes.ssd.update(size=20)のような呼び出しを行うことができます。

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

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

3
Peter Slovak

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

    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")             
1
Abhishek Jain

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

1
user2772852

一般的な例として、__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
endless

関数呼び出し演算子。

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

x = Foo()
x(1, 2, 3)
 </ code>

_ CALL _メソッドを使用して、同じオブジェクトを再定義/再初期化できます。また、オブジェクトに引数を渡すことにより、クラスのインスタンス/オブジェクトを関数として使用しやすくします。

0

__call__()を定義する呼び出し可能オブジェクトを使用する適切な場所は、map()filter()reduce()などのPythonの関数型プログラミング機能を使用する場合です。

プレーン関数またはラムダ関数で呼び出し可能オブジェクトを使用する最適なタイミングは、ロジックが複雑で、何らかの状態を保持する必要がある場合、または__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
cycgrog