Python __call__ özel yöntem pratik örneği


159

__call__Bir sınıf örneği çağrıldığında bir sınıftaki yöntem tetiklendi biliyorum . Ancak, bu özel yöntemi ne zaman kullanabileceğim hakkında hiçbir fikrim yok, çünkü sadece yeni bir yöntem oluşturabilir ve yöntemde yapılan aynı işlemi gerçekleştirebilir __call__ve örneği çağırmak yerine yöntemi çağırabilirsiniz.

Birisi bana bu özel yöntemin pratik bir kullanımını verirse gerçekten memnun olurum.



8
_call_'ın işlevselliği, C ++ 'daki () aşırı yüklenmiş operatörü gibidir . Sınıf dışında yeni bir yöntem oluşturursanız, sınıftaki dahili verilere erişemeyebilirsiniz.
andy

2
En yaygın kullanımı __call__düz görünümde gizlidir; bir sınıfı böyle başlatırsınız: x = Foo()gerçekten x = type(Foo).__call__(Foo), nerede __call__metasınıfı tarafından tanımlanır Foo.
chepner

Yanıtlar:


88

Django form modülü __call__form doğrulaması için tutarlı bir API uygulamak için yöntemi güzel kullanır . Django'da bir form için kendi doğrulayıcınızı işlev olarak yazabilirsiniz.

def custom_validator(value):
    #your validation logic

Django, genel olarak RegEx doğrulayıcılarının şemsiyesi altında olan e-posta doğrulayıcıları, url doğrulayıcıları vb. Gibi bazı varsayılan yerleşik doğrulayıcılara sahiptir. Bunları temiz bir şekilde uygulamak için Django çağrılabilir sınıflara başvurur (işlevler yerine). RegexValidator'da varsayılan Regex Doğrulama mantığını uygular ve daha sonra bu sınıfları diğer doğrulamalar için genişletir.

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

Artık hem özel işleviniz hem de yerleşik EmailValidator aynı sözdizimi ile çağrılabilir.

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

Gördüğünüz gibi, Django'daki bu uygulama, diğerlerinin aşağıdaki cevaplarında açıkladıklarına benzer. Bu başka bir şekilde uygulanabilir mi? Yapabilirsiniz, ancak IMHO, Django gibi büyük bir çerçeve için okunabilir veya kolayca genişletilemez.


5
Bu yüzden doğru kullanılırsa, kodu daha okunabilir hale getirebilir. Yanlış yerde kullanılırsa, kodu da okunamaz hale getireceğini varsayalım.
mohi666

15
Bu nasıl kullanılabileceğinin bir örneği, ama bence iyi değil. Bu durumda çağrılabilir bir örneğe sahip olmanın hiçbir avantajı yoktur. .Validate () gibi bir yöntemle bir arabirim / soyut sınıfa sahip olmak daha iyi olur; aynı şey sadece daha açık. __Call__'ın gerçek değeri, çağrılabilir bir öğenin beklendiği yerde bir örneği kullanabilmektir. Örneğin dekoratör oluştururken __call__ kullanıyorum.
Daniel

120

Bu örnek, değerleri bir tabloya (bu örnekte sözlük) depolayan notlama kullanır , böylece onları yeniden hesaplamak yerine daha sonra arayabilirsiniz.

