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”?
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.
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]
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”?
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.
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]