1

I have the following python snippet:

class myClass:
    myVar = 'a'

    def __init__(self):
        self.myOtherVar = 'b'
        myVar = 'c'  # Gets assigned but only as a local variable.

    print myVar            # prints 'a' !
    print self.myOtherVar  # says 'self' not found

My question is this; What's the proper way to print the contents of myVar from within myClass and/or re-assign them from init?

stratis
  • 7,750
  • 13
  • 53
  • 94
  • 2
    duplicate of [Static class variables in Python](http://stackoverflow.com/questions/68645/static-class-variables-in-python) – BartoszKP Jul 01 '14 at 08:41
  • For me it prints `'a'` for `print myVar`. – bereal Jul 01 '14 at 08:46
  • Are you sure your indentation is correct? – Burhan Khalid Jul 01 '14 at 09:01
  • There is nothing special about `self`; it is just the conventional name for the the first argument to a method, which automatically gets assigned to the instance being called. Outside of methods, you can access class-level attributes directly. – Andrew Jaffe Jul 01 '14 at 09:03
  • What you describe is not true. That code prints `a` and then an error since `self` doesn't exist. But `myVar` is **not** `None` and does *not* raise an error. Also, if the line with `myVar` raises an error then how you know that the following line raises an error too, since it wont be executed? – Bakuriu Jul 01 '14 at 09:09
  • @Bakuriu You are corrent. My bad. `myVar` prints out correctly. The real problem is that I can't access it from `myClass` init. And if I do, I will have simply created a new -local- one. – stratis Jul 01 '14 at 09:13
  • @BurhanKhalid Yes identation is correct. Please see updated answer. – stratis Jul 01 '14 at 09:14
  • @bereal Please see updated answer. `myVar` simply can't be accessed from within `__init__`. – stratis Jul 01 '14 at 09:15
  • @Konos5 you have to use `myClass.myVar` inside `__init__`. – Bakuriu Jul 01 '14 at 09:22
  • @Konos5 The syntax for accessing class's static variable is as in the thread I posted above. `myClass.myVar` in this case. It doesn't matter from where you do it. – BartoszKP Jul 01 '14 at 09:25

3 Answers3

9

The problem you are facing is because you don't understand how the scoping of class declarations work. A class declaration is executed in its own scope. After the execution is completed a new class object is created and the obtained scope is attached to the class as its __dict__.

Note: the class scope is not searched from within the methods scopes! This means that you have to reference class attributes as MyClass.attribute when inside a method definition.

For example:

class MyClass:
    var = 1

    # we are executing this code as a single block
    # so you must reference the variable as is usual
    print(var)

    # default values are *not* inside the definition.
    # they are evaluated in the outer scope, so use plain "var" here
    def method(self, a_default=var):
        print(a_default)

    def other_method(self):

        # inside methods you are in a different scope
        print(MyClass.var)

        # equivalent *if* no "var" instance attributes exists
        print(self.var)

Note: since the class doesn't still exist when executing its declaration you cannot refer to MyClass at the "top level" of MyClass declaration:

class MyClass:
    var = 1
    print(MyClass.var)   # error: MyClass still doesn't exist.

A side effect of this, is that the following code:

class MyClass:
    x = 1
    results = list(i+x for i in range(10))

Produces:

NameError                                 Traceback (most recent call last)
<ipython-input-6-f1d4417b2e52> in <module>()
----> 1 class MyClass:
      2     x = 1
      3     results = list(i+x for i in range(10))
      4 

<ipython-input-6-f1d4417b2e52> in MyClass()
      1 class MyClass:
      2     x = 1
----> 3     results = list(i+x for i in range(10))
      4 

<ipython-input-6-f1d4417b2e52> in <genexpr>(.0)
      1 class MyClass:
      2     x = 1
----> 3     results = list(i+x for i in range(10))
      4 

NameError: name 'x' is not defined

Because generator expressions (and list-comprehensions in python3) are, in fact, considered functions with their own scope. Since the class scope isn't searched from inner function scopes the x cannot be found.

You can word around this using a function definition and default values:

class MyClass:
    x = 1
    def _make_results(x=x):
        return list(i+x for i in range(10))
    results = _make_results()
    del _make_results    # otherwise it would be added as a method.
    # or:
    results = (lambda x=x: list(i+x for i in range(10)))()

This isn't usually a problem since class definitions rarely contain anything other than method definitions and a few constants.


There are already a few questions on SO about class scopes:

Community
  • 1
  • 1
Bakuriu
  • 98,325
  • 22
  • 197
  • 231
  • An exemplary answer by all means. However I need one last clarification; You said that *I cannot refer to MyClass at the "top level" of MyClass declaration* but instead I can do it from within one of its method declarations. This simply begs the question: When exactly is a **class** considered alive so that I can start refering to it? And I iterate - not the instance but the class. – stratis Jul 01 '14 at 09:44
  • 1
    As soon as the `class` block ends. You can use it it in methods, because a function's body is only evaluated once you call it. Which is after the class definition's parsed and executed. – sebastian Jul 01 '14 at 09:48
  • 1
    @Konos5 A class is "alive" after its body is evaluated. The evaluation of a method definition means that its code is *compiled*. So inside a method there is no problem referring to `MyClass` because, since the method isn't executed, the reference will be only evaluated after the class was completely created. As far as the method is concerned `MyClass` is just a global variable. In fact you can access the class also as `self.__class__` without referencing `MyClass`. However `self.__class__` may not be `MyClass` if you create some subclasses. – Bakuriu Jul 01 '14 at 09:49
0

self.var will:

  1. give var in self.__dict__ if present
  2. give var in self.__class__.__dict__ if present
  3. AttributeError

So use this or self.__class__.var if you want to access the static variable minding inheritance. If you extend myClass, the children instances will access the static variable in the child class.

If you want to access the static variable in myClass even when called from descendants, use myClass.var.

As for reassigning them, this must be done explicitly on the class object, or the assignment will just target the instance.

salezica
  • 74,081
  • 25
  • 105
  • 166
  • No. From within myClass I can't do `print myClass.var`. Python spits out an error saying the `myClass` is not defined. – stratis Jul 01 '14 at 08:52
  • @Konos5 You can't `print` within a class, but not within its method. Could just please read the duplicate I've given link to? It contains everything there is to know on static variables in Python. – BartoszKP Jul 01 '14 at 08:54
  • @BartoszKP I'm looking at it as we speak. However I still can't seem to find a clear way of reassigning the static variavle `myVar` from within `myClass`'s `__init__` method... – stratis Jul 01 '14 at 09:07
  • @Konos5 It's the same syntax as everywhere else in that thread: `myClass.myVar`. It doesn't matter from where you do it. It's a variable inside that class, `.` always means reaching inside of something. – BartoszKP Jul 01 '14 at 09:22
-4
class myClass:
    myVar = 'a'

    def __init__(self):
        self.myOtherVar = 'b'

    print myVar  # -> 'a'

    class EmptyClass: pass
    s = EmptyClass()
    __init__(s)

    myVar = s.myOtherVar
    print myVar  # -> 'b'

print myClass.myVar  # -> 'b'
g0g0
  • 62
  • 7
  • Care to explain what exactly did you do here? I am looking for the functionality you demostrate but in a more "clean" way. Also at some point you wrote `__init__(s)`... which class that __init__ refers to? – stratis Jul 01 '14 at 09:21
  • 1
    We have to call `myClass.__init__` with some instance to get `'b'`. So I created an empty class called `EmptyClass` then I used instance of `EmptyClass` to call `myClass.__init__` by `__init__(s)` so `__init__` refers to `myClass` – g0g0 Jul 01 '14 at 09:53