0

Disclaimer: this is about weird language behavior in a crazy edge case. I'm using it to ask a bigger question about python and memory/disk read writes. How does dynamically editing a file and subsequently calling differ from importing the file and then calling? Is importing the same file different than exporting external modules with respect to loading from memory/ loading from disk? What conditions trigger reloading a function from disk.

I am trying to understand the way that python functions are loaded into memory (and when they are reread from disk). I wrote a simple script (temp.py) that modifies itself in a call to modify_this_function, it writes in a print line. Nothing crazy, predictable behavior, adds a print statement when called.

import dis

def modify_this_function():
    f = open("temp.py", "r")
    contents = f.readlines()
    f.close()

    contents.insert(3, '\tprint("hello!")\n')

    f = open("temp.py", "w")
    contents = "".join(contents)
    f.write(contents)
    f.close()

modify_this_function()
print(dis.dis(modify_this_function))
modify_this_function()

This call doesn't do anything particularly interesting (though it does modify the file on reload, adds two print statements). The output of the call to dis.dis reflects the original function definition.

  4           0 LOAD_GLOBAL              0 (open)
              3 LOAD_CONST               1 ('temp.py')
              6 LOAD_CONST               2 ('r')
              9 CALL_FUNCTION            2
             12 STORE_FAST               0 (f)

  5          15 LOAD_FAST                0 (f)
             18 LOAD_ATTR                1 (readlines)
             21 CALL_FUNCTION            0
             24 STORE_FAST               1 (contents)

  6          27 LOAD_FAST                0 (f)
             30 LOAD_ATTR                2 (close)
             33 CALL_FUNCTION            0
             36 POP_TOP             

  8          37 LOAD_FAST                1 (contents)
             40 LOAD_ATTR                3 (insert)
             43 LOAD_CONST               3 (3)
             46 LOAD_CONST               4 ('\tprint("hello!")\n')
             49 CALL_FUNCTION            2
             52 POP_TOP             

 10          53 LOAD_GLOBAL              0 (open)
             56 LOAD_CONST               1 ('temp.py')
             59 LOAD_CONST               5 ('w')
             62 CALL_FUNCTION            2
             65 STORE_FAST               0 (f)

 11          68 LOAD_CONST               6 ('')
             71 LOAD_ATTR                4 (join)
             74 LOAD_FAST                1 (contents)
             77 CALL_FUNCTION            1
             80 STORE_FAST               1 (contents)

 12          83 LOAD_FAST                0 (f)
             86 LOAD_ATTR                5 (write)
             89 LOAD_FAST                1 (contents)
             92 CALL_FUNCTION            1
             95 POP_TOP             

 13          96 LOAD_FAST                0 (f)
             99 LOAD_ATTR                2 (close)
            102 CALL_FUNCTION            0
            105 POP_TOP             
            106 LOAD_CONST               0 (None)
            109 RETURN_VALUE        
None

This seems to indicate that the function was in memory, not reloaded from disk. Is that correct?

import dis

def modify_this_function():
    f = open("temp.py", "r")
    contents = f.readlines()
    f.close()

    contents.insert(3, '\tprint("hello!")\n')

    f = open("temp.py", "w")
    contents = "".join(contents)
    f.write(contents)
    f.close()

modify_this_function()
print(dis.dis(modify_this_function))
#modify_this_function()
from temp import modify_this_function

This function call is more interesting.

  4           0 LOAD_GLOBAL              0 (open)
              3 LOAD_CONST               1 ('temp.py')
              6 LOAD_CONST               2 ('r')
              9 CALL_FUNCTION            2
             12 STORE_FAST               0 (f)

  5          15 LOAD_FAST                0 (f)
             18 LOAD_ATTR                1 (readlines)
             21 CALL_FUNCTION            0
             24 STORE_FAST               1 (contents)

  6          27 LOAD_FAST                0 (f)
             30 LOAD_ATTR                2 (close)
             33 CALL_FUNCTION            0
             36 POP_TOP             

  8          37 LOAD_FAST                1 (contents)
             40 LOAD_ATTR                3 (insert)
             43 LOAD_CONST               3 (3)
             46 LOAD_CONST               4 ('\tprint("hello!")\n')
             49 CALL_FUNCTION            2
             52 POP_TOP             

 10          53 LOAD_GLOBAL              0 (open)
             56 LOAD_CONST               1 ('temp.py')
             59 LOAD_CONST               5 ('w')
             62 CALL_FUNCTION            2
             65 STORE_FAST               0 (f)

 11          68 LOAD_CONST               6 ('')
             71 LOAD_ATTR                4 (join)
             74 LOAD_FAST                1 (contents)
             77 CALL_FUNCTION            1
             80 STORE_FAST               1 (contents)

 12          83 LOAD_FAST                0 (f)
             86 LOAD_ATTR                5 (write)
             89 LOAD_FAST                1 (contents)
             92 CALL_FUNCTION            1
             95 POP_TOP             

 13          96 LOAD_FAST                0 (f)
             99 LOAD_ATTR                2 (close)
            102 CALL_FUNCTION            0
            105 POP_TOP             
            106 LOAD_CONST               0 (None)
            109 RETURN_VALUE        
