Ali250
Ali250

Reputation: 662

Using a variable that is shared across multiple modules as a default function argument

The issue at hand is best illustrated with this minimal example containing 3 scripts:

foo.py

global_val = [0]

bar.py

from foo import global_val

def work(val=global_val[0])
    print("global_val: ", global_val[0])
    print("val: ", val)

main.py

from bar import work
import foo

if __name__ == '__main__':
    foo.global_val[0] = 1
    work()

What I expect the output to be:

global_val: 1
val: 1

Actual output:

global_val: 1
val: 0

I don't understand why the default argument for val in bar.py isn't 1. I'm confused because I clearly update global_val before calling work(), but for some reason the old value is still used as the default function argument.

It seems that the default argument is somehow pre-computed when global_val is imported in bar.py. Isn't Python code supposed to be dynamically compiled at run-time?

I'm using Python 3.6, if that helps.

Upvotes: 3

Views: 130

Answers (5)

Sean Vieira
Sean Vieira

Reputation: 160005

The key is that def work(val=global_val[0]): is evaluated at import time (e. g. when from bar import work is hit in main.py.) The default arguments for a function in Python are evaluated at the moment the function is defined and stored in its signature (here's how you can inspect them).

Thus, the order of operations is:

  1. Run main.py
  2. from bar import work
  3. Locate and load bar
  4. from foo import global_val
  5. Locate and load foo
  6. def work(val=global_val[0]):
  7. Construct a function called work and evaluate its default parameters (global_val[0] == 0)
  8. foo.global_val[0] = 1
  9. Invoke work.

Upvotes: 4

Lurvas777
Lurvas777

Reputation: 35

I think when the the compiler sees this:

def work(val=global_val[0])

the default value of val will be what global_val[0] were at that time. Setting it to something different later on will not change the function definition, i.e. set the val variable to 0 (which is the first element of global_val) if no argument for that parameter is supplied.

Try something like this instead:

def work(val=None):
  if not val:
    global global_val
    val = global_val[0]

Here you set val to something known that you can catch, None in this case, and then set the proper value. Using the keyword global makes sure you use the variable from the global namespace. This will sett the correct value of global_val if it's changed later on after the function definition is compiled.

Upvotes: 0

Grunt Futuk
Grunt Futuk

Reputation: 16

As defaults are fixed at the time of definition/import you would be best setting a flag or None as the default and testing for that flag at the head of the function where you can then assign an object, from available scope (of course), at execution time as the actual default you want.

Upvotes: 0

Davichete
Davichete

Reputation: 425

If I'm not wrong, what is going on is that at the moment you are importing work func from bar.py, the default argument is executed, and doesn't matter if you change the value later, because the default argument was already "declared" on the import, since the default arguments are evaluated just once

Upvotes: 1

Michael Bianconi
Michael Bianconi

Reputation: 5242

item = 0

def bar(val=item):
    print(val)

bar(2)  # 2
bar()  # 0
item = 1
bar()  # 0

This is something to do with default arguments, not globals. Default arguments are evaluated once, not each time the function is called.

Upvotes: 0

Related Questions