{ "cells": [ { "cell_type": "markdown", "id": "3cdca540", "metadata": {}, "source": [ "\n", "<a id='oop-names'></a>\n", "<div id=\"qe-notebook-header\" align=\"right\" style=\"text-align:right;\">\n", " <a href=\"https://quantecon.org/\" title=\"quantecon.org\">\n", " <img style=\"width:250px;display:inline;\" width=\"250px\" src=\"https://assets.quantecon.org/img/qe-menubar-logo.svg\" alt=\"QuantEcon\">\n", " </a>\n", "</div>" ] }, { "cell_type": "markdown", "id": "eed32bf0", "metadata": {}, "source": [ "# Names and Namespaces" ] }, { "cell_type": "markdown", "id": "b1e813c1", "metadata": {}, "source": [ "## Overview\n", "\n", "This lecture is all about variable names, how they can be used and how they are\n", "understood by the Python interpreter.\n", "\n", "This might sound a little dull but the model that Python has adopted for\n", "handling names is elegant and interesting.\n", "\n", "In addition, you will save yourself many hours of debugging if you have a good\n", "understanding of how names work in Python.\n", "\n", "\n", "<a id='var-names'></a>" ] }, { "cell_type": "markdown", "id": "c6a8e307", "metadata": {}, "source": [ "## Variable Names in Python\n", "\n", "\n", "<a id='index-0'></a>\n", "Consider the Python statement" ] }, { "cell_type": "code", "execution_count": null, "id": "eef0fa19", "metadata": { "hide-output": false }, "outputs": [], "source": [ "x = 42" ] }, { "cell_type": "markdown", "id": "5525aac5", "metadata": {}, "source": [ "We now know that when this statement is executed, Python creates an object of\n", "type `int` in your computer’s memory, containing\n", "\n", "- the value `42` \n", "- some associated attributes \n", "\n", "\n", "But what is `x` itself?\n", "\n", "In Python, `x` is called a **name**, and the statement `x = 42` **binds** the name `x` to the integer object we have just discussed.\n", "\n", "Under the hood, this process of binding names to objects is implemented as a dictionary—more about this in a moment.\n", "\n", "There is no problem binding two or more names to the one object, regardless of what that object is" ] }, { "cell_type": "code", "execution_count": null, "id": "d5e68eb8", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def f(string): # Create a function called f\n", " print(string) # that prints any string it's passed\n", "\n", "g = f\n", "id(g) == id(f)" ] }, { "cell_type": "code", "execution_count": null, "id": "e84bc8c2", "metadata": { "hide-output": false }, "outputs": [], "source": [ "g('test')" ] }, { "cell_type": "markdown", "id": "68db6753", "metadata": {}, "source": [ "In the first step, a function object is created, and the name `f` is bound to it.\n", "\n", "After binding the name `g` to the same object, we can use it anywhere we would use `f`.\n", "\n", "What happens when the number of names bound to an object goes to zero?\n", "\n", "Here’s an example of this situation, where the name `x` is first bound to one object and then **rebound** to another" ] }, { "cell_type": "code", "execution_count": null, "id": "d4277bc8", "metadata": { "hide-output": false }, "outputs": [], "source": [ "x = 'foo'\n", "id(x)\n", "x = 'bar' \n", "id(x)" ] }, { "cell_type": "markdown", "id": "fa3073aa", "metadata": {}, "source": [ "In this case, after we rebind `x` to `'bar'`, no names bound are to the first object `'foo'`.\n", "\n", "This is a trigger for `'foo'` to be garbage collected.\n", "\n", "In other words, the memory slot that stores that object is deallocated and returned to the operating system.\n", "\n", "Garbage collection is actually an active research area in computer science.\n", "\n", "You can [read more on garbage collection](https://rushter.com/blog/python-garbage-collector/) if you are interested." ] }, { "cell_type": "markdown", "id": "a6bb17d1", "metadata": {}, "source": [ "## Namespaces\n", "\n", "\n", "<a id='index-1'></a>\n", "Recall from the preceding discussion that the statement" ] }, { "cell_type": "code", "execution_count": null, "id": "cfdcf7a0", "metadata": { "hide-output": false }, "outputs": [], "source": [ "x = 42" ] }, { "cell_type": "markdown", "id": "99b19dcb", "metadata": {}, "source": [ "binds the name `x` to the integer object on the right-hand side.\n", "\n", "We also mentioned that this process of binding `x` to the correct object is implemented as a dictionary.\n", "\n", "This dictionary is called a namespace." ] }, { "cell_type": "markdown", "id": "08a8d928", "metadata": {}, "source": [ "## Definition\n", "\n", "A **namespace** is a symbol table that maps names to objects in memory.\n", "\n", "Python uses multiple namespaces, creating them on the fly as necessary.\n", "\n", "For example, every time we import a module, Python creates a namespace for that module.\n", "\n", "To see this in action, suppose we write a script `mathfoo.py` with a single line" ] }, { "cell_type": "code", "execution_count": null, "id": "a31841e1", "metadata": { "hide-output": false }, "outputs": [], "source": [ "%%file mathfoo.py\n", "pi = 'foobar'" ] }, { "cell_type": "markdown", "id": "a71441ca", "metadata": {}, "source": [ "Now we start the Python interpreter and import it" ] }, { "cell_type": "code", "execution_count": null, "id": "5eea6db1", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import mathfoo" ] }, { "cell_type": "markdown", "id": "118fbf72", "metadata": {}, "source": [ "Next let’s import the `math` module from the standard library" ] }, { "cell_type": "code", "execution_count": null, "id": "77bfa51b", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import math" ] }, { "cell_type": "markdown", "id": "d4f7130c", "metadata": {}, "source": [ "Both of these modules have an attribute called `pi`" ] }, { "cell_type": "code", "execution_count": null, "id": "7198fc82", "metadata": { "hide-output": false }, "outputs": [], "source": [ "math.pi" ] }, { "cell_type": "code", "execution_count": null, "id": "21972e84", "metadata": { "hide-output": false }, "outputs": [], "source": [ "mathfoo.pi" ] }, { "cell_type": "markdown", "id": "cd852de0", "metadata": {}, "source": [ "These two different bindings of `pi` exist in different namespaces, each one implemented as a dictionary.\n", "\n", "If you wish, you can look at the dictionary directly, using `module_name.__dict__`." ] }, { "cell_type": "code", "execution_count": null, "id": "385ef6d8", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import math\n", "\n", "math.__dict__.items()" ] }, { "cell_type": "code", "execution_count": null, "id": "47837af1", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import mathfoo\n", "\n", "mathfoo.__dict__" ] }, { "cell_type": "markdown", "id": "058138fa", "metadata": {}, "source": [ "As you know, we access elements of the namespace using the dotted attribute notation" ] }, { "cell_type": "code", "execution_count": null, "id": "d4991855", "metadata": { "hide-output": false }, "outputs": [], "source": [ "math.pi" ] }, { "cell_type": "markdown", "id": "12b45e84", "metadata": {}, "source": [ "This is entirely equivalent to `math.__dict__['pi']`" ] }, { "cell_type": "code", "execution_count": null, "id": "9c1ce569", "metadata": { "hide-output": false }, "outputs": [], "source": [ "math.__dict__['pi'] " ] }, { "cell_type": "markdown", "id": "70dcbf9b", "metadata": {}, "source": [ "## Viewing Namespaces\n", "\n", "As we saw above, the `math` namespace can be printed by typing `math.__dict__`.\n", "\n", "Another way to see its contents is to type `vars(math)`" ] }, { "cell_type": "code", "execution_count": null, "id": "b3c50430", "metadata": { "hide-output": false }, "outputs": [], "source": [ "vars(math).items()" ] }, { "cell_type": "markdown", "id": "6e7c935c", "metadata": {}, "source": [ "If you just want to see the names, you can type" ] }, { "cell_type": "code", "execution_count": null, "id": "bacb5ed0", "metadata": { "hide-output": false }, "outputs": [], "source": [ "# Show the first 10 names\n", "dir(math)[0:10]" ] }, { "cell_type": "markdown", "id": "4e76f058", "metadata": {}, "source": [ "Notice the special names `__doc__` and `__name__`.\n", "\n", "These are initialized in the namespace when any module is imported\n", "\n", "- `__doc__` is the doc string of the module \n", "- `__name__` is the name of the module " ] }, { "cell_type": "code", "execution_count": null, "id": "57382522", "metadata": { "hide-output": false }, "outputs": [], "source": [ "print(math.__doc__)" ] }, { "cell_type": "code", "execution_count": null, "id": "88c9e32e", "metadata": { "hide-output": false }, "outputs": [], "source": [ "math.__name__" ] }, { "cell_type": "markdown", "id": "08001344", "metadata": {}, "source": [ "## Interactive Sessions\n", "\n", "\n", "<a id='index-2'></a>\n", "In Python, **all** code executed by the interpreter runs in some module.\n", "\n", "What about commands typed at the prompt?\n", "\n", "These are also regarded as being executed within a module — in this case, a module called `__main__`.\n", "\n", "To check this, we can look at the current module name via the value of `__name__` given at the prompt" ] }, { "cell_type": "code", "execution_count": null, "id": "3037120c", "metadata": { "hide-output": false }, "outputs": [], "source": [ "print(__name__)" ] }, { "cell_type": "markdown", "id": "0fc41da3", "metadata": {}, "source": [ "When we run a script using IPython’s `run` command, the contents of the file are executed as part of `__main__` too.\n", "\n", "To see this, let’s create a file `mod.py` that prints its own `__name__` attribute" ] }, { "cell_type": "code", "execution_count": null, "id": "719b4c6a", "metadata": { "hide-output": false }, "outputs": [], "source": [ "%%file mod.py\n", "print(__name__)" ] }, { "cell_type": "markdown", "id": "4980f6b7", "metadata": {}, "source": [ "Now let’s look at two different ways of running it in IPython" ] }, { "cell_type": "code", "execution_count": null, "id": "cf67fbb4", "metadata": { "hide-output": false }, "outputs": [], "source": [ "import mod # Standard import" ] }, { "cell_type": "code", "execution_count": null, "id": "5b54dd90", "metadata": { "hide-output": false }, "outputs": [], "source": [ "%run mod.py # Run interactively" ] }, { "cell_type": "markdown", "id": "23a6747e", "metadata": {}, "source": [ "In the second case, the code is executed as part of `__main__`, so `__name__` is equal to `__main__`.\n", "\n", "To see the contents of the namespace of `__main__` we use `vars()` rather than `vars(__main__)`.\n", "\n", "If you do this in IPython, you will see a whole lot of variables that IPython\n", "needs, and has initialized when you started up your session.\n", "\n", "If you prefer to see only the variables you have initialized, use `%whos`" ] }, { "cell_type": "code", "execution_count": null, "id": "7d05e4cc", "metadata": { "hide-output": false }, "outputs": [], "source": [ "x = 2\n", "y = 3\n", "\n", "import numpy as np\n", "\n", "%whos" ] }, { "cell_type": "markdown", "id": "0a3fadc7", "metadata": {}, "source": [ "## The Global Namespace\n", "\n", "\n", "<a id='index-3'></a>\n", "Python documentation often makes reference to the “global namespace”.\n", "\n", "The global namespace is *the namespace of the module currently being executed*.\n", "\n", "For example, suppose that we start the interpreter and begin making assignments.\n", "\n", "We are now working in the module `__main__`, and hence the namespace for `__main__` is the global namespace.\n", "\n", "Next, we import a module called `amodule`" ] }, { "cell_type": "markdown", "id": "b673e75f", "metadata": { "hide-output": false }, "source": [ "```python3\n", "import amodule\n", "```\n" ] }, { "cell_type": "markdown", "id": "66992069", "metadata": {}, "source": [ "At this point, the interpreter creates a namespace for the module `amodule` and starts executing commands in the module.\n", "\n", "While this occurs, the namespace `amodule.__dict__` is the global namespace.\n", "\n", "Once execution of the module finishes, the interpreter returns to the module from where the import statement was made.\n", "\n", "In this case it’s `__main__`, so the namespace of `__main__` again becomes the global namespace." ] }, { "cell_type": "markdown", "id": "c5df7338", "metadata": {}, "source": [ "## Local Namespaces\n", "\n", "\n", "<a id='index-4'></a>\n", "Important fact: When we call a function, the interpreter creates a *local namespace* for that function, and registers the variables in that namespace.\n", "\n", "The reason for this will be explained in just a moment.\n", "\n", "Variables in the local namespace are called *local variables*.\n", "\n", "After the function returns, the namespace is deallocated and lost.\n", "\n", "While the function is executing, we can view the contents of the local namespace with `locals()`.\n", "\n", "For example, consider" ] }, { "cell_type": "code", "execution_count": null, "id": "8d378794", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def f(x):\n", " a = 2\n", " print(locals())\n", " return a * x" ] }, { "cell_type": "markdown", "id": "f605077b", "metadata": {}, "source": [ "Now let’s call the function" ] }, { "cell_type": "code", "execution_count": null, "id": "066116a1", "metadata": { "hide-output": false }, "outputs": [], "source": [ "f(1)" ] }, { "cell_type": "markdown", "id": "df4f9629", "metadata": {}, "source": [ "You can see the local namespace of `f` before it is destroyed." ] }, { "cell_type": "markdown", "id": "0c76a1f3", "metadata": {}, "source": [ "## The `__builtins__` Namespace\n", "\n", "\n", "<a id='index-5'></a>\n", "We have been using various built-in functions, such as `max(), dir(), str(), list(), len(), range(), type()`, etc.\n", "\n", "How does access to these names work?\n", "\n", "- These definitions are stored in a module called `__builtin__`. \n", "- They have their own namespace called `__builtins__`. " ] }, { "cell_type": "code", "execution_count": null, "id": "b3ae453d", "metadata": { "hide-output": false }, "outputs": [], "source": [ "# Show the first 10 names in `__main__`\n", "dir()[0:10]" ] }, { "cell_type": "code", "execution_count": null, "id": "f920600d", "metadata": { "hide-output": false }, "outputs": [], "source": [ "# Show the first 10 names in `__builtins__`\n", "dir(__builtins__)[0:10]" ] }, { "cell_type": "markdown", "id": "bb3c96ad", "metadata": {}, "source": [ "We can access elements of the namespace as follows" ] }, { "cell_type": "code", "execution_count": null, "id": "db4007ca", "metadata": { "hide-output": false }, "outputs": [], "source": [ "__builtins__.max" ] }, { "cell_type": "markdown", "id": "62fe2dfc", "metadata": {}, "source": [ "But `__builtins__` is special, because we can always access them directly as well" ] }, { "cell_type": "code", "execution_count": null, "id": "ed2ce250", "metadata": { "hide-output": false }, "outputs": [], "source": [ "max" ] }, { "cell_type": "code", "execution_count": null, "id": "76c86886", "metadata": { "hide-output": false }, "outputs": [], "source": [ "__builtins__.max == max" ] }, { "cell_type": "markdown", "id": "756125cb", "metadata": {}, "source": [ "The next section explains how this works …" ] }, { "cell_type": "markdown", "id": "2cfc4ae4", "metadata": {}, "source": [ "## Name Resolution\n", "\n", "\n", "<a id='index-6'></a>\n", "Namespaces are great because they help us organize variable names.\n", "\n", "(Type `import this` at the prompt and look at the last item that’s printed)\n", "\n", "However, we do need to understand how the Python interpreter works with multiple namespaces.\n", "\n", "Understanding the flow of execution will help us to check which variables are in scope and how to operate on them when writing and debugging programs.\n", "\n", "At any point of execution, there are in fact at least two namespaces that can be accessed directly.\n", "\n", "(“Accessed directly” means without using a dot, as in `pi` rather than `math.pi`)\n", "\n", "These namespaces are\n", "\n", "- The global namespace (of the module being executed) \n", "- The builtin namespace \n", "\n", "\n", "If the interpreter is executing a function, then the directly accessible namespaces are\n", "\n", "- The local namespace of the function \n", "- The global namespace (of the module being executed) \n", "- The builtin namespace \n", "\n", "\n", "Sometimes functions are defined within other functions, like so" ] }, { "cell_type": "code", "execution_count": null, "id": "1b593709", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def f():\n", " a = 2\n", " def g():\n", " b = 4\n", " print(a * b)\n", " g()" ] }, { "cell_type": "markdown", "id": "607d3185", "metadata": {}, "source": [ "Here `f` is the *enclosing function* for `g`, and each function gets its\n", "own namespaces.\n", "\n", "Now we can give the rule for how namespace resolution works:\n", "\n", "The order in which the interpreter searches for names is\n", "\n", "1. the local namespace (if it exists) \n", "1. the hierarchy of enclosing namespaces (if they exist) \n", "1. the global namespace \n", "1. the builtin namespace \n", "\n", "\n", "If the name is not in any of these namespaces, the interpreter raises a `NameError`.\n", "\n", "This is called the **LEGB rule** (local, enclosing, global, builtin).\n", "\n", "Here’s an example that helps to illustrate.\n", "\n", "Visualizations here are created by [nbtutor](https://github.com/lgpage/nbtutor) in a Jupyter notebook.\n", "\n", "They can help you better understand your program when you are learning a new language.\n", "\n", "Consider a script `test.py` that looks as follows" ] }, { "cell_type": "code", "execution_count": null, "id": "9bf3691a", "metadata": { "hide-output": false }, "outputs": [], "source": [ "%%file test.py\n", "def g(x):\n", " a = 1\n", " x = x + a\n", " return x\n", "\n", "a = 0\n", "y = g(10)\n", "print(\"a = \", a, \"y = \", y)" ] }, { "cell_type": "markdown", "id": "48939c0e", "metadata": {}, "source": [ "What happens when we run this script?" ] }, { "cell_type": "code", "execution_count": null, "id": "8f1cc58b", "metadata": { "hide-output": false }, "outputs": [], "source": [ "%run test.py" ] }, { "cell_type": "markdown", "id": "4351e22f", "metadata": {}, "source": [ "First,\n", "\n", "- The global namespace `{}` is created. \n", "\n", "\n", "\n", "\n", " \n", "- The function object is created, and `g` is bound to it within the global namespace. \n", "- The name `a` is bound to `0`, again in the global namespace. \n", "\n", "\n", "\n", "\n", " \n", "Next `g` is called via `y = g(10)`, leading to the following sequence of actions\n", "\n", "- The local namespace for the function is created. \n", "- Local names `x` and `a` are bound, so that the local namespace becomes `{'x': 10, 'a': 1}`. \n", "\n", "\n", "Note that the global `a` was not affected by the local `a`.\n", "\n", "\n", "\n", " \n", "- Statement `x = x + a` uses the local `a` and local `x` to compute `x + a`, and binds local name `x` to the result. \n", "- This value is returned, and `y` is bound to it in the global namespace. \n", "- Local `x` and `a` are discarded (and the local namespace is deallocated). \n", "\n", "\n", "\n", "\n", " \n", "\n", "<a id='mutable-vs-immutable'></a>" ] }, { "cell_type": "markdown", "id": "6703abeb", "metadata": {}, "source": [ "### Mutable Versus Immutable Parameters\n", "\n", "This is a good time to say a little more about mutable vs immutable objects.\n", "\n", "Consider the code segment" ] }, { "cell_type": "code", "execution_count": null, "id": "5a074d31", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def f(x):\n", " x = x + 1\n", " return x\n", "\n", "x = 1\n", "print(f(x), x)" ] }, { "cell_type": "markdown", "id": "3ce8b0a6", "metadata": {}, "source": [ "We now understand what will happen here: The code prints `2` as the value of `f(x)` and `1` as the value of `x`.\n", "\n", "First `f` and `x` are registered in the global namespace.\n", "\n", "The call `f(x)` creates a local namespace and adds `x` to it, bound to `1`.\n", "\n", "Next, this local `x` is rebound to the new integer object `2`, and this value is returned.\n", "\n", "None of this affects the global `x`.\n", "\n", "However, it’s a different story when we use a **mutable** data type such as a list" ] }, { "cell_type": "code", "execution_count": null, "id": "aedfc268", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def f(x):\n", " x[0] = x[0] + 1\n", " return x\n", "\n", "x = [1]\n", "print(f(x), x)" ] }, { "cell_type": "markdown", "id": "133d97b2", "metadata": {}, "source": [ "This prints `[2]` as the value of `f(x)` and *same* for `x`.\n", "\n", "Here’s what happens\n", "\n", "- `f` is registered as a function in the global namespace \n", "\n", "\n", "\n", "\n", " \n", "- `x` is bound to `[1]` in the global namespace \n", "\n", "\n", "\n", "\n", " \n", "- The call `f(x)` \n", " - Creates a local namespace \n", " - Adds `x` to the local namespace, bound to `[1]` \n", "\n", "\n", "\n", "\n", " \n", ">**Note**\n", ">\n", ">The global `x` and the local `x` refer to the same `[1]`\n", "\n", "We can see the identity of local `x` and the identity of global `x` are the same" ] }, { "cell_type": "code", "execution_count": null, "id": "dd340849", "metadata": { "hide-output": false }, "outputs": [], "source": [ "def f(x):\n", " x[0] = x[0] + 1\n", " print(f'the identity of local x is {id(x)}')\n", " return x\n", "\n", "x = [1]\n", "print(f'the identity of global x is {id(x)}')\n", "print(f(x), x)" ] }, { "cell_type": "markdown", "id": "b1cecd78", "metadata": {}, "source": [ "- Within `f(x)` \n", " - The list `[1]` is modified to `[2]` \n", " - Returns the list `[2]` \n", "\n", "\n", "\n", "\n", " \n", "- The local namespace is deallocated, and the local `x` is lost \n", "\n", "\n", "\n", "\n", " \n", "If you want to modify the local `x` and the global `x` separately, you can create a [*copy*](https://docs.python.org/3/library/copy.html) of the list and assign the copy to the local `x`.\n", "\n", "We will leave this for you to explore." ] } ], "metadata": { "date": 1755222216.5282357, "filename": "names.md", "kernelspec": { "display_name": "Python", "language": "python3", "name": "python3" }, "title": "Names and Namespaces" }, "nbformat": 4, "nbformat_minor": 5 }