1

I need to change the global variable S at a.py from b.py, but it is used as a default value in a function at a.py.

a.py

S = "string"


def f(s=S):
    print(s)
    print(S)

b.py

import a


def main():
    a.S = "another string"
    a.f()


if __name__ == "__main__":
    main()

python b.py outputs

string
another string

instead of expected

another string
another string

If I call a.f in b.py like this

a.f(a.S)

this works as expected, but is there any way to change default variable value?

Georgy
  • 12,464
  • 7
  • 65
  • 73
fas
  • 1,393
  • 10
  • 20
  • 1
    It's because default arguments are defined at function definition time, not function execution time. This is the same mechanism that drives the [mutable default arg](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) behavior as well – C.Nivs Aug 26 '19 at 17:14
  • You can change the `__defaults__` and `__kwdefaults__` attributes of the function object to dynamically change the default values. – Jaakkonen Mar 03 '22 at 00:17

1 Answers1

4

The short answer is: You can't.

The reason for this is that the function default arguments are created at function definition time, and the defaults are not meant to be re-defined. The variable name is bound once to a value and that is all, you can't re-bind that name to another value. First, let's look at variables in global scope:

# create a string in global scope
a = "string"

# b is "string"
b = a

a += " new" # b is still "string", a is a new object since strings are immutable

You've now just bound a new name to "string", and "string new" is a completely new value bound to a, it does not change b because str += str returns a new str, making a and b refer to different objects.

The same happens with functions:

x = "123"

# this expression is compiled here at definition time
def a(f=x):
    print(f)

x = "222"
a()
# 123

The variable f was defined with the default of "123" at definition time. This can't be changed. Even with mutable defaults such as in this question:

x = []

def a(f=x):
    print(x)

a()
[]

# mutate the reference to the default defined in the function
x.append(1)

a()
[1]

x
[1]

The default argument was already defined, and the name f was bound to the value [], that cannot be changed. You can mutate the value associated with f, but you cannot bind f to a new value as a default. To further illustrate:

x = []

def a(f=x):
    f.append(1)
    print(f)

a()
x
[1]

# re-defining x simply binds a new value to the name x
x = [1,2,3]

# the default is still the same value that it was when you defined the
# function, albeit, a mutable one
a()
[1, 1]

It might be better to either A) pass the global variable in as an argument to the function or B) use the global variable as global. If you are going to change the global variable you wish to use, don't set it as a default parameter and choose a more suitable default:

# some global value
x = "some default"

# I'm choosing a default of None here
# so I can either explicitly pass something or
# check against the None singleton
def a(f=None):
    f = f if f is not None else x
    print(f)

a()
some default

x = "other default"
a()
other default

a('non default')
non default
C.Nivs
  • 12,353
  • 2
  • 19
  • 44
  • @c-nivs "Pair this with the fact that strings are immutable, " => this is mostly unrelated actually - what you're illustrating here is tye difference between rebinding and mutating, not the difference between mutable and immutable objects (except of course for the fact that you can't mutate an immutable object). You may want to read tthis: https://nedbatchelder.com/text/names.html – bruno desthuilliers Aug 27 '19 at 09:23
  • Also you don't need the `global x` - it's only required if you want to rebind the global from within a function. – bruno desthuilliers Aug 27 '19 at 09:24
  • @brunodesthuilliers I'll remove the `global`. The fact that strings are immutable, as I've understood it (for small strings, not sure about large ones), are the reason that python will re-bind the value and not share a mutable reference. If I've misunderstood, is there an edit I can make to make a more correct answer? – C.Nivs Aug 27 '19 at 12:36
  • 1/ all strings are immutable, period, and 2/ python doesn't rebind the name because the string is immutable but because __you__ told it to do so ("binding" => "assignment"). In your second example (the one with a list), if you rebind the list instead of mutating it (=> replace `x.append(1)` with `x = [1]` in the snippet) you'll find out that the behaviour is exactlt the same as for the first (string) example. FWIW, __all this is explained in the article I linked to__ (https://nedbatchelder.com/text/names.html), and you _definitly_ want to read this article (and then edit your post) – bruno desthuilliers Aug 27 '19 at 13:32