Python __call__ special method practical example

后端 未结 14 1511
感动是毒
感动是毒 2020-11-28 00:28

I know that __call__ method in a class is triggered when the instance of a class is called. However, I have no idea when I can use this special method, because

相关标签:
14条回答
  • 2020-11-28 01:12

    The function call operator.

    class Foo:
        def __call__(self, a, b, c):
            # do something
    
    x = Foo()
    x(1, 2, 3)
    

    The __call__ method can be used to redefined/re-initialize the same object. It also facilitates the use of instances/objects of a class as functions by passing arguments to the objects.

    0 讨论(0)
  • 2020-11-28 01:13

    One common example is the __call__ in functools.partial, here is a simplified version (with 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)
    

    Usage:

    def add(x, y):
        return x + y
    
    inc = partial(add, y=1)
    print(inc(41))  # 42
    
    0 讨论(0)
  • 2020-11-28 01:18

    IMHO __call__ method and closures give us a natural way to create STRATEGY design pattern in Python. We define a family of algorithms, encapsulate each one, make them interchangeable and in the end we can execute a common set of steps and, for example, calculate a hash for a file.

    0 讨论(0)
  • 2020-11-28 01:18

    I just stumbled upon a usage of __call__() in concert with __getattr__() which I think is beautiful. It allows you to hide multiple levels of a JSON/HTTP/(however_serialized) API inside an object.

    The __getattr__() part takes care of iteratively returning a modified instance of the same class, filling in one more attribute at a time. Then, after all information has been exhausted, __call__() takes over with whatever arguments you passed in.

    Using this model, you can for example make a call like api.v2.volumes.ssd.update(size=20), which ends up in a PUT request to https://some.tld/api/v2/volumes/ssd/update.

    The particular code is a block storage driver for a certain volume backend in OpenStack, you can check it out here: https://github.com/openstack/cinder/blob/master/cinder/volume/drivers/nexenta/jsonrpc.py

    EDIT: Updated the link to point to master revision.

    0 讨论(0)
  • 2020-11-28 01:19

    Yes, when you know you're dealing with objects, it's perfectly possible (and in many cases advisable) to use an explicit method call. However, sometimes you deal with code that expects callable objects - typically functions, but thanks to __call__ you can build more complex objects, with instance data and more methods to delegate repetitive tasks, etc. that are still callable.

    Also, sometimes you're using both objects for complex tasks (where it makes sense to write a dedicated class) and objects for simple tasks (that already exist in functions, or are more easily written as functions). To have a common interface, you either have to write tiny classes wrapping those functions with the expected interface, or you keep the functions functions and make the more complex objects callable. Let's take threads as example. The Thread objects from the standard libary module threading want a callable as target argument (i.e. as action to be done in the new thread). With a callable object, you are not restricted to functions, you can pass other objects as well, such as a relatively complex worker that gets tasks to do from other threads and executes them sequentially:

    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)
    

    This is just an example off the top of my head, but I think it is already complex enough to warrant the class. Doing this only with functions is hard, at least it requires returning two functions and that's slowly getting complex. One could rename __call__ to something else and pass a bound method, but that makes the code creating the thread slightly less obvious, and doesn't add any value.

    0 讨论(0)
  • 2020-11-28 01:20

    Django forms module uses __call__ method nicely to implement a consistent API for form validation. You can write your own validator for a form in Django as a function.

    def custom_validator(value):
        #your validation logic
    

    Django has some default built-in validators such as email validators, url validators etc., which broadly fall under the umbrella of RegEx validators. To implement these cleanly, Django resorts to callable classes (instead of functions). It implements default Regex Validation logic in a RegexValidator and then extends these classes for other validations.

    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
    

    Now both your custom function and built-in EmailValidator can be called with the same syntax.

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

    As you can see, this implementation in Django is similar to what others have explained in their answers below. Can this be implemented in any other way? You could, but IMHO it will not be as readable or as easily extensible for a big framework like Django.

    0 讨论(0)
提交回复
热议问题