5

I know python treats this namespace in a different way ever since I found out about

def foo(l=[]):
    l.append(1)
    print(l)

foo()
foo()
foo([])
foo()

which prints the following.

[1]
[1,1]
[1]
[1,1,1]

So I was sceptical about their use as an object initializers. Then recently I encountered another similarly strange behaviour, demonstrated below.

class Foo:
    bar = 0
    def __init__(self):
        self.a = bar
Foo()

This raises an exception as bar is not defined inside this namespace.

class Foo:
   bar = 0
   def __init__(self, a=bar)
       self.a = a
Foo()

Now this successfully assigns value held by a class variable foo to an object a inside the initializer. Why do these things happen and how are the default argument values treated?

kuco 23
  • 786
  • 5
  • 18
  • 1
    The issue of the peculiarities of `bar` in the class scope is sort of orthogonal to the issue of default values. What you need to understand is that class bodies do not form an enclosing scope, which is why you must always use `self.bar` within the function bodies of methods defined in the class body. Also note, *values* do not have namespaces *per se*, rather, various namespaces can reference the same value. However, default values are evaluated *once* at function definition time, and they have access to the scope of where the function is defined for whatever expression you are using – juanpa.arrivillaga Oct 09 '19 at 23:31
  • Regarding the "namespace" question, also have a look at [this page](https://docs.python.org/3/reference/datamodel.html#objects-values-and-types) and look for `__defaults__` if you want to know where a function's default arguments are actually stored. – Iguananaut Oct 09 '19 at 23:36

1 Answers1

5

Three facts:

  1. The name (left side) of a default argument is a local variable name inside the function body.
  2. The value (right side) of a default argument is evaluated in the scope where the function is defined, at the time of function definition.
  3. Code within a class block is executed in a temporary namespace during class definition. The class block is not treated like an enclosing scope, which may be surprising if you were expecting behavior similar to a nested def.

Point 3 is the most subtle and perhaps contrary to initial expectations. It's documented in the execution model (section 4.2.2. Resolution of names):

The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods

This is why the name bar is not resolved in your second example:

class Foo:
    bar = 0
    def __init__(self):
        self.a = bar  # name "bar" isn't accessible here, but code is valid syntax

Foo()  # NameError: name 'bar' is not defined

Note that bar value, 0, would still be accessible from within the method as a class attribute: via either Foo.bar or self.bar.

You should now understand why the final example does work:

class Foo:
   bar = 0
   def __init__(self, a=bar):
       self.a = a
Foo()

And, considering points 1-3 above, you should also be able to correctly predict what happens here:

class Foo:
   def __init__(self, a=bar):
       self.a = a
   bar = 0
Foo()

There's more information about the weird class scope over in UnboundLocalError: local variable referenced before assignment why LEGB Rule not applied in this case.

wim
  • 338,267
  • 99
  • 616
  • 750