In Python, just as in SML, or (modern) Lisp, the body of a function is evaluated in the environment where it was defined. So, all three languages are lexically scoped.
In Python and Lisp, environments are mutable. That is, you can assign a new value to an existing variable, and that mutates the environment the variable is part of. Any functions defined within that environment will be evaluated in that environment—which means they will see the new value of the variable.
In SML, environments are not mutable; the environment can't change, there is no new value, so there's no question of whether the function will see that new value.
The syntax can be a bit misleading. In ML, val x = 1
and val x = 10
both define a brand new variable. In Python, x = 1
and x = 10
are assignment statements—they reassign to an existing variable, only defining a new one if there wasn't one of that name yet. (You don't see this in Lisp, where, e.g., let
and setq
are pretty hard to confuse.)
By the way, a closure with mutable variables is functionally equivalent to a mutable object (in the OO sense), so this feature of Lisp (and Python) has traditionally been pretty important.
As a side note, Python actually has slightly special rules for the global namespace (and the builtins one above it), so you could argue that the code in your example technically isn't relying on lexical scoping. But if you put the whole thing inside a function and call that function, then it definitely is an example of lexical scoping, so the global issue really isn't that important here.