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]