Skip to content Skip to sidebar Skip to footer

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

Solution 1:

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.

defadder(base=0):  # factory function returns a new, parameterised functiondefadd(x):
        return base + x  # inner function is implicitly parameterised by basereturn add

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.

>>> some_name = 42>>> defask():
... print("the answer is", some_name)
...     some_name = 13
...
>>> ask()
UnboundLocalError: local variable 'some_name' referenced before assignment

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:

import contextlib
import threading


classDynamicScope(threading.local):  # instance data is local to each thread"""Dynamic scope that supports assignment via a context manager"""def__init__(self):
        super().__setattr__('_scopes', [])  # keep stack of scopes    @contextlib.contextmanager  # a context enforces pairs of set/unset operationsdefassign(self, **names):
        self._scopes.append(names)  # push new assignments to stackyield self                  # suspend to allow calling other functions
        self._scopes.pop()          # clear new assignments from stackdef__getattr__(self, item):
        for sub_scope inreversed(self._scopes):  # linearly search through scopestry:
                return sub_scope[item]
            except KeyError:
                passraise NameError(f"name {item!r} not dynamically defined")

    def__setattr__(self, key, value):
        raise TypeError(f'{self.__class__.__name__!r} does not support assignment')

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.

scope = DynamicScope()

defprint_answer():
    print(scope.answer)  # read from scope and hope something is assigneddefguess_answer():
    # assign to scope before calling function that uses the scopewith scope.assign(answer=42):
        print_answer()

with scope.assign(answer=13):
    print_answer()  # 13
    guess_answer()  # 42
    print_answer()  # 13
print_answer()      # NameError: name 'answer' not dynamically defined

Solution 2:

Static (Early) and Dynamic (Late) Binding :

Binding refers to the association of names in program text to the storage locations to which they refer. In static binding, this association is predetermined at build time. With dynamic binding, this association is not determined until run-time.

Dynamic binding is the binding which happens in Python. This means that the Python interpreter does binding only as code runs. For example -

>>>ifFalse:...    x  # This line never runs, so no error is raised...else:...1 + 2...
3
>>>

Advantages of dynamic binding

  • Main advantage of dynamic type binding is flexibility. It is more easy to write generic code.
    • Ex - a program to process a list of data in a language that uses dynamic type binding can be written as a generic program.

Disadvantages of dynamic binding

  • Error detection capability of the compiler is diminished. some errors that a compiler could have caught.
  • considerable overhead at runtime.

Post a Comment for "Loose Late Binding V. Strict Late Binding"