Python __call__ metode khusus contoh praktis

159

Saya tahu bahwa __call__metode dalam suatu kelas dipicu ketika instance dari sebuah kelas dipanggil. Namun, saya tidak tahu kapan saya bisa menggunakan metode khusus ini, karena orang dapat dengan mudah membuat metode baru dan melakukan operasi yang sama dilakukan dalam __call__metode dan alih-alih memanggil contoh, Anda dapat memanggil metode.

Saya akan sangat menghargai jika seseorang memberi saya penggunaan praktis metode khusus ini.

mohi666
sumber
8
fungsi _call_ sama seperti operator kelebihan () di C ++ . Jika Anda hanya membuat metode baru di luar kelas, Anda mungkin tidak dapat mengakses data internal di kelas.
andy
2
Penggunaan yang paling umum __call__disembunyikan dalam tampilan biasa; itu bagaimana Anda instantiate kelas: x = Foo()benar-benar x = type(Foo).__call__(Foo), di mana __call__didefinisikan oleh metaclass dari Foo.
chepner

Jawaban:

88

Modul formulir Django menggunakan __call__metode dengan baik untuk mengimplementasikan API yang konsisten untuk validasi formulir. Anda dapat menulis validator Anda sendiri untuk formulir di Django sebagai fungsi.

def custom_validator(value):
    #your validation logic

Django memiliki beberapa validator bawaan bawaan seperti validator email, validator url dll., Yang secara luas berada di bawah payung validator RegEx. Untuk mengimplementasikan ini dengan bersih, Django menggunakan kelas yang dapat dipanggil (alih-alih fungsi). Ini menerapkan logika Validasi Regex default di RegexValidator dan kemudian memperluas kelas-kelas ini untuk validasi lainnya.

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

Sekarang fungsi kustom Anda dan EmailValidator bawaan dapat dipanggil dengan sintaks yang sama.

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

Seperti yang Anda lihat, implementasi di Django ini mirip dengan apa yang orang lain jelaskan dalam jawaban mereka di bawah ini. Bisakah ini diimplementasikan dengan cara lain? Anda bisa, tetapi IMHO itu tidak akan mudah dibaca atau mudah diperluas untuk kerangka kerja besar seperti Django.

Praveen Gollakota
sumber
5
Jadi jika digunakan dengan benar, itu bisa membuat kode lebih mudah dibaca. Saya berasumsi jika digunakan di tempat yang salah, itu akan membuat kode juga sangat tidak terbaca.
mohi666
15
Ini adalah sebuah contoh bagaimana hal itu dapat digunakan, tetapi tidak bagus menurut saya. Dalam hal ini tidak ada manfaatnya memiliki instance yang dapat dipanggil. Akan lebih baik untuk memiliki antarmuka / kelas abstrak dengan metode, seperti .validate (); itu hal yang sama hanya lebih eksplisit. Nilai sebenarnya dari __call__ adalah dapat menggunakan instance di tempat di mana callable diharapkan. Saya menggunakan __call__ paling sering saat membuat dekorator, misalnya.
Daniel
120

Contoh ini menggunakan memoisasi , yang pada dasarnya menyimpan nilai dalam sebuah tabel (kamus dalam kasus ini) sehingga Anda dapat mencarinya nanti alih-alih menghitung ulang.

