23

In Python, is it possible to redefine the default parameters of a function at runtime?

I defined a function with 3 parameters here:

def multiplyNumbers(x,y,z):
    return x*y*z

print(multiplyNumbers(x=2,y=3,z=3))

Next, I tried (unsuccessfully) to set the default parameter value for y, and then I tried calling the function without the parameter y:

multiplyNumbers.y = 2;
print(multiplyNumbers(x=3, z=3))

But the following error was produced, since the default value of y was not set correctly:

TypeError: multiplyNumbers() missing 1 required positional argument: 'y'

Is it possible to redefine the default parameters of a function at runtime, as I'm attempting to do here?

Anderson Green
  • 30,230
  • 67
  • 195
  • 328

4 Answers4

44

Just use functools.partial

 multiplyNumbers = functools.partial(multiplyNumbers, y = 42)

One problem here: you will not be able to call it as multiplyNumbers(5, 7, 9); you should manually say y=7

If you need to remove default arguments I see two ways:

  1. Store original function somewhere

    oldF = f
    f = functools.partial(f, y = 42)
    //work with changed f
    f = oldF //restore
    
  2. use partial.func

    f = f.func //go to previous version.
    
RiaD
  • 46,822
  • 11
  • 79
  • 123
  • @joshlf13, you may be in static languages too. – RiaD Jul 13 '13 at 00:14
  • You mean may be able to do something like that code snippet? I suppose it'd be possible with a powerful enough type system, but I don't know many languages like that. Many functional languages, I suppose, but I meant imperative (though I should've said that explicitly). – joshlf Jul 13 '13 at 00:20
  • 1
    Actually, come to think of it, that's basically like currying, except without needing to have your variables ordered. – joshlf Jul 13 '13 at 00:22
  • You can do this in C++ easily. But that's because C++ is sort of a dynamic-at-compile-time language that generates code in a static-at-run-time language. – abarnert Jul 13 '13 at 00:22
  • Ah, yes, I should've figured templating might do it. – joshlf Jul 13 '13 at 00:23
  • @joshlf13: Without templates, C++ is boring and useless. With templates, it's endlessly entertaining and cool… as long as you don't need to write any actual code. :) – abarnert Jul 13 '13 at 00:27
  • I wonder if it's possible to unset a parameter's default value after it has been set. – Anderson Green Jul 13 '13 at 03:06
  • @AndersonGreen, you may store original somewhere – RiaD Jul 13 '13 at 03:19
  • @RiaD What I meant was whether it it's possible to remove an optional parameter's default value so that the optional parameter changes back into a required parameter. – Anderson Green Jul 13 '13 at 03:26
  • 1
    @AndersonGreen, I did understand. I mean, I don't know how to do it directly but you may do it as following `oldF = f; f = partials(...); ... ; f = oldF` – RiaD Jul 13 '13 at 03:28
  • @RiaD It would be very useful to include this information in the answer, then. :) – Anderson Green Jul 13 '13 at 03:30
  • actually you may use `f.func` to go to previous function, but I'm not sure if it could be changed – RiaD Jul 13 '13 at 03:31
  • 1
    @AndersonGreen, added some info – RiaD Jul 13 '13 at 03:36
18

Technically, it is possible to do what you ask… but it's not a good idea. RiaD's answer is the Pythonic way to do this.

In Python 3:

>>> def f(x=1, y=2, z=3):
...     print(x, y, z)
>>> f()
1 2 3
>>> f.__defaults__ = (4, 5, 6)
4 5 6

As with everything else that's under the covers and hard to find in the docs, the inspect module chart is the best place to look for function attributes.

The details are slightly different in Python 2, but the idea is the same. (Just change the pulldown at the top left of the docs page from 3.3 to 2.7.)


If you're wondering how Python knows which defaults go with which arguments when it's just got a tuple… it just counts backward from the end (or the first of *, *args, **kwargs—anything after that goes into the __kwdefaults__ dict instead). f.__defaults = (4, 5) will set the defaults to y and z to 4 and 5, and with default for x. That works because you can't have non-defaulted parameters after defaulted parameters.


There are some cases where this won't work, but even then, you can immutably copy it to a new function with different defaults:

>>> f2 = types.FunctionType(f.__code__, f.__globals__, f.__name__,
...                         (4, 5, 6), f.__closure__)

Here, the types module documentation doesn't really explain anything, but help(types.FunctionType) in the interactive interpreter shows the params you need.


The only case you can't handle is a builtin function. But they generally don't have actual defaults anyway; instead, they fake something similar in the C API.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 2
    You mentioned that it's "not a good idea" to change the default parameters of a function at runtime. Have you noticed any disadvantages of this approach? (I'm not yet aware of any). – Anderson Green Jul 13 '13 at 18:32
  • 3
    @AndersonGreen: Well, there's the obvious disadvantage that it makes it harder for you, other humans, linters, IDEs, reflection-based tools like `pickle`, etc. to understand your code. And you have to fully understand the rules about args/params to be sure you're doing it right. Then there's the fact that you can't make it work generally without writing code that can deal with regular functions, weird immutable functions (which need to construct a new function out of the code), and builtin functions (which you need to wrap, with a `partial` or a new function that calls it). – abarnert Jul 15 '13 at 18:10
  • Can we do something like `f.__defaults__ = (4, 5, 6)` from inside a `lambda` function? @abarnert – Diptangsu Goswami Oct 01 '19 at 06:20
  • @DiptangsuGoswami `lambda` doesn't allow assignment, although assignment expressions in 3.8 will make that technically possible. – Artemis Oct 13 '19 at 16:26
  • Interesting, but can you modify default values inside of the function definition? The reason why I ask this is because I have two versions of a library, one is build with foo = true, the other is with foo = false. So, for my function f(a, b=1) return a+b; I want the default value of b =2 if foo = true; if foo = false, it should default to 1. – StayFoolish Aug 25 '21 at 02:58
3

yes, you can accomplish this by modifying the function's func.__defaults__ tuple

that attribute is a tuple of the default values for each argument of the function.

for example, to make pandas.read_csv always use sep='\t', you could do:

import inspect

import pandas as pd

default_args = inspect.getfullargspec(pd.read_csv).args
default_arg_values = list(pd.read_csv.__defaults__)
default_arg_values[default_args.index("sep")] = '\t'
pd.read_csv.__defaults__ = tuple(default_arg_values)
william_grisaitis
  • 5,170
  • 3
  • 33
  • 40
1

use func_defaults as in

def myfun(a=3):
    return a

myfun.func_defaults = (4,)
b = myfun()
assert b == 4

check the docs for func_defaults here

UPDATE: looking at RiaD's response I think I was too literal with mine. I don't know the context from where you're asking this question but in general (and following the Zen of Python) I believe working with partial applications is a better option than redefining a function's defaults arguments

Francisco Meza
  • 875
  • 6
  • 8
  • I didn't manage to set it to second of 3 variables. Only works for last for me. – RiaD Jul 13 '13 at 00:20
  • @RiaD: Function defaults have to be a final sequence of parameters; just as you can't write `def foo(x, y=2, z):`, you can't change the `func_defaults`/`__defaults__` to just cover `y`. – abarnert Jul 13 '13 at 00:21