Burada , statik bir değişken içeren (Python'da mümkün olmadığı için) faktöriyel fonksiyon yerine __call__faktöriyelleri ( çağrılabilir bir nesne aracılığıyla) hesaplamak için bir yöntemle basit bir sınıf kullanıyoruz .

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

Artık factdiğer tüm fonksiyonlarda olduğu gibi çağrılabilir bir nesneniz var . Örneğin

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

Ve aynı zamanda durum bilgisi de var.


2
İşleviniz aslında bir dizin factolduğundan dizinlenebilir bir nesneye sahip olmayı tercih ederim __call__. Ayrıca bir dikte yerine bir liste kullanırsınız, ama bu sadece benim.
Chris Lutz

4
@delnan - Hemen hemen her şey birkaç farklı yolla yapılabilir. Hangisinin daha okunabilir olduğu okuyucuya bağlıdır.
Chris Lutz

1
@Chris Lutz: Bu tür değişiklikleri düşünmekte özgürsünüz. Memoization için genel olarak , bir sözlük İşlerin listenizi doldurmak sırayı garanti edemez iyi çünkü dışarı çalışır. Bu durumda, bir liste işe yarayabilir, ancak daha hızlı veya daha basit olmayacaktır.
S.Lott

8
@delnan: Bunun en kısa olması amaçlanmamıştır. Kimse kod golf kazanır. Göstermek __call__, basit olmak ve daha fazlası değil.
S.Lott

3
Ancak, gösterilen teknik görevler için ideal olmadığında örneği mahveder, değil mi? (Ve ben "onun heck için satırları kaydedelim" hakkında değildi -kısa, "bu eşit derecede açık bir şekilde yazın ve bazı kaynak kodunu kaydedin" -short hakkında konuşuyordum. Mümkün olan en kısa kodu yazmaya çalışan bu deliler, ben sadece okuyucu için hiçbir şey ekler demirbaş kod kaçınmak istiyorum.)

40

Kullanımı kolay API'ler oluşturmamı sağladığım için (bazı belirli bağımsız değişkenler gerektiren bazı çağrılabilir nesneleriniz var) ve Nesneye Dayalı uygulamaları kullanabileceğiniz için uygulanması kolay olan API'leri oluşturmamı sağlıyor.

Aşağıdaki dün yazdım hashlib.foodizeleri yerine tüm dosyaları hash yöntemlerin bir sürümünü yapar kod :

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

Bu uygulama, işlevleri işlevlere benzer şekilde kullanmama izin veriyor hashlib.foo:

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

Elbette bunu farklı bir şekilde uygulayabilirdim, ancak bu durumda basit bir yaklaşım gibi görünüyordu.


7
Yine, kapaklar bu örneği mahvetmektedir. pastebin.com/961vU0ay satırların% 80'i kadar açık ve net.

8
Ben her zaman biri (örneğin belki sadece Java kullanan biri) için açık olacağını ikna değilim . Yuvalanmış işlevler ve değişken arama / kapsam kafa karıştırıcı olabilir. Demek __call__istediğim, size sorunları çözmek için OO tekniklerini kullanmanıza izin veren bir araç verdi.
bradley.ayers

4
Bence her ikisi de eşdeğer işlevsellik sağlamak "neden Y üzerinde X kullanın" sorusunun son derece öznel olduğunu düşünüyorum. Bazı insanlar için OO yaklaşımını anlamak daha kolaydır, diğerleri için kapanış yaklaşımıdır. Eğer bir durum vardı sürece, birini diğerine kullanmak için hiçbir zorlayıcı argüman var vardı kullanmak isinstanceveya buna benzer.
bradley.ayers

2
@delnan Kapanış örneğiniz daha az kod satırıdır, ancak tartışmasının daha zor olduğu açıktır.
Dennis

8
__call__Kapatma yerine bir yöntemi kullanmayı tercih edeceğiniz bir örnek, işlemler arasında bilgi aktarmak için dekapaj kullanan çok işlemli modülle uğraşmanızdır. Kapanış seçemezsiniz, ancak bir sınıf örneğini seçebilirsiniz.
John Peter Thompson Garcés

21

__call__python'da dekoratör sınıflarını uygulamak için de kullanılır. Bu durumda, dekoratörle yöntem çağrıldığında sınıf örneği çağrılır.

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

Evet, nesnelerle uğraştığınızı bildiğinizde, açık bir yöntem çağrısı kullanmak tamamen mümkündür (ve birçok durumda tavsiye edilir). Bununla birlikte, bazen çağrılabilir nesneler bekleyen kodla ilgilenirsiniz - tipik olarak işlev görür, ancak sayınızla __call__daha karmaşık nesneler, örneğin veriler ve yine de çağrılabilir tekrarlanan görevleri devretmek için daha fazla yöntem oluşturabilirsiniz.

Ayrıca, bazen her iki nesneyi karmaşık görevler için (özel bir sınıf yazmanın mantıklı olduğu yerlerde) ve basit görevler için (zaten işlevlerde var olan veya daha kolay işlev olarak yazılan) nesneler kullanırsınız. Ortak bir arayüze sahip olmak için, ya bu işlevleri beklenen arayüzle saran küçük sınıflar yazmanız ya da işlevlerin işlevlerini tutmanız ve daha karmaşık nesneleri çağrılabilir hale getirmeniz gerekir. Örnek olarak konuları ele alalım. ThreadStandart kitaplığındaki modülünden nesnelerithreading gibi bir çağrılabilir isteyen targetbağımsız değişken (örneğin, aksiyon yeni iplik yapılması gibi). Çağrılabilir bir nesnede, işlevlerle sınırlı değilsiniz, görevleri diğer iş parçacıklarından yapmak ve sıralı olarak yürütmek gibi nispeten karmaşık bir işçi gibi diğer nesneleri de iletebilirsiniz:

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)

Bu sadece kafamın tepesinden bir örnek, ama bence sınıfı garanti edecek kadar karmaşık. Bunu sadece işlevlerle yapmak zordur, en azından iki işlevi geri döndürmeyi gerektirir ve bu yavaş yavaş karmaşıklaşır. Bir olabilir adlandırmak __call__başka bir şey ve bir sınır yöntemi geçmesi, ama bu biraz daha az belirgin zinciri oluşturur kodu yapar ve herhangi bir değer katmaz.


