8

I know that we can access class attributes with self or the class name.

But I am bit confused why the following also works

class Crazy(object):
  VERSION = 1
  def __init__(self, version=VERSION):
    print version

But this doesn't

class Crazy(object):
  VERSION = 1
  def __init__(self):
    print VERSION
deceze
  • 510,633
  • 85
  • 743
  • 889
mittal
  • 915
  • 10
  • 29
  • You couldn't access it **with** `self` in the first snippet, because the method definition runs at class definition time, before you've finished creating the class and *definitely* before you can have created an instance. – jonrsharpe Jul 03 '19 at 11:22
  • But I can do `version=Crazy.VERSION`, right ? – mittal Jul 03 '19 at 11:24
  • 1
    *Where*? Not inside the method parameter list, as the name Crazy doesn't exist yet either. Only inside the method body – jonrsharpe Jul 03 '19 at 11:25
  • Oh yes !! But why does it work with `VERSION` in parameter list but not inside `__init__()` ? – mittal Jul 03 '19 at 11:45
  • 1
    That's just how scope works. The class attributes are in scope inside the class definition, but *not* inside the method bodies. – jonrsharpe Jul 03 '19 at 11:47
  • Oh ok !! So you mean, that the class method `__init__` is still in the phase of being defined in `def __init__(...)`. Basically, we don't have class ready yet, hence `VERSION` works and not `Crazy.VERSION` . But inside `__init__(...)`, class is already defined and instance is being created hence accessing `VERSION` with `self/Crazy.` will work – mittal Jul 03 '19 at 11:54
  • @jonrsharpe "not inside the method bodies" That's apparently how it works in _python_ (which is imo kinda strange) . Not in c/jvm languages afaik – WestCoastProjects Nov 13 '22 at 20:02

1 Answers1

7

A class definition, i.e. the block inside class ...:, is evaluated like any regular Python code block. Just at the end of the class block, every local name which was defined inside that block becomes part of the class' __dict__. The class syntax is more or less just syntactic sugar over:

Crazy = type('Crazy', (object,), {'VERSION': 1, ...})

See https://docs.python.org/3/library/functions.html#type.

Given this, you'd expect this to work, right?

VERSION = 1

def foo(bar=VERSION):
    print(bar)

(Also see this for more explanation on its behaviour.)

Inside a class block this works exactly the same way, with the only special behaviour that you're not creating global names, but names which will become the class' __dict__.

The relevant paragraph in the documentation is here:

The class’s suite is then executed in a new execution frame (see Naming and binding), using a newly created local namespace and the original global namespace. (Usually, the suite contains mostly function definitions.) When the class’s suite finishes execution, its execution frame is discarded but its local namespace is saved. A class object is then created using the inheritance list for the base classes and the saved local namespace for the attribute dictionary. The class name is bound to this class object in the original local namespace.

https://docs.python.org/3/reference/compound_stmts.html#class-definitions

After the class is defined though, that implicit namespace doesn't exist anymore, so this doesn't work:

def __init__(self):
    print(VERSION)

The scoping rules follow the regular lookup chain:

  • is it a local variable inside the same function?
  • is it a nonlocal variable in a surrounding function?
  • is it a global variable?

None of these are true, since VERSION is just an attribute of Crazy at this point, so is only accessible as Crazy.VERSION or self.VERSION, the latter of which actually also doesn't exist and falls back onto its own lookup chain and traverses up to Crazy.VERSION.

Community
  • 1
  • 1
deceze
  • 510,633
  • 85
  • 743
  • 889