viral_mutant
viral_mutant

Reputation: 1003

Confusion in accessing class attribute without self - python

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

Upvotes: 8

Views: 4429

Answers (1)

deceze
deceze

Reputation: 522005

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.

Upvotes: 8

Related Questions