Python closures work exactly like this.
All free variables (i.e. variables which are neither local vars, nor arguments) are saved in the internal __closure__ property.
In [1394]: def make_counter():
...: count = 0
...: def counter():
...: nonlocal count
...: count += 1
...: return count
...: return counter
...:
In [1395]: f = make_counter()
In [1396]: f
Out[1396]: <function __main__.make_counter.<locals>.counter>
In [1397]: f.__closure__
Out[1397]: (<cell at 0x10af4bd98: int object at 0x10024ac20>,)
In [1399]: f.__closure__[0].cell_contents
Out[1399]: 0
It just so happens that, in the first case, ints are immutable, so you are not modifying the original count.
When f is called, the inner function has its own environment frame is created which contains local variable count and is merged with __closure__.
Try calling f, and the counter is incremented:
In [1400]: f()
Out[1400]: 1
In [1401]: f.__closure__[0].cell_contents
Out[1401]: 1
In the latter case, you're modifying an entry in the list, which is mutable. The reference is copied into the __closure__ property.
By the way, make_counter is a global function, and so has an empty __closure__ attribute:
In [1403]: print(make_counter.__closure__)
None
Your second example, modified a bit:
In [1404]: def make_counter():
...: count = [0]
...: print(id(count))
...: def counter():
...: nonlocal count
...: count[0] += 1
...: return count[0]
...: return counter
...:
In [1405]: f = make_counter()
4495587464
In [1408]: id(f.__closure__[0].cell_contents)
Out[1408]: 4495587464
The id is identical.