From what I can find, the short answer is:
No, normally the Python interpreter does not recognize changes to a file once that file has been parsed, analyzed, and fed into the interpreter.
What you should do instead apparently is use your .py file as a module, import that as a module into another .py file, then run that new file. This allows your first file to be reloaded through the interactive interpreter. Here's an example:
from importlib import reload  # Python 3.4+ only.
import foo
while True:
    # Do some things.
    if is_changed(foo):
        foo = reload(foo)
I am still a little fuzzy on the details, but maybe someone can help fill those in. As far as I can tell from the sources I linked below, the interpreter basically takes some steps to load your program from the saved python file into memory (glossing over a lot of details). Once this process has been performed, the interpreter does not perform it again unless you explicitly ask it to do so, for example by using the importlib's reload() function to again perform the process.
Sources:
How do I unload (reload) a Python module? (quoted above)
A Python Interpreter Written in Python:
This link has a lot more information about how the interpreter works, and I found this section particularly helpful:
Real Python Bytecode
  At this point, we'll abandon our toy instruction
  sets and switch to real Python bytecode. The structure of bytecode is
  similar to our toy interpreter's verbose instruction sets, except that
  it uses one byte instead of a long name to identify each instruction.
  To understand this structure, we'll walk through the bytecode of a
  short function. Consider the example below:
>>> def cond():  
...     x = 3  
...     if x < 5:  
...         return 'yes'  
...     else:  
...         return 'no'  
...  
Python exposes a boatload of its internals at run time, and we can access them right
  from the REPL. For the function object cond, cond.code is the code
  object associated it, and cond.code.co_code is the bytecode.
  There's almost never a good reason to use these attributes directly
  when you're writing Python code, but they do allow us to get up to all
  sorts of mischief—and to look at the internals in order to understand
  them.
>>> cond.__code__.co_code  # the bytecode as raw bytes  
 b'd\x01\x00}\x00\x00|\x00\x00d\x02\x00k\x00\x00r\x16\x00d\x03\x00Sd\x04\x00Sd\x00\x00S'
>>> list(cond.__code__.co_code)  # the bytecode as numbers  
[100, 1, 0, 125, 0, 0, 124, 0, 0, 100, 2, 0, 107, 0, 0, 114, 22, 0, 100, 3, 0, 83,
100, 4, 0, 83, 100, 0, 0, 83]  
When we just print the bytecode, it
  looks unintelligible—all we can tell is that it's a series of bytes.
  Luckily, there's a powerful tool we can use to understand it: the dis
  module in the Python standard library.
dis is a bytecode disassembler. A disassembler takes low-level code
  that is written for machines, like assembly code or bytecode, and
  prints it in a human-readable way. When we run dis.dis, it outputs an
  explanation of the bytecode it has passed.
>>> dis.dis(cond)   
  2           0 LOAD_CONST               1 (3)
              3 STORE_FAST               0 (x)
  3           6 LOAD_FAST                0 (x)
              9 LOAD_CONST               2 (5)
             12 COMPARE_OP               0 (<)
             15 POP_JUMP_IF_FALSE       22
  4          18 LOAD_CONST               3 ('yes')
             21 RETURN_VALUE
  6     >>   22 LOAD_CONST               4 ('no')
             25 RETURN_VALUE
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE  
What does all this mean? Let's look at the first instruction LOAD_CONST as an example. The number in the
  first column (2) shows the line number in our Python source code. The
  second column is an index into the bytecode, telling us that the
  LOAD_CONST instruction appears at position zero. The third column is
  the instruction itself, mapped to its human-readable name. The fourth
  column, when present, is the argument to that instruction. The fifth
  column, when present, is a hint about what the argument means.
How does the Python Runtime actually work?:  
With Python, it uses an interpreter rather than a compiler. An
  interpreter works in exactly the same way as a compiler, with one
  difference: instead of code generation, it loads the output in-memory
  and executes it directly on your system. (The exact details of how
  this happens can vary wildly between different languages and different
  interpreters.)
importlib — The implementation of import:  
When reload() is executed:
Python module’s code is recompiled and the module-level code
  re-executed, defining a new set of objects which are bound to names in
  the module’s dictionary by reusing the loader which originally loaded
  the module. The init function of extension modules is not called a
  second time.
Again, please let me know if I need to edit this answer to follow etiquette.