How it Works

The basic idea of reinteract is that reinteract saves a copy of the scope dictionary after every statement. If it needs to recompile starting from a particular statement, it then has the right scope to execute that statement in.

# scope: { a: 1, l: [1,2,3,4] }
b = a + 1
# scope: { a: 1, b: 2, l: [1,2,3,4] }

This is pretty efficient because variables that stay the same can be shared between successive scopes, so the only memory used is for the scope dictionaries themselves. However, if reinteract detects that a statement modifies a variable, then it makes a shallow copy of the variable using copy.copy()

# scope: { a: 1, l: [1,2,3,4] }}
# reinteract does scope['l'] = copy.copy(scope['l'])
l[0] = 5
# scope: { a: 1, l: [5,2,3,4] }

Some types of modification are easy to detect, like the slice assignment above or assigning to an attribute. However, some modifications are harder to detect. Compare:

l.append(2)    # modifies l
l.count(2)     # no modification

The rule that reinteract uses is that a bare method call like the ones above is probably a modification, and it conservatively makes a copy in that case. You can avoid a copy by using a temporary variable or explicitly printing the result.

print l.count(2)    # no copy
1
c = l.count(2);     # no copy