Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to partially set arguments to a function without running it?

I'm trying to make life as easy as possible for my users while providing them with complete flexibility. I need to write functions for them to use, but the trick is that a user needs to pick the function before running it. Here's what I would like to do:

def standardGenerator(obj,param=8.0):
   # algorithm which generates stuff based on obj and param
   return stuff

opts.usersChoice = standardGenerator

in this example, opts is an option container which is provided to the user to set whatever options they want. When they're done setting all their options, they pass opts back to me.

now the issue is that obj will not be known until after opts is in my hands, but maybe a user wants params=4.0 instead of my default. My question is what is the easiest way to allow a user to set param?

here are the ideas I thought of:

def standardGenerator4(obj):
   return standardGenerator(obj,param=4.0)
opts.usersChoice = standardGenerator4

opts.usersChoice = lambda obj: standardGenerator(obj,4.0)

probably lambda is the best idea, but i'm wondering if there's some other way to do this without requiring users to know what a lambda function is...

like image 943
amos Avatar asked Dec 27 '22 06:12

amos


2 Answers

Well, another way to do this is to use functools.partial.

like image 184
senderle Avatar answered Dec 31 '22 13:12

senderle


Why not make it a class with __call__?

class standardGenerator(object):
    def __init__(self, param=8.0):
        self.param = param
    def __call__(self, obj):
        # do stuff with obj and self.param

The users code does this:

opts.usersChoice = standardGenerator() # for default, or...
opts.usersChoice = standardGenerator(4.0)

Then opts.usersChoice is callable just like before, with only one argument for obj.

EDIT: If you have several functions and don't want to convert them all to classes, you could make a wrapper class:

class wrapper(object):
    def __init__(self, func, **args):
        self.func, self.args = func, args
    def __call__(self, *args):
        return self.func(*args, **self.kwargs)

Then your old function could be used "raw" the same as before, or like this for a user-defined argument:

opts.usersChoice = wrapper(standardGenerator, param=4.0)

I think the first solution is cleaner, and the second solution is probably starting to duplicate functools.partial, but you get the idea.

EDIT: Another thing you could do, which is arguably evil, is use operator overloading:

class _standardGenerator(object):
    def __init__(self, param = 8.0):
        self.param = param
    # overload << to make it look most evil
    def __lshift__(self, other):
        return _standardGenerator(other)
    def __call__(self, obj):
        # do stuff with self.param and obj

standardGenerator = _standardGenerator()

# user code for default
opts.usersChoice = standardGenerator
# user code for nondefault
opts.usersChoice = standardGenerator << 4.0

Of course, I think it's infinitely cleaner to use a regular class and have the user instantiate it him/herself, but if you really don't like those parentheses on the end of standardGenerator something like this would do the trick.

like image 42
Chris Lutz Avatar answered Dec 31 '22 12:12

Chris Lutz