Skip to content
Advertisement

Loose late binding v. strict late binding

While reading Python’s Execution model documentation, I realized that Python’s free variables do not seem to have a strict late binding property where a name binding occurring in any code block can be used for name resolution. Indeed, executing:

JavaScript

raises:

JavaScript

They have rather a loose late binding property where only a name binding occurring in an outer code block of the code block introducing the free variable can be used for name resolution. Indeed executing

JavaScript

prints:

JavaScript

What are the benefits and drawbacks of the loose late binding property compared to the strict late binding property?

Advertisement

Answer

This is generally known as dynamic scoping and static scoping. Roughly speaking, dynamic scoping determines scope by call nesting and static scoping determines scope by declaration nesting.

In general, dynamic scoping is very easy to implement for any language with a call stack – a name lookup simply searches the current stack linearly. In contrast, static scoping is more complex, requiring several distinct scopes with their own lifetime.

However, static scoping is generally easier to understand, since the scope of a variable never changes – a name lookup has to be resolved once and will always point to the same scope. In contrast, dynamic scoping is more brittle, with names being resolved in different or no scope when calling a function.


Python’s scoping rules are mainly defined by PEP 227 introducing nested scoping (“closures”) and PEP 3104 introducing writable nested scoping (nonlocal). The primary use-case of such static scoping is to allow higher-order functions (“function-producing-function”) to automatically parameterise inner functions; this is commonly used for callbacks, decorators or factory-functions.

JavaScript

Both PEPs codify how Python handles the complications of static scoping. In specific, scope is resolved once at compile time – every name is thereafter strictly either global, nonlocal or local. In return, static scoping allows to optimise variable access – variables are read either from a fast array of locals, an indirecting array of closure cells, or a slow global dictionary.

An artefact of this statically scoped name resolution is UnboundLocalError : a name may be scoped locally but not yet assigned locally. Even though there is some value assigned to the name somewhere, static scoping forbids accessing it.

JavaScript

Various means exist to circumvent this, but they all come down to the programmer having to explicitly define how to resolve a name.


While Python does not natively implement dynamic scoping, it can in be easily emulated. Since dynamic scoping is identical to a stack of scopes for each stack of calls, this can be implemented explicitly.

Python natively provides threading.local to contextualise a variable to each call stack. Similarly, contextvars allows to explicitly contextualise a variable – this is useful for e.g. async code which sidesteps the regular call stack. A naive dynamic scope for threads can be built as a literal scope stack that is thread local:

JavaScript

This allows to globally define a dynamic scope, to which a name can be assigned for a restricted duration. Assigned names are automatically visible in called functions.

JavaScript
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement