External dictionary reading behavior in a class

Question:

I have found this behavior that I can not understand. I state that this is a simplification of the problem I’m having, since the dictionary in my script has a lot more items.

configuration = {
    "video": {
        "fullscreen": {
            "user": None,
            "default": False
        }
    }
}

class test():

    fullscreen = configuration["video"]["fullscreen"]["user"]

    def __init__(self):
        print(configuration)
        print(configuration["video"]["fullscreen"]["user"])
        print(self.fullscreen)

if __name__ == "__main__":
    configuration["video"]["fullscreen"]["user"] = True
    t = test()

This is the result:

{'video': {'fullscreen': {'user': True, 'default': False}}}
True
None

Why in the third print the result is “None”?

Asked By: BlackFenix06

||

Answers:

The behaviour occurs because self.fullscreen refers to the class attribute which is defined before the edit to the one in the global scope.

Once you create it, it goes into the class’ __dict__ so changing the one in the outer scope obviously will have no correlation to the on stored in the class.

I have a feeling you’d get different results if you had written fullscreen = configuration since it may refer to the same dictionary object.

Answered By: N Chauhan

Actually the explanations given so far to your question were not really totally clarifying to me the order of instructions execution in your more-than-legitimate question. I think I perfectly understood what you meant and it puzzled me too

The following example will show you that the class attribute user_conf [renamed to avoid focusing on the wrong point] is created before running configuration["video"]["fullscreen"]["user"] = "John" in the main(). In other words – at pure class attribute level – its value is set from the configuration blueprint. It will be only the class constructor – called after the main – to update that value later

configuration = {
    "video": {
        "fullscreen": {
            "user": None,
            "default": False
        }
    }
}
# correcting global variable blueprint
# configuration["video"]["fullscreen"]["user"] = "John"

class test():

    print(configuration["video"]["fullscreen"]["user"])
    user_conf = configuration["video"]["fullscreen"]["user"]
    print(user_conf)

    def __init__(self):
        # printing modified global variable, all right
        print(configuration)
        print(configuration["video"]["fullscreen"]["user"])
        print(self.user_conf)
        self.user_conf = "Jack"
        print(self.user_conf)

def main():
    # modifying global variable later
    # at this point the class attribute user_conf has already been assigned with the old value
    configuration["video"]["fullscreen"]["user"] = "John"
    test()

if __name__ == '__main__':
    main()

Please notice that if you comment the value update in the main and uncomment these lines that I added:

# correcting global variable blueprint
# configuration["video"]["fullscreen"]["user"] = "John"

just after the configuration declaration you will have the output without any None that you were expecting to find, because the class attribute will be created by a "corrected" blueprint. In this case then you will get:

John
John
{‘video’: {‘fullscreen’: {‘user’: ‘John’, ‘default’:
False}}}
John
John
Jack

Another way to produce this tweaking the sample at point 6 here:

def outer():

    configuration = {
      "video": {
          "fullscreen": {
              "user": None,
              "default": False
              }
          }
    }  
    print("initial outer configuration:", configuration)
    
    def inner():
        nonlocal configuration
        '''
        configuration = {
          "video": {
              "fullscreen": {
                  "user": "John",
                  "default": False
                  }
              }
        }  
        '''
        configuration["video"]["fullscreen"]["user"] = "John"
        print("inner configuration:", configuration)
    
    inner()
    print("modified outer configuration:", configuration)

outer()

which would give:

initial outer configuration: {‘video’: {‘fullscreen’: {‘user’: None,
‘default’: False}}}
inner configuration: {‘video’:
{‘fullscreen’: {‘user’: ‘John’, ‘default’: False}}}
modified
outer configuration: {‘video’: {‘fullscreen’: {‘user’: ‘John’,
‘default’: False}}}

Hope this can solve better your doubt


Edit after the OP comment: as I openly declared it took me some time to figure out what is happening. Let’s take this code:

configuration = {
    "video": {
        "fullscreen": {
            "user": None,
            "default": False
        }
    }
}

print("step 1 -> " + str(configuration))

# correcting global variable blueprint
# configuration["video"]["fullscreen"]["user"] = "John"

class test():

    print("step 2 -> " + str(configuration))
    user_conf = configuration["video"]["fullscreen"]["user"]

    def __init__(self):
        # printing modified global variable, all right
        print("step 5 -> constructor reads the updated value: " + str(configuration))

def main():
    # modifying global variable later
    # at this point the class attribute user_conf has already been assigned with the old value
    print("step 3 -> " + str(configuration))
    configuration["video"]["fullscreen"]["user"] = "John"
    print("step 4 -> main just updated the global variable: " + str(configuration))
    test()

if __name__ == '__main__':
    main()

Printing this will give you the following output:

step 1 -> {‘video’: {‘fullscreen’: {‘user’: None, ‘default’: False}}}

step 2 -> {‘video’: {‘fullscreen’: {‘user’: None, ‘default’:
False}}}
step 3 -> {‘video’: {‘fullscreen’: {‘user’: None,
‘default’: False}}}
step 4 -> main just updated the global
variable: {‘video’: {‘fullscreen’: {‘user’: ‘John’, ‘default’:
False}}}
step 5 -> constructor reads the updated value:
{‘video’: {‘fullscreen’: {‘user’: ‘John’, ‘default’: False}}}

Now, if you read this answer you will easily understand that Python is executed top to bottom and executing a def block – in our case __init__(self) – doesn’t immediately execute the contained code. Instead it creates a function object with the given name in the current scope which is actually entered only after calling test() from main(), i.e. only after you ask to instance an object from the test() class, which will trigger its constructor

IMPORTANT: in your case I realized that you are calling the class test() and test() is what you are calling from main(). Your main is calling a method actually, test(): so please replace class test() with def test() in the previous code and you will get a different and more understandable execution flow:

step 1 -> {‘video’: {‘fullscreen’: {‘user’: None, ‘default’: False}}}

step 3 -> {‘video’: {‘fullscreen’: {‘user’: None, ‘default’:
False}}}
step 4 -> main just updated the global variable:
{‘video’: {‘fullscreen’: {‘user’: ‘John’, ‘default’: False}}}
step 2 -> {‘video’: {‘fullscreen’: {‘user’: ‘John’, ‘default’:
False}}}

After the first print all the def blocks are skipped and we enter the main(). The main() updates the global variable and then the test() function would work immediately on the updated value. Of course the constructor in this case would not be triggered [this is not anymore a class] and this explains the lack of step 5

-> are you sure you are making a good choice in defining and using your class in this way? [probably not]
-> isn’t it better to declare test() as def instead that as class? [I really think so]

Answered By: Antonino
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.