3
Burada muhtemelen "duck typing" ( en.wikipedia.org/wiki/Duck_typing#In_Python ) ifadesini kullanmak yararlı olabilir - bu şekilde daha karmaşık bir sınıf nesnesi kullanarak bir işlevi taklit edebilirsiniz .
Andrew Jaffe

2
İlgili bir örnek olarak, __call__WSGI uygulamaları olarak sınıf örneklerini (işlevler yerine) kullandığımı gördüm . "Dikmesi Kesin Kılavuz" dan bir örnek: Sınıfların Örneklerini Kullanma
Josh Rosen

5

Sınıf tabanlı dekoratörler __call__, sarılmış işleve başvurmak için kullanır . Örneğin:

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'daki çeşitli seçeneklerin iyi bir açıklaması var


Metodlarla çalışmak için bazı belirgin olmayan demirbaş kodu gerektirdiğinden, nadiren sınıf dekoratörlerini görüyorum.

4

IMHO __call__yöntemi ve kapanışları bize Python'da STRATEJİ tasarım deseni oluşturmanın doğal bir yolunu sunuyor. Bir algoritma ailesi tanımlıyoruz, her birini kapsülliyoruz, bunları değiştirilebilir yapıyoruz ve sonunda ortak bir dizi adım yürütebiliriz ve örneğin bir dosya için bir karma hesaplayabiliriz.


4

Sadece güzel olduğunu düşündüğüm bir __call__()konser kullanımı üzerine tökezledim __getattr__(). Bir nesnenin içindeki birden çok JSON / HTTP / (ancak_serialized) API'sını gizlemenizi sağlar.

__getattr__()Bölüm iteratif bir seferde bir daha özelliğinde doldurarak, aynı sınıfın bir modifiye örneğini dönen ilgilenir. Sonra, tüm bilgiler tükendikten sonra, __call__()geçtiğiniz argümanları ele alır.

Bu modeli kullanarak, örneğin api.v2.volumes.ssd.update(size=20)bir PUT isteğiyle sonuçlanan benzeri bir arama yapabilirsiniz https://some.tld/api/v2/volumes/ssd/update.

Belirli kod, OpenStack'ta belirli bir birim arka ucu için bir blok depolama sürücüsüdür, buradan kontrol edebilirsiniz: https://github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc.py

DÜZENLE: Ana revizyona işaret edecek şekilde bağlantı güncellendi.


Bu iyi. Bir keresinde aynı mekanizmayı öznitelik erişimini kullanarak rastgele bir XML ağacından geçmek için kullandım.
Petri

1

Bir yöntem belirtin __metaclass__ve __call__yöntemi geçersiz kılın ve belirtilen meta sınıfların __new__yönteminin sınıfın bir örneğini döndürmesini sağlayın;


1

__call__Statik yöntemler olarak diğer sınıf yöntemlerini kullanmak için yöntemi kullanabiliriz.

    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

Ortak bir örnektir __call__içinde functools.partialburada, basitleştirilmiş bir versiyonudur (ile 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)

Kullanımı:

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

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

0

İşlev çağrısı operatörü.

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

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

__Call__ yöntemi en yeniden / aynı nesneye yeniden başlatmak için kullanılabilir. Ayrıca, nesnelerin argümanlarını ileterek bir sınıfın örneklerinin / nesnelerinin işlev olarak kullanılmasını da kolaylaştırır.


Ne zaman yararlı olur? Foo (1, 2, 3) daha net görünüyor.
Yaroslav Nikitenko

0

Ben çağrılabilir nesneleri kullanmak iyi bir yer bulmak, tanımlamak olanlar __call__()gibi, Python fonksiyonel programlama yeteneklerini kullanırken, olduğu map(), filter(), reduce().

Çağrılabilir bir nesneyi düz bir işlev veya lambda işlevi üzerinde kullanmak için en iyi zaman, mantığın karmaşık olması ve bazı durumları koruması veya __call__()işleve iletilmeyen diğer bilgileri kullanmasıdır .

Dosya adlarını, çağrılabilir bir nesne ve kullanarak dosya adı uzantılarına göre filtreleyen bazı kodlar filter().

çağrılabilir:

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)

Kullanımı:

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

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

Çıktı:

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

0

Bu çok geç ama bir örnek veriyorum. Bir Vectorsınıfınız ve bir Pointsınıfınız olduğunu düşünün . Her ikisi x, yde konumsal argümanlar olarak kabul edilir. Vektör üzerine konacak noktayı hareket ettiren bir işlev oluşturmak istediğinizi düşünelim.

4 Çözümler

  • put_point_on_vec(point, vec)

  • Vektör sınıfında bir yöntem yapın. Örneğin my_vec.put_point(point)

  • PointSınıfta bir yöntem olun .my_point.put_on_vec(vec)
  • Vectoruygular __call__, böylece kullanabilirsinizmy_vec_instance(point)

Bu aslında, er ya da geç serbest bırakacağım Maths ile açıklanan dunder yöntemleri için bir kılavuz için çalıştığım bazı örneklerin parçası.

Noktanın kendisini taşıma mantığını bıraktım çünkü bu sorunun konusu bu değil

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.