In Python, do assignment operators access class or instance variables when passed as a default value in a class method definition?

Viewed 422

for Python 3.7

I have a class with a class attribute (class-wide variable):

class foo:
    var="value goes here"

as well as an instance variable created in the class's init method:

def __init__(self, var=var):
    self.var=var

Passing class variable to parameter

The class variable has the same name as init's parameter, and this doesn't confuse the interpreter, because the interpreter treats any field to the left of an "=" sign as a new variable within the scope of the method. It accomplishes this by populating a new namespace (a dictionary of variables) for the method's scope, implemented either as an array: e.g. parameters[1] = "var" or an associative array: parameters['var'] = pointer_to_value. Then the interpreter looks inside the method body and substitutes the generic reference for any references to "var" that occur on the right side of an "=" sign. Actually, this is a lie, but it's simpler to understand that than what it really does:

The interpreter identifies the matching regular expression .*= *var(,{0,1}| *) *(;{0,1}|\n*) and then passes the corresponding pointer_to_value to the program's call stack). Because of that, the interpreter doesn't care what the parameters are named and is oblivious to the ambiguity of var=var. The fact that the ambiguity can be resolved is a side-effect of the structure of the language, rather than an intentional design decision. After all, ask yourself, when defining a method, why would you access a variable from inside of the method that you are defining? And why would you ever call an assignment operation to a variable in the parent scope from within a method definition? These are both illogical actions, and their namespace possibilities are mutually exclusive, so the interpreter never needs to address the ambiguity.

Conversely, the interpreter treats the right side of the "=" sign as an existing value and searches the class's namespace for variable definitions.

Storing parameter in Instance Variable

Within the method, the instance variable also has the same name as the class variable and the parameter, and this works inside the init method, because the instance variable is accessed via the self reference, e.g.

self.varname = varname

The problem

Method Definition

I need to access the instance variable from within the method definition of another method and I want to use the same name for this function's parameter:

def lookup(self, var=var):
    print(var)

Will the expression var = var get the class attribute or the instance attribute? What does methodname(self) do with self, exactly? Is self a reference to an actual object, or does it only change the behavior of the interpreter from from static method to instance method? Does the interpreter automatically contextualize the right hand side of the "=" sign as an instance attribute of whatever object is typed into methodname(object)?

Method Body

Within the method body, if I make an assignment to var...

def lookup(self, var=var):
    var = var
  1. Will it store it in the class variable, the instance variable, or a new variable with the same name?
  2. Will it get the class variable, instance variable, or the method's variable?
  3. How do I explicitly reference these variables?

I have read the documentation and several OOP tutorials, and a recent book, and this is still not clear to me.

3 Answers

The only way you can access an instance variable is as an attribute of self.

When you just refer to var, that's never an instance variable; it's always a local, enclosing, global, or builtin variable.


In your method definition:

def lookup(self, var=var):
    print(var)

… you have a parameter named var. Parameters are local variables. The print(var) inside the body prints that local variable.

What about this?

def lookup(self, var=var):
    var = var

Again, var is a local variable—a parameter. So, you're just assigning the current value of that local variable to the same variable. Which has no useful effect, but of course it's perfectly legal.


Where does the parameter's value come from? At function call time, if you pass an argument, that argument gets bound to the parameter; if you don't, it gets filled in with the default value.

OK, so where does the default value come from?

At function definition time (when the def statement is executed), var is looked up in the current scope—that is, the body of the class definition—and its value is stored as a default in the function object (it should be visible as foo.lookup.__defaults__[0]).

So, the default value is "value goes here".

Notice that it's not a closure capture or other reference to the class attribute. When the class statement is executed, it uses that same class body namespace to build the class's attributes, so you end up with foo.var as another name for the same value that's in foo.lookup.__defaults__[0]. But they're completely independent names for that value; you can reassign foo.var = 3, and the default value for lookup's parameter will still be "value goes here".


So, to answer your specific questions:

Will it store it in the class variable, the instance variable, or a new variable with the same name?

None of the above. It stores it in a local variable that already exists, because it's a parameter.

Will it get the class variable, instance variable, or the method's variable?

If by "the method's variable" you mean the parameter, it's the last one.

How do I explicitly reference these variables?

The same way you explicitly reference anything else:

  • var is a local-enclosing-global-or-builtin variable.
  • self.var is an instance attribute, or a class attribute if there is no instance attribute.
  • type(self).var is a class attribute, even if there is an instance attribute.

The implicit method argument self is the instance. Thus, while self.var is the instance variable, you need to prefix with the class object to access the class variable. Sometimes, you may not know the class object and then you can simply use the __class__ attribute, i.e. the class variable is self.__class__.var.

To answer your three questions 1. Your assignment in the lookup method will create a new variable with the same name var. 2. It will get the argument that you passed to the method (I guess that's what you mean by "method's variable" 3. The code below shows how you can explicitly access these variables (is illustrates it in the __init__, but it doesn't really matter which method you are in.

class A(object):
    var = 'class_variable'

    def __init__(self, var=var):
        print('Argument var={}'.format(var))
        var = var
        self.var = var
        print('Instance var={}'.format(self.var))
        print('Class var={}'.format(self.__class__.var))
        print('Alternative access to Class var={}'.format(A.var))

This gives

>>> a = A()
Argument var=class_variable
Instance var=class_variable
Class var=class_variable
Alternative access to Class var=class_variable
>>> b = A('v')
Argument var=v
Instance var=v
Class var=class_variable
Alternative access to Class var=class_variable
>>> c = A()
Argument var=class_variable
Instance var=class_variable
Class var=class_variable
Alternative access to Class var=class_variable

I'm going to do my best to answer your questions but if I may isolate your problem first:

I want to use the same name for this function's parameter

Don't do this. There's no logical reason to do it and if it's confusing you now imagine how you'll feel when you look at your code in a few hours/days/weeks/months/years. Imagine how someone else looking at your code will feel.

For everything I write, imagine we have an instance named bob

This will cause bob.var to be "value goes here" every time it is instantiated. Every new object will have var set to "value goes here"

class foo:
    var="value goes here"

This will cause bob.var to be whatever value gets passed to foo during instantiation. If nothing is passed, it will default to "value goes here"

def __init__(self, var=var):
    self.var=var

This will print whatever is passed OR "value goes here" if nothing is passed

def lookup(self, var=var):
    print(var)

This does nothing as the var in question is locally scoped and only exists until the end of the method

def lookup(self, var=var):
    var = var

I hope this helps and just to reiterate: Use different variable names for your different variables.

Related