Python -- Only pass arguments if the variable exists
Asked Answered
D

3

28

I have the following variables that a user can optionally submit through a form (they are not required, but may do this to filter down a search).

color = request.GET.get ('color')
size = request.GET.get ('size')

Now I want to pass these variables to a function, but only if they exist. If they do not exist I want to just run the function without arguments.

the function without arguments is:

apicall = search ()

with color only it's

apicall = search (color)

and with color and size it's

apicall = search (color, size)

If the argument is defined I want to pass it to the function, but if it's not I do not want to pass it.

What is the most efficient way to do that? Does python have built-in methods for this?

Demagogic answered 15/5, 2013 at 23:21 Comment(0)
G
16

Assuming that's a standard get call (like on a dictionary), this ought to be easy. Define your function with None for the defaults for your parameters, and then pass color and size without bothering to check them!

def apicall(color=None, size=None):
    pass # Do stuff

color = request.GET.get('color')
size = request.GET.get('size')
apicall(color, size)

This way, you only check for None arguments in one place (inside the function call, where you have to check anyway if the function can be called multiple ways). Everything stays nice and clean. Of course this assumes (like I said at the top) that your get call is like a normal Python dictionary's get method, which returns None if the value isn't found.

Finally, I notice that your function name is apicall: there's a chance you don't actually have access to the function code itself. In this case, since you may not know anything about the default values of the function signature and None might be wrong, I would probably just write a simple wrapper to do the argument-checking for you. Then you can call the wrapper as above!

def wrapped_apicall(color=None, size=None):
    if color is None and size is None:
        return apicall()
    # At least one argument is not None, so...
    if size is None:
        # color is not None
        return apicall(color)
    if color is None: 
        # size is not None
        return apicall(size)
    # Neither argument is None
    return apicall(color, size)

NOTE: This second version shouldn't be necessary unless you can't see the code that you're calling and don't have any documentation on it! Using None as a default argument is very common, so chances are that you can just use the first way. I would only use the wrapper method if you can't modify the function you're calling and you don't know what its default arguments are (or its default arguments are module constants or something, but that's pretty rare).

Gelb answered 15/5, 2013 at 23:24 Comment(5)
Yes, but if i one of the variables is not defined then the apicall (color, size) will throw an error, won't it?Demagogic
Ok thanks... what you wrote is how I'd assume it to be done, but I was thinking there must be a more efficient way...Demagogic
@Demagogic Assuming both of these lines execute: color = request.GET.get('color') and size = request.GET.get('size'), then both color and size are guaranteed to be defined, though they may be None. What do you think is inefficient about this?Gelb
I'm guessing this solution can be generalized to any number of variables using a decorator and a dictionary of arguments, though yet not precisely sure how. This is useful to prevent overwriting function parameters from partial assignment (specified with functools.partial)Alexandrite
The if color is None block in the second code snippet should probably be left out unless you insist on taking the OP literally. It would be awful design for an API to accept both colors and sizes for its only argument. From the examples given, it looks like search(size) should not be an option.Seaquake
W
11

In python 3 you could pack them up in a list, filter it, and use the *-operator to unpack the list as arguments to search:

color = request.GET.get ('color')
size = request.GET.get ('size')
args = [ arg for arg in [color, size] if arg ]
search(*args)

Note, however, if color is falsy and size is truthy, you would be calling search with 1 argument being the value of size, which would probably be wrong, but the original question doesn't mention desired behaviour in that case.

(necromancing since I was looking for a better solution than mine, but found this question)

Whippoorwill answered 6/6, 2019 at 14:43 Comment(0)
H
3

As much as I like @HenryKeiter's solution, Python provides a MUCH easier way to check the parameters. In fact, there are a couple different solutions.

  1. For example, if search() is a standalone function and you want to view the args, you can use the inspect module as seen in this solution.

Example Code 1

>>> import inspect
>>> print(inspect.getfullargspec(search))

ArgSpec(args=['size', 'color'], varargs=None, keywords=None, defaults=(None, None))
  1. However, if search() is a method of a class (we'll call it Search), then you can simply do use the built-in vars function to see all of the class methods and their parameters:

Example Code 2

>>> import Search
>>> print(vars(Search))

mappingproxy({'__init__': <function Search.__init__(self, size, color)>,
                  'search': <function Search.search(self, size, color)})

The only caveat with the 2nd method is that it's more useful as a visual inspection tool, rather than a programmatic one, although you could technically say if 'size' in vars(Search)['search']: do something. It just wouldn't be very robust. Easier and more durable to say if 'size' in inspect.getfullargspec(Search.search).args: do something or for arg in inspect.getfullargsspec(Search.search).args: do something

Heterotopia answered 25/1, 2019 at 21:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.