None
hello!
  4           0 LOAD_CONST               1 ('hello!')
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       

  5           5 LOAD_GLOBAL              0 (open)
              8 LOAD_CONST               2 ('temp.py')
             11 LOAD_CONST               3 ('r')
             14 CALL_FUNCTION            2
             17 STORE_FAST               0 (f)

  6          20 LOAD_FAST                0 (f)
             23 LOAD_ATTR                1 (readlines)
             26 CALL_FUNCTION            0
             29 STORE_FAST               1 (contents)

  7          32 LOAD_FAST                0 (f)
             35 LOAD_ATTR                2 (close)
             38 CALL_FUNCTION            0
             41 POP_TOP             

  9          42 LOAD_FAST                1 (contents)
             45 LOAD_ATTR                3 (insert)
             48 LOAD_CONST               4 (3)
             51 LOAD_CONST               5 ('\tprint("hello!")\n')
             54 CALL_FUNCTION            2
             57 POP_TOP             

 11          58 LOAD_GLOBAL              0 (open)
             61 LOAD_CONST               2 ('temp.py')
             64 LOAD_CONST               6 ('w')
             67 CALL_FUNCTION            2
             70 STORE_FAST               0 (f)

 12          73 LOAD_CONST               7 ('')
             76 LOAD_ATTR                4 (join)
             79 LOAD_FAST                1 (contents)
             82 CALL_FUNCTION            1
             85 STORE_FAST               1 (contents)

 13          88 LOAD_FAST                0 (f)
             91 LOAD_ATTR                5 (write)
             94 LOAD_FAST                1 (contents)
             97 CALL_FUNCTION            1
            100 POP_TOP             

 14         101 LOAD_FAST                0 (f)
            104 LOAD_ATTR                2 (close)
            107 CALL_FUNCTION            0
            110 POP_TOP             
            111 LOAD_CONST               0 (None)
            114 RETURN_VALUE        
None

Here, it seems like the print is called and it appears in the disassembler output. So does this indicate that the import triggered a reread from disk? A reread that wasn't initiated by simply calling the function. Is my intuition here correct?

martineau
  • 119,623
  • 25
  • 170
  • 301
  • A re-read? When did you originaly import `temp.py`? – juanpa.arrivillaga Feb 02 '18 at 01:31
  • Apologies, the function is called temp.py so no original import was necessary – Peter Barrett Bryan Feb 02 '18 at 01:34
  • 1
    Well anyway, yes, your function wouldn't do anything that could possible cause python to re-read and load a module. I've never even considered a case of a module importing itself, so I'll wait for those with more arcane knowledge than myself.. – juanpa.arrivillaga Feb 02 '18 at 01:35
  • Yeah it is obviously a super edge case. This is more just a curiosity about language behavior :) – Peter Barrett Bryan Feb 02 '18 at 01:36
  • 1
    So, in general, if you do `import module` then somewhere else `import module` again, Python will not actually re-load the module. However, when it *imports* itself, it does do this, I tried it with a simple example temp.py: `print('hello'); import temp`. Likely, this occurs because the module's namespace is `__main__` when it is loaded using `python module.py`, then it is re-loaded when it imports itself as `temp`. There is no infinite recursion because it doesn't keep re-loading once it hits `import temp` again. – juanpa.arrivillaga Feb 02 '18 at 01:38
  • 1
    But modifying source code files dynamically will never result in those files being reloaded. To re-load a file explicitely, you need to use `import importlib; importlib.reload()` – juanpa.arrivillaga Feb 02 '18 at 01:39
  • Super funky. So this is a behavior that is limited to exclusively when a module imports itself? I'm surprised by this implicit reload behavior. Bonkers – Peter Barrett Bryan Feb 02 '18 at 01:39
  • 1
    It only happens once. Try to modify the function again, then use `import temp` again, I don't think you'll see any further modifications, because the import system recognizes `temp` as already loaded. – juanpa.arrivillaga Feb 02 '18 at 01:42
  • The function is not modified dynamically. In fact there is nothing special about the function (you can use `print('hello')` as mentioned above and you will get the same behaviour – nicolas Feb 02 '18 at 01:43
  • So conditions that would trigger a reload from disk: import (but only the first time) or an explicit reload (import importlib; importlib.reload()). Any others? – Peter Barrett Bryan Feb 02 '18 at 01:45
  • The only interesting/confusing points here are a duplicate of [Importing modules: \_\_main\_\_ vs import as module](https://stackoverflow.com/questions/13181559/importing-modules-main-vs-import-as-module) – Davis Herring Feb 02 '18 at 01:55
  • Maybe it is redundant, but the answer to that question didn't fully answer my curiosities. Maybe I was simply being ignorant. In any case, I learned a lot from @juanpa.arrivillaga and I'd be happy to accept an answer of that form :) – Peter Barrett Bryan Feb 02 '18 at 03:07

1 Answers1

1

You are loading your module twice. That's not normally possible, since Python caches module objects, but it can happen. This time it occurs because the first time you're loading it as the main module, named __main__. The second time you load it as its normal name temp. The second time, you'll see the results of the modifications the module made to its own file prior to the import that loaded again.

After a module is imported, changes to the file it was loaded from won't be reflected in the module's code, which was all compiled at the time the module was loaded. Such changes might confuse some debugging tools into reading the wrong version of the source and misreporting error locations and other details in some situations, but they won't have any effect on how the code runs.

You can also use the reload function (from importlib in modern versions of Python, a builtin in Python 2) to reload a module on demand. The contents of the new version of the module will overwrite the original version. Note though that any external references to objects from the old version of the module will remain pointing to the same old objects.

Blckknght
  • 100,903
  • 11
  • 120
  • 169