Di sini kita menggunakan kelas sederhana dengan __call__metode untuk menghitung faktorial (melalui objek yang bisa dipanggil ) alih-alih fungsi faktorial yang berisi variabel statis (karena itu tidak mungkin dengan 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()

Sekarang Anda memiliki factobjek yang dapat dipanggil, sama seperti fungsi lainnya. Sebagai contoh

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

Dan itu juga stateful.

S.Lott
sumber
2
Saya lebih suka memiliki factobjek yang dapat diindeks karena __call__fungsi Anda pada dasarnya adalah indeks. Juga akan menggunakan daftar alih-alih dict, tapi itu hanya saya.
Chris Lutz
4
@delnan - Hampir semua hal dapat dilakukan dengan beberapa cara berbeda. Mana yang lebih mudah dibaca tergantung pada pembaca.
Chris Lutz
1
@ Chris Lutz: Anda bebas merenungkan perubahan semacam itu. Untuk memoisasi secara umum , kamus berfungsi dengan baik karena Anda tidak dapat menjamin urutan yang mengisi daftar Anda. Dalam hal ini, daftar mungkin berfungsi, tetapi tidak akan menjadi lebih cepat atau lebih sederhana.
S.Lott
8
@delnan: Ini tidak dimaksudkan untuk menjadi yang terpendek. Tidak ada yang menang di kode golf. Ini dimaksudkan untuk ditampilkan __call__, sederhana dan tidak lebih.
S.Lott
3
Tapi itu semacam reruntuhan contoh ketika teknik yang ditunjukkan tidak ideal untuk tugas, bukan? (Dan saya bukan tentang "mari kita simpan baris untuk heck itu" -pendek, saya sedang berbicara tentang "menulisnya dengan cara yang sama jernih ini dan menyimpan beberapa kode boilerplate" -pendek. Yakinlah bahwa saya tidak salah satu dari orang-orang gila yang mencoba menulis kode sesingkat mungkin, saya hanya ingin menghindari kode boilerplate yang tidak menambah apa-apa bagi pembaca.)
40

Saya merasa berguna karena memungkinkan saya untuk membuat API yang mudah digunakan (Anda memiliki beberapa objek yang dapat dipanggil yang memerlukan beberapa argumen tertentu), dan mudah diterapkan karena Anda dapat menggunakan praktik Berorientasi Objek.

Berikut ini adalah kode yang saya tulis kemarin yang membuat versi hashlib.foometode yang hash seluruh file daripada string:

# 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)

Implementasi ini memungkinkan saya untuk menggunakan fungsi dengan cara yang mirip dengan hashlib.foofungsi:

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

Tentu saja saya bisa menerapkannya dengan cara yang berbeda, tetapi dalam kasus ini sepertinya pendekatan yang sederhana.

bradley.ayers
sumber
7
Sekali lagi, penutupan merusak contoh ini. pastebin.com/961vU0ay adalah 80% garis dan sama jelasnya.
8
Saya tidak yakin itu akan selalu jelas bagi seseorang (mis. Mungkin seseorang yang hanya menggunakan Java). Fungsi bersarang dan pencarian variabel / ruang lingkup mungkin membingungkan. Saya kira maksud saya adalah __call__memberi Anda alat yang memungkinkan Anda menggunakan teknik OO untuk memecahkan masalah.
bradley.ayers
4
Saya pikir pertanyaan "mengapa menggunakan X over Y" ketika keduanya menyediakan fungsionalitas yang setara sangat subyektif. Bagi sebagian orang pendekatan OO lebih mudah dipahami, bagi orang lain pendekatan penutupannya. Tidak ada argumen yang meyakinkan untuk menggunakan salah satunya, kecuali Anda memiliki situasi di mana Anda harus menggunakan isinstanceatau sesuatu yang serupa.
bradley.ayers
2
@delnan Contoh penutupan Anda kurang dari baris kode, tetapi yang jelas lebih sulit untuk diperdebatkan.
Dennis
8
Contoh di mana Anda lebih suka menggunakan __call__metode daripada penutupan adalah ketika Anda berurusan dengan modul multiprosesing, yang menggunakan pengawetan untuk meneruskan informasi antar proses. Anda tidak bisa membuat acar penutupan, tetapi Anda bisa acar contoh kelas.
John Peter Thompson Garcés
21

__call__juga digunakan untuk mengimplementasikan kelas dekorator dalam python. Dalam hal ini instance dari kelas dipanggil ketika metode dengan dekorator dipanggil.

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()
Keris
sumber
9

Ya, ketika Anda tahu Anda berurusan dengan objek, sangat mungkin (dan dalam banyak kasus disarankan) untuk menggunakan pemanggilan metode eksplisit. Namun, kadang-kadang Anda berurusan dengan kode yang mengharapkan objek yang dapat dipanggil - biasanya berfungsi, tetapi berkat __call__Anda dapat membangun objek yang lebih kompleks, dengan data contoh dan lebih banyak metode untuk mendelegasikan tugas berulang, dll. Yang masih dapat dipanggil.

Juga, kadang-kadang Anda menggunakan kedua objek untuk tugas-tugas kompleks (di mana masuk akal untuk menulis kelas khusus) dan objek untuk tugas-tugas sederhana (yang sudah ada dalam fungsi, atau lebih mudah ditulis sebagai fungsi). Untuk memiliki antarmuka umum, Anda harus menulis kelas kecil yang membungkus fungsi-fungsi tersebut dengan antarmuka yang diharapkan, atau Anda menjaga fungsi fungsi dan membuat objek yang lebih kompleks dapat dipanggil. Mari kita ambil utas sebagai contoh. The Threadbenda dari modul libary standarthreading ingin callable sebagai targetargumen (yaitu sebagai tindakan yang akan dilakukan di thread baru). Dengan objek yang dapat dipanggil, Anda tidak terbatas pada fungsi, Anda dapat melewati objek lain juga, seperti pekerja yang relatif kompleks yang mendapatkan tugas untuk dilakukan dari utas lain dan menjalankannya secara berurutan:

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)

Ini hanya contoh dari atas kepala saya, tetapi saya pikir itu sudah cukup kompleks untuk menjamin kelas. Melakukan ini hanya dengan fungsi itu sulit, setidaknya memerlukan mengembalikan dua fungsi dan itu perlahan semakin kompleks. Seseorang dapat mengubah nama __call__menjadi sesuatu yang lain dan melewati metode terikat, tetapi itu membuat kode membuat utas sedikit kurang jelas, dan tidak menambah nilai apa pun.

