19

In many cases, there are two implementation choices: a closure and a callable class. For example,

class F:
  def __init__(self, op):
    self.op = op
  def __call__(self, arg1, arg2):
    if (self.op == 'mult'):
      return arg1 * arg2
    if (self.op == 'add'):
      return arg1 + arg2
    raise InvalidOp(op)

f = F('add')

or

def F(op):
  if op == 'or':
    def f_(arg1, arg2):
      return arg1 | arg2
    return f_
  if op == 'and':
    def g_(arg1, arg2):
      return arg1 & arg2
    return g_
  raise InvalidOp(op)

f = F('add')

What factors should one consider in making the choice, in either direction?

I can think of two:

  • It seems a closure would always have better performance (can't think of a counterexample).

  • I think there are cases when a closure cannot do the job (e.g., if its state changes over time).

Am I correct in these? What else could be added?

max
  • 49,282
  • 56
  • 208
  • 355
  • 1
    "What is better to use"? Please define the criteria you are trying to optimize. Better at what? Smaller? Faster? More use of Oracle licensed products? What do you mean by "better"? – S.Lott Jan 23 '12 at 04:04
  • 1
    @max, actually closure can have state (they can catch anything around them, including local variables). – ony Jan 23 '12 at 05:04
  • 1
    @S.Lott, as I understand question is something like: "Why would you prefer callable classes over closures?" – ony Jan 23 '12 at 05:05
  • See also: http://stackoverflow.com/questions/2497801/closures-are-poor-mans-objects-and-vice-versa-what-does-this-mean – Ian Clelland Jan 23 '12 at 05:35
  • @S.Lott: I am actually trying to find out what I should think of in the future, whenever this question comes up. So I'm not trying to limit the discussion to one specific situation. I'd guess the aspects I'd want to consider is performance, clarity, flexibility, and reliability - at the very least. – max Jan 23 '12 at 06:36
  • @max: Please **update** the question to remove the vague, useless, undefined "best" and replace it with your **actual** considerations. – S.Lott Jan 23 '12 at 10:43
  • Well, in this case the best is "neither". Instead define f1 and f2 and use the appropriate one in each place. The second example where you re-create the same functions for every call of F() is completely pointless. – Lennart Regebro Jan 24 '12 at 06:58
  • @RaymondHettinger: I will, I always do. I just want to think more about it, and read more answers. – max Jan 29 '12 at 02:20
  • @LennartRegebro: my example is bad; I just wanted to show the techniques I'm asking about. I guess my question is too general, and if I explain the exact situation I have, it will become way too specific to be of interest. – max Jan 29 '12 at 03:33
  • @max: Probably not. The exact situation is generally more interesting that theory. You can learn more. – Lennart Regebro Jan 29 '12 at 03:50

6 Answers6

16

Closures are faster. Classes are more flexible (i.e. more methods available than just __call__).

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • 3
    I'd not agree with that. You could add anything you want to the object that express closure in Python (by some unknown for me reason). Since object is the main value of Python language - there is no big difference between how values obtained (either by bringing code snippet as an object or by constructing object of some other class) - they all objects. Since Python is dynamic language almost any object can be modified (Actually I did thought that closure isn't one of them). – ony Jan 23 '12 at 04:59
  • 6
    Sorry ony, your comment doesn't reflect a sound understanding of Python. For example, you can add a __len__ method to a class or you can attach it as a function attribute to a closure, but ``len(obj)`` will only work with the former. – Raymond Hettinger Jan 23 '12 at 07:11
  • `x = lambda a, b: a * b; x.f = lambda x: x*41; x.f(3)` produces `123`. Yes actual [user-defined function object](http://docs.python.org/py3k/reference/datamodel.html#the-standard-type-hierarchy) stores everything in `func_dict` by providing `__getattribute__` and `__setattr__` (I've expected that function object wouldn't provide possibility to change anything, but). So you say that I'm wrong when say [code is represented by object](http://docs.python.org/py3k/reference/datamodel.html#objects-values-and-types)? Do I miss something? I though I understand some basic ideas behind this language. – ony Jan 23 '12 at 16:34
  • The x.f assignment attaches a function, but you don't get method behavior (dotted lookup, automatic prepending of self, etc). Most of the capabilities of classes have been lost. The function attributes are a dictionary but they don't have class logic. – Raymond Hettinger Jan 23 '12 at 20:31
  • In this closure he redefines f_ and g_ every call, that can't be "faster". ;-) – Lennart Regebro Jan 24 '12 at 07:01
  • 6
    What a person does in their closure is the own business :-) – Raymond Hettinger Jan 24 '12 at 07:33
  • Does this claim have any backing? By 'faster', do you mean they actually run faster or only that they're faster to write? What about memory consumption? – Yuval Jan 14 '17 at 08:56
  • 2
    @Yuval. Faster means that it runs faster. The reason is lookups for closure variables is faster than for instance variables (See https://code.activestate.com/recipes/577834 ). Also, calling methods entails creating bound methods, but that isn't necessary for closures. You can run timeit.py to convince yourself that this is true :-) – Raymond Hettinger Jan 15 '17 at 03:00
  • @RaymondHettinger I did that exactly (see answer below) and it seems to be the opposite of what you state. It turns out calling callable classes is faster (but slower to initially construct). Re the link you provided, there is no reference to a closure variable, which have [their own implementation](http://stackoverflow.com/questions/3145893/how-are-closures-implemented). The major overhead won't be the plain variable access, but performing the actual call. Calls to either closures or callable classes must be bound - here to a set of locals, and there to a class. – Yuval Jan 21 '17 at 07:32
  • 1
    @RaymondHettinger I apologize - but there was an error in my code where I mixed up the two results. It turns out you are completely right. I fixed my answer below and I'm sorry for the confusion. – Yuval Feb 07 '17 at 21:18
  • 1
    @RaymondHettinger: Isn't it just a nested function instead of closure ? I was reading this answer [here](https://stackoverflow.com/questions/4020419/why-arent-python-nested-functions-called-closures) which says "_a closure occurs when a function has access to a local variable from an enclosing scope that has finished its execution_ " – Rahul Verma May 23 '18 at 17:54
  • @batMan Nested functions make closures. So the two topics are tightly intertwined. – Raymond Hettinger May 23 '18 at 18:36
  • 1
    @RaymondHettinger: Yes, I agree and I understand that _All closures are nested functions but all nested functions are not closures_. But here the local variable in the enclosing function scope is `op` which is not getting referenced in the enclosed function. so how are we referring it a closure here ? – Rahul Verma May 23 '18 at 18:52
  • 2
    @batMan You're right, the OPs example doesn't make a closure; however, most of the reasoning about performance still holds. The class style of access incurs attribute lookup overhead while the nested function style uses local variables and/or closure cell variables, both of which are faster than attribute access. – Raymond Hettinger May 23 '18 at 22:18
4

I realize this is an older posting, but one factor I didn't see listed is that in Python (pre-nonlocal) you cannot modify a local variable contained in the referencing environment. (In your example such modification is not important, but technically speaking the lack of being able to modify such a variable means it's not a true closure.)

For example, the following code doesn't work:

def counter():
    i = 0
    def f():
        i += 1
        return i
    return f

c = counter()
c()

The call to c above will raise a UnboundLocalError exception.

This is easy to get around by using a mutable, such as a dictionary:

def counter():
    d = {'i': 0}
    def f():
        d['i'] += 1
        return d['i']
    return f

c = counter()
c()     # 1
c()     # 2

but of course that's just a workaround.

Adam Donahue
  • 1,618
  • 15
  • 18
  • Not a very intuitive solution but you can always use `f.i = 0`, `f.i += 1` since functions can store their own attrs. (`func_dict`, anyone?). However this makes the closure's variable "transparent" and, by definition, not a closure any longer. – knight Aug 29 '15 at 02:51
  • 1
    Add the line "nonlocal i" before the line "i += 1" – chairam Jul 30 '21 at 10:51
4

Please note that because of an error previously found in my testing code, my original answer was incorrect. The revised version follows.

I made a small program to measure running time and memory consumption. I created the following callable class and a closure:

class CallMe:
    def __init__(self, context):
        self.context = context

    def __call__(self, *args, **kwargs):
        return self.context(*args, **kwargs)

def call_me(func):
    return lambda *args, **kwargs: func(*args, **kwargs)

I timed calls to simple functions accepting different number of arguments (math.sqrt() with 1 argument, math.pow() with 2 and max() with 12).

I used CPython 2.7.10 and 3.4.3+ on Linux x64. I was only able to do memory profiling on Python 2. The source code I used is available here.

My conclusions are:

  • Closures run faster than equivalent callable classes: about 3 times faster on Python 2, but only 1.5 times faster on Python 3. The narrowing is both because closure became slower and callable classes slower.
  • Closures take less memory than equivalent callable classes: roughly 2/3 of the memory (only tested on Python 2).
  • While not part of the original question, it's interesting to note that the run time overhead for calls made via a closure is roughly the same as a call to math.pow(), while via a callable class it is roughly double that.

These are very rough estimates, and they may vary with hardware, operating system and the function you're comparing it too. However, it gives you an idea about the impact of using each kind of callable.

Therefore, this supports (conversely to what I've written before), that the accepted answer given by @RaymondHettinger is correct, and closures should be preferred for indirect calls, at least as long as it doesn't impede on readability. Also, thanks to @AXO for pointing out the mistake in my original code.

Yuval
  • 3,207
  • 32
  • 45
  • 2
    In your code you are mistakenly using the class version to measure closure time and vice versa. (You've `a = CallMe(pow)` and `b = call_me(pow)`, then you have used `a` to measure closure timing and `b` for class timing). – AXO Feb 03 '17 at 00:55
  • 1
    @AXO thank you for the comment. I've revised the code an my answer accordingly. – Yuval Feb 07 '17 at 21:17
4

I consider the class approach to be easier to understand at one glance, and therefore, more maintainable. As this is one of the premises of good Python code, I think that all things being equal, one is better off using a class rather than a nested function. This is one of the cases where the flexible nature of Python makes the language violate the "there should be one, and preferably only one, obvious way of doing something" predicate for coding in Python.

The performance difference for either side should be negligible - and if you have code where performance matters at this level, you certainly should profile it and optimize the relevant parts, possibly rewriting some of your code as native code.

But yes, if there was a tight loop using the state variables, assessing the closure variables should be slight faster than assessing the class attributes. Of course, this would be overcome by simply inserting a line like op = self.op inside the class method, before entering the loop, making the variable access inside the loop to be made to a local variable - this would avoid an attribute look-up and fetching for each access. Again, performance differences should be negligible, and you have a more serious problem if you need this little much extra performance and are coding in Python.

Tristan
  • 1,730
  • 3
  • 20
  • 25
jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • I used to thing that optimization of smaller bits of code might be boosted by times they are used. But in most cases high-level algorithms have more potential for squizing out more perfomance. – ony Jan 23 '12 at 04:36
  • These functions do show up in the profiler at the top by the use of cpu time because they are repeated many times. I was thinking since the closure doesn't need to look up and check `op`, it might be noticeably faster. But I do agree, given that I write in Python, I probably shouldn't even attempt this sort of optimization... – max Jan 23 '12 at 06:38
  • I has just sepnt some time on this question: http://stackoverflow.com/questions/8968407/where-is-nonlocals - and found out further inconvenience of using scoped functions for larger, more complex code. For small functions that will just calculate some expressions like in your example, I think I might go with functions all the way, though – jsbueno Jan 23 '12 at 11:52
  • Thank you, even though the concept of nested class was present at least 10 years ago in many languages including java and python, I see very few developers actually write them in production for this exact reasons: they are hard to maintain. – Harvey Lin Apr 21 '20 at 03:26
1

Mr. Hettinger's answer still is true ten years later in Python3.10. For anyone wondering:

from timeit import timeit
class A: # Naive class
    def __init__(self, op):
        if op == "mut":
            self.exc = lambda x, y: x * y
        elif op == "add":
            self.exc = lambda x, y: x + y
    def __call__(self, x, y):
        return self.exc(x,y)

class B: # More optimized class
    __slots__ = ('__call__')
    def __init__(self, op):
        if op == "mut":
            self.__call__ = lambda x, y: x * y
        elif op == "add":
            self.__call__ = lambda x, y: x + y

def C(op): # Closure
    if op == "mut":
        def _f(x,y):
            return x * y
    elif op == "add":
        def _f(x,t):
            return x + y
    return _f

a = A("mut")
b = B("mut")
c = C("mut")
print(timeit("[a(x,y) for x in range(100) for y in range(100)]", globals=globals(), number=10000)) 
# 26.47s naive class
print(timeit("[b(x,y) for x in range(100) for y in range(100)]", globals=globals(), number=10000)) 
# 18.00s optimized class
print(timeit("[c(x,y) for x in range(100) for y in range(100)]", globals=globals(), number=10000)) 
# 12.12s closure

Using closure seems to offer significant speed gains in cases where the call number is high. However, classes have extensive customization and are superior choice at times.

vahvero
  • 525
  • 11
  • 24
-1

I'd re-write class example with something like:

class F(object):
    __slots__ = ('__call__')
    def __init__(self, op):
        if op == 'mult':
            self.__call__ = lambda a, b: a * b
        elif op == 'add':
            self.__call__ = lambda a, b: a + b
        else:
            raise InvalidOp(op)

That gives 0.40 usec/pass (function 0.31, so it 29% slower) at my machine with Python 3.2.2. Without using object as a base class it gives 0.65 usec/pass (i.e. 55% slower than object based). And by some reason code with checking op in __call__ gives almost the same results as if it was done in __init__. With object as a base and check inside __call__ gives 0.61 usec/pass.

The reason why would you use classes might be polymorphism.

class UserFunctions(object):
    __slots__ = ('__call__')
    def __init__(self, name):
        f = getattr(self, '_func_' + name, None)
        if f is None: raise InvalidOp(name)
        else: self.__call__ = f

class MyOps(UserFunctions):
    @classmethod
    def _func_mult(cls, a, b): return a * b
    @classmethod
    def _func_add(cls, a, b): return a + b
ony
  • 12,457
  • 1
  • 33
  • 41
  • I thought derivation from `class object` was only needed before Python 3, and you're using Python 3.2? I'm not sure I understand the impact of specifying `object` as a base class... – max Jan 23 '12 at 06:45
  • As I remember `object` as a base class was somehow related with using `__slots__`. I usually use them when I want to get more slim object (less dynamic). – ony Jan 23 '12 at 10:39
  • In Python 3 everything is derived from `object` -- there is no need to specify it. It's only needed in Python 2.x to get new-style class behavior. – Ethan Furman Jan 24 '12 at 20:04