13

I have a function with one optional argument, like this:

def funA(x, a, b=1):
   return a+b*x

I want to write a new function that calls funA and also has an optional argument, but if no argument is passed, I want to keep the default in funA.

I was thinking something like this:

def funB(x, a, b=None):
   if b:
     return funA(x, a, b)
   else:
     return funA(x, a)

Is there a more pythonic way of doing this?

SuperBiasedMan
  • 9,814
  • 10
  • 45
  • 73
itzy
  • 11,275
  • 15
  • 63
  • 96

6 Answers6

16

I would replace if b with if b is not None, so that if you pass b=0 (or any other "falsy" value) as argument to funB it will be passed to funA.

Apart from that it seems pretty pythonic to me: clear and explicit. (albeit maybe a bit useless, depending on what you're trying to do!)

A little more cryptic way that relies on calling funB with the correct keyword arguments (e.g. funB(3, 2, b=4):

def funB(x, a, **kwargs):
    return funA(x, a, **kwargs)
LeartS
  • 2,866
  • 2
  • 25
  • 45
  • 5
    The cryptic way is a step backwards. Harder to read with no gain in functionality. – chepner Dec 10 '15 at 15:43
  • 3
    Yes, you are right ("cryptic" rarely indicates something positive) and it also isn't really the same thing functionally, but still something that may be useful and avoid a lot of code in some situations, probably not this one. But it starts to be convenient if, instead of 1, we have 2,3,4... optional default arguments – LeartS Dec 10 '15 at 15:46
  • 1
    @falsetru that's why I specified "that relies on calling `funB` with the correct keyword arguments" – LeartS Dec 10 '15 at 15:53
  • The "cryptic" way is better if you ever use multiple inheritance since you may not know all of the arguments. – Neil G Dec 23 '15 at 10:39
7
def funA(x, a, b=1):
    return a+b*x

def funB(x, a, b=1):     
   return funA(x, a, b)

Make the default value of b=1 in funB() and then pass it always to funA()

k4ppa
  • 4,122
  • 8
  • 26
  • 36
  • 1
    I can't change `funA`. Take it as given. – itzy Dec 10 '15 at 15:37
  • No need to change `funA()` if you don't want to. – k4ppa Dec 10 '15 at 15:39
  • @itzy if the default of funB is the same as the default of funA this is a good solution – gkusner Dec 10 '15 at 15:39
  • 2
    Ah, sorry, I wasn't clear on this in the question: I don't know what the default argument to `funA` is. (It's code maintained somewhere else that may change.) – itzy Dec 10 '15 at 15:41
  • 2
    I would make `funB` independent of the actual default value used by `A`. Ideally, you don't want to change `funB` if `funA` chooses a different default. – chepner Dec 10 '15 at 15:41
3

The way you did it is fine. Another way is for funB to have the same defaults as funA, so you can pass the same parameters right through. E.g., if you do def funB(x, a, b=1), then you can always call return funA(x, a, b) just like that.

For simple cases, the above will work fine. For more complex cases, you may want to use *args and **kwargs (explained here and here). Specifically, you can pass in all your keyword arguments as a dictionary (conventionally called kwargs). In this case, each function would set its own independent defaults, and you would just pass the whole dictionary through:

def funA(x, a, **kwargs):
   b = kwargs.get("b", 1)
   return a+b*x

def funB(x, a, **kwargs):
   return funA(x, a, **kwargs)

If kwargs is empty when passed to funB (b is not specified), it will be set to the default in funA by the statement b = kwargs.get("b", 1). If b is specified, it will be passed through as-is. Note that in funB, you can access b with its own, independent default value and still get the behavior you are looking for.

While this may seem like overkill for your example, extracting a couple of arguments at the beginning of a function is not a big deal if the function is complex enough. It also gives you a lot more flexibility (such as avoiding many of the common gotchas).

Community
  • 1
  • 1
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
2

Using inspect.getargspec, you can get the default values (fourth item of the returned tuple = defaults):

import inspect

def funA(x, a, b=1):
   return a + b * x

# inspect.getargspec(funA) =>
#     ArgSpec(args=['x', 'a', 'b'], varargs=None, keywords=None, defaults=(1,))
def funcB(x, a, b=inspect.getargspec(funA)[3][0]):
    return funA(x, a, b)

OR (in Python 2.7+)

def funcB(x, a, b=inspect.getargspec(funA).defaults[0]):
    return funA(x, a, b)

In Python 3.5+, it's recommend to use inspect.signature instead:

def funcB(x, a, b=inspect.signature(funA).parameters['b'].default):
    return funA(x, a, b)
falsetru
  • 357,413
  • 63
  • 732
  • 636
2

Using FunctionType from types, you can just take a function and create a new one specifying the defaults at runtime. You can put all this in a decorator so that at the point of where you write your code it will keep things tidy, whilst still giving the reader a clue about what you are trying to accomplish. It also allows the exact same call signature for funB as funA -- all arguments can be positional, or all arguments can be keywords, or any valid mix thereof, and any arguments with default values are optional. Should play nice with positional arguments (*args) and keyword arguments (**kwargs) too.

import inspect
from types import FunctionType

def copy_defaults(source_function):
    def decorator(destination_function):
        """Creates a wrapper for the destination function with the exact same 
        signature as source_function (including defaults)."""

        # check signature matches
        src_sig = inspect.signature(source_function)
        dst_sig = inspect.signature(destination_function)
        if list(src_sig.parameters) != list(dst_sig.parameters):
            raise ValueError("src func and dst func do not having matching " \
                "parameter names / order")

        return FunctionType(
            destination_function.__code__,
            destination_function.__globals__,
            destination_function.__name__,
            source_function.__defaults__, # use defaults from src
            destination_function.__closure__
        )
    return decorator

def funA(x, a, b=1):
   return a+b*x

@copy_defaults(funA)
def funB(x, a, b):
    """this is fun B"""
    return funA(x, a, b)

assert funA(1, 2) == funB(1, 2)
assert funB.__name__ == "funB"
assert funB.__doc__ == "this is fun B"
Dunes
  • 37,291
  • 7
  • 81
  • 97
0

You can also use:

def funA(x, a, b=1):
   return a+b*x

def funB(x, a, b=None):
   return funA(*filter(lambda o: o is not None, [x, a, b]))

Version which will not fail if x or a are None:

def funB(x, a, b=None):
    return funA(*([x, a]+filter(lambda o: o is not None, [b])))
  • What, why?! I assume x, a and b must always be passed to x, a and b, not "take the non None values and pass them to the underlying function completely disregarding what/where each parameter is" – LeartS Dec 10 '15 at 16:03