Celery signatures .s(): early or late binding?

94 Views Asked by At

This blog post of Adam Johnson perfectly illustrates the difference between early binding and late binding closures in Python. This is also explained here.

I've got a function my_func which corresponds to a Celery task (it's decorated with @shared_task). It expects some arguments. I run it with the following piece of code, using Celery signatures as described in the documentation (see also this StackOverflow answer):

from functools import partial
from django.db import transaction
from example.tasks import my_func

# next line can be found at the end of some other functions which involve a transaction
transaction.on_commit(my_func.s(param1, param2).delay))

Are Celery signatures early binding or late binding? And how to demonstrate it in an elegant way?

If they are early binding, the following line of code should be equivalent:

transaction.on_commit(partial(my_func.delay, param1, param2))

If they are late binding, I think I could be facing a pesky bug with my current code, in some edge cases...

1

There are 1 best solutions below

0
juanpa.arrivillaga On BEST ANSWER

So, according to your link, in this section:

partial() is early binding, whilst all the prior techniques are late binding.

Early binding here means that function arguments are resolved when calling partial()

At the risk of being pedantic, I would say that this is mixing terminology. Late/early binding applies to how free variables are evaluated when a function that creates a closures is called.

When you are talking about how arguments are evaluated during a function call, that's different. For all function calls in python the arguments are evaluated fully when the function is called. That is a consequence of Python's evaluation strategy, which is strict (also called greedy) as opposed to non-strict (also called lazy).

The strict/greed evaluation of function arguments is analogous to early-binding in closures. So using this way of phrasing it, yes, .s is "early binding".

That is, because my_func.s(param1, param2) is just a function call, then the arguments are eagerly evaluated.


Note, one example of a non-strict evaluation strategy is call-by-name. Languages like scala for an example) support call by name.

There is also Haskell, which has call-by-need, which is like a cached version fo call-by-name. Because Haskell is purely functional, and doesn't allow variables to be re-assigned, you don't see bugs like the ones you would be worried about though.