yann.kmm
sumber
3
Mungkin berguna untuk menggunakan frasa "duck typing" ( en.wikipedia.org/wiki/Duck_typing#In_Python ) di sini - Anda dapat meniru fungsi menggunakan objek kelas yang lebih rumit dengan cara ini.
Andrew Jaffe
2
Sebagai contoh terkait, saya telah melihat __call__digunakan untuk menggunakan instance kelas (bukan fungsi) sebagai aplikasi WSGI. Berikut adalah contoh dari "The Definitive Guide to Pylons": Menggunakan Instances of Classes
Josh Rosen
5

Dekorator berbasis kelas digunakan __call__untuk referensi fungsi yang dibungkus. Misalnya:

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

Ada deskripsi yang baik tentang berbagai opsi di sini di Artima.com

rorycl
sumber
Saya jarang melihat dekorator kelas, karena mereka memerlukan beberapa kode boilerplate nonobvious untuk bekerja dengan metode.
4

__call__Metode dan penutupan IMHO memberi kita cara alami untuk membuat pola desain STRATEGI dengan Python. Kami mendefinisikan sekelompok algoritma, merangkum masing-masing, membuatnya dipertukarkan dan pada akhirnya kami dapat menjalankan serangkaian langkah-langkah umum dan, misalnya, menghitung hash untuk file.

ady
sumber
4

Saya hanya menemukan penggunaan __call__()dalam konser __getattr__()yang menurut saya indah. Ini memungkinkan Anda untuk menyembunyikan beberapa level API JSON / HTTP / (yet_serialized) di dalam suatu objek.

Bagian __getattr__()menangani iteratif mengembalikan instance yang dimodifikasi dari kelas yang sama, mengisi satu atribut lagi pada suatu waktu. Kemudian, setelah semua informasi habis, __call__()ambil alih dengan argumen apa pun yang Anda sampaikan.

Menggunakan model ini, misalnya Anda dapat membuat panggilan seperti api.v2.volumes.ssd.update(size=20), yang berakhir dengan permintaan PUT ke https://some.tld/api/v2/volumes/ssd/update.

Kode tertentu adalah driver penyimpanan blok untuk backend volume tertentu di OpenStack, Anda dapat memeriksanya di sini: https://github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc.py

SUNTING: Memperbarui tautan menuju revisi utama.

Peter Slovak
sumber
Itu bagus. Saya pernah menggunakan mekanisme yang sama untuk melintasi pohon XML acak menggunakan akses atribut.
Petri
1

Tentukan __metaclass__dan timpa __call__metode, dan minta metode meta class yang ditentukan __new__mengembalikan instance kelas, biola Anda memiliki "fungsi" dengan metode.

pengguna2772852
sumber
1

Kita dapat menggunakan __call__metode untuk menggunakan metode kelas lain sebagai metode statis.

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

Salah satu contoh umum adalah __call__di functools.partial, di sini adalah versi yang disederhanakan (dengan 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)

Pemakaian:

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

inc = partial(add, y=1)
print(inc(41))  # 42
tak ada habisnya
sumber
0

Operator panggilan fungsi.

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

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

The __call__ metode dapat digunakan untuk didefinisikan ulang / re-menginisialisasi objek yang sama. Ini juga memfasilitasi penggunaan instance / objek kelas sebagai fungsi dengan meneruskan argumen ke objek.


sumber
Kapan itu berguna? Foo (1, 2, 3) tampak lebih jelas.
Yaroslav Nikitenko
0

Aku menemukan tempat yang baik untuk menggunakan benda-benda callable, mereka yang menentukan __call__(), adalah ketika menggunakan kemampuan pemrograman fungsional dengan Python, seperti map(), filter(), reduce().

Waktu terbaik untuk menggunakan objek yang dapat dipanggil pada fungsi polos atau fungsi lambda adalah ketika logikanya kompleks dan perlu mempertahankan beberapa keadaan atau menggunakan info lain yang tidak diteruskan ke __call__()fungsi tersebut.

Berikut adalah beberapa kode yang memfilter nama file berdasarkan ekstensi nama file mereka menggunakan objek dan callable filter().

Callable:

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)

Pemakaian:

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

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

Keluaran:

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

Ini sudah terlambat tapi saya memberi contoh. Bayangkan Anda memiliki Vectorkelas dan Pointkelas. Keduanya mengambil x, yargumen posisi. Bayangkan Anda ingin membuat fungsi yang memindahkan titik untuk diletakkan pada vektor.

4 Solusi

  • put_point_on_vec(point, vec)

  • Jadikan metode sebagai kelas vektor. misalnya my_vec.put_point(point)

  • Jadikan itu metode di Pointkelas.my_point.put_on_vec(vec)
  • Vectormengimplementasikan __call__, Jadi Anda bisa menggunakannya sepertimy_vec_instance(point)

Ini sebenarnya bagian dari beberapa contoh yang sedang saya kerjakan untuk panduan metode dunder yang dijelaskan dengan Matematika yang akan saya rilis cepat atau lambat.

Saya meninggalkan logika memindahkan poin itu sendiri karena ini bukan tentang pertanyaan ini

Ahmed I. Elsayed
sumber