title: This Python class variable scope behavior is totally unexpected and frankly weird. What's going on?
body: <p>Ok, this one has me tearing my hair out:</p>
  
  <p>I have a multi-process program, with separate workers each working on a given task.</p>
  
  <p>When a KeyboardInterrupt comes, I want each worker to save its internal state to a file, so it can continue where it left off next time.</p>
  
  <p>HOWEVER...</p>
  
  <p>It looks like the dictionary which contains information about the state is vanishing before this can happen!</p>
  
  <p>How? The exit() function is accessing a more globally scoped version of the dictionary... and it turns out that the various run() (and subordinate to run()) functions have been creating their own version of the variable.</p>
  
  <p>Nothing strange about that...</p>
  
  <p>Except...</p>
  
  <p>All of them have been using the self. keyword.</p>
  
  <p>Which, if my understanding is correct, should mean they are always accessing the instance-wide version of the variable... not creating their own!</p>
  
  <p>Here's a simplified version of the code:</p>
  
  <pre><code>import multiprocessing
  import atexit
  import signal
  import sys
  import json
  
  class Worker(multiprocessing.Process):
      def __init__(self, my_string_1, my_string_2):
          # Inherit the __init_ from Process, very important or we will get errors
          super(Worker, self).__init__()
          # Make sure we know what to do when called to exit
          atexit.register(self.exit)
          signal.signal(signal.SIGTERM, self.exit)
          self.my_dictionary = {
              'my_string_1' : my_string_1,
              'my_string_2' : my_string_2
              }
      def run(self):
          self.my_dictionary = {
              'new_string' : 'Watch me make weird stuff happen!'
              }
          try:
              while True:
                  print(self.my_dictionary['my_string_1'] + " " + self.my_dictionary['my_string_2'])
          except (KeyboardInterrupt, SystemExit):
              self.exit()
      def exit(self):
          # Write the relevant data to file
          info_for_file = {
              'my_dictionary': self.my_dictionary
              }
          print(info_for_file) # For easier debugging
          save_file = open('save.log', 'w')
          json.dump(info_for_file, save_file)
          save_file.close()
          # Exit
          sys.exit()
  
  if __name__ == '__main__':
      strings_list = ["Hello", "World", "Ehlo", "Wrld"]
      instances = []
      try:
          for i in range(len(strings_list) - 2):
              my_string_1 = strings_list[i]
              my_string_2 = strings_list[i + 1]
              instance = Worker(my_string_1, my_string_2)
              instances.append(instance)
              instance.start()
          for instance in instances:
              instance.join()
      except (KeyboardInterrupt, SystemExit):
          for instance in instances:
              instance.exit()
              instance.close()
  </code></pre>
  
  <p>On run we get the following traceback...</p>
  
  <pre><code>Process Worker-2:
  Process Worker-1:
  Traceback (most recent call last):
    File "/usr/lib/python3.5/multiprocessing/process.py", line 249, in _bootstrap
      self.run()
  Traceback (most recent call last):
    File "/usr/lib/python3.5/multiprocessing/process.py", line 249, in _bootstrap
      self.run()
    File "&lt;stdin&gt;", line 18, in run
    File "&lt;stdin&gt;", line 18, in run
  KeyError: 'my_string_1'
  KeyError: 'my_string_1'
  </code></pre>
  
  <p>In other words, even though the key my_string_1 was explicitly added during <strong>init</strong>, the run() function is accessing a new version of self.my_dictionary which does not contain that key!</p>
  
  <p>Again, this would be expected if we were dealing with a normal variable (my_dictionary instead of self.my_dictionary) but I thought that self.variables were always instance-wide...</p>
  
  <p>What is going on here?</p>