nerdfever.com
nerdfever.com

Reputation: 1782

Python globals and keyword arguments

Why does test2() below print "True False"? I would expect "False False".

I expect test2() to change the global value EC to be False, so ec should also be False.

Why not?

Is there a straightforward way to get the "False False" behavior?

EC = True

def test1(ec=EC):
    print(ec, EC)

def test2():

    global EC
    EC=False

    test1()

Upvotes: 0

Views: 173

Answers (3)

Nguyễn Minh Nhật
Nguyễn Minh Nhật

Reputation: 43

WHY IT WORKS LIKE THAT?

I think of your problem as this:

  • when you create your variable EC, it stay in location A in your memory.
  • when you create your function test1(), it stay in location B.
  • when you create your function test2(), it stay in location C.

Let say: in test2(), you set EC as global variable, it runs, but save that global EC in location D, at the end of test2(), EC gets its value from D.

That means when you call test1() inside test2(), EC that set to ec stay in A, not in D. But somehow when you print, it looks for D in your memory.

MY SOLUTION

EC = True

def test1(ec=EC):
    print(ec, EC)

def test2():
    global EC
    EC = False
    test1(ec=EC)

This is my code, and it works with Python 3.6.9

MY PROVE

To understand this, in Python there are 2 build-in functions are hex() and id(), so when you want to find where your variable stay in your memory you can check by hex(id(EC))

You can run your code as:

EC = True
print(hex(id(EC)))
def test1(ec=EC):
    print(ec, EC)
print(hex(id(test1)))
def test2():
    global EC
    EC = False
    print(hex(id(EC)))
    test1()
print(hex(id(test2)))

You will find out that your global EC & first EC variable are stay in 2 different location. That's why you get the result of True False

Upvotes: 0

user2357112
user2357112

Reputation: 280181

First, that's a default value, not a keyword argument. If you want to pass keyword arguments, that would look like this:

def test1(ec):
    print(ec)

test1(ec=True)

Second, unlike most languages with default argument values, Python evaluates default values at function definition time, not function call time. This is an extremely unusual design decision that causes a lot of problems. The typical workaround is to use a sentinel value like None as the default, and compute the "real" default inside the function if the sentinel is detected:

EC = True

def test1(ec=None):
    if ec is None:
        ec = EC
    print(ec, EC)

def test2():

    global EC
    EC=False

    test1()

Upvotes: 1

ShadowRanger
ShadowRanger

Reputation: 155323

Function defaults bind at function definition, not at function call. For immutable types like bool, nothing you do to EC after test1 is defined will affect the default value bound to test1's ec argument.

If you want call time binding, you're stuck accepting a sentinel default, and loading dynamically in response to getting it, e.g.:

def test1(ec=None):
    if ec is None:
        ec = EC
    ...

If None might be a valid argument (and therefore not usable as a sentinel value), you can make and use your own sentinel object, used for no other purpose, instead:

_test1_sentinel = object()  # Cheapest way to make new, guaranteed unique object
def test1(ec=_test1_sentinel):
    if ec is _test1_sentinel:
        ec = EC
    ...

Upvotes: 2

Related Questions