Darshan Chaudhary
Darshan Chaudhary

Reputation: 2233

python - pass a argument expecting function object

I am using exoredis, a key-value store that works great as a cache. It has a method get_or_set that sets the value of the key if it doesn't exist already.

exoredis.get_or_set('my_new_key', 'my new value')

Here, my new value can also be a callable that returns a string like:

exoredis.get_or_set('some-timestamp-key', datetime.datetime.now)

In my case, my callable needs to accept one argument "pk":

def get_gender(pk):
    return Users.objects.filter(pk=pk).gender

How do I pass this function as a callable? This gives me a TypeError saying my function expects one argument. (In the event that it is executed)

pk = request.user.id
print exoredis.get_or_set("gender:%s"%pk, get_gender)

TypeError: get_gender() takes exactly 1 argument (0 given)


This executes the function each time:

pk = request.user.id
print exoredis.get_or_set("gender:%s"%pk, get_gender(pk))

Upvotes: 0

Views: 334

Answers (2)

PM 2Ring
PM 2Ring

Reputation: 55499

The way to do this is to wrap the one-argument function in a zero-argument function, and then pass that zero-arg function to the function that wants a zero-arg function.

As I mentioned in the comments there are two options here:
1) When the wrapped function is eventually called it's called with the current value of its parameter.
2) When the wrapped function is eventually called it's called with the old value that its parameter had when it got wrapped.

The code below illustrates both options.

class Test(object):
    def __init__(self, f):
        self.f = f

def myfunc(s):
    return 'myfunc ' + s

s = 'hello'
a = Test(lambda: myfunc(s))
s = 'bye'
print(a.f())

s = 'hello'
a = Test(lambda s=s: myfunc(s))
s = 'bye'
print(a.f())    

output

myfunc bye
myfunc hello

The "trick" used in the second option is to set the parameter as a default argument, since default arguments are evaluated when the function definition is executed, that is, when the function is created.

Technically, lambda s=s: myfunc(s) is really a one-arg function, but we can call it with zero args and it will use the supplied default value for its argument.

Upvotes: 2

John Kugelman
John Kugelman

Reputation: 362047

pk = request.user.id
print exoredis.get_or_set("gender:%s"%pk, lambda: get_gender(pk))

Pass in a lambda function that takes no arguments. This will ensure get_gender is called later rather than immediately.

Upvotes: 1

Related Questions