\n"
]
},
{
"cell_type": "markdown",
"id": "0df0a1d4",
"metadata": {},
"source": [
"# >>> Introduction"
]
},
{
"cell_type": "markdown",
"id": "b29499dc",
"metadata": {},
"source": [
"### What is Python?\n",
"\n",
"Python is a programming language. \n",
"It is high-level, object-oriented, interpreted and portable.\n",
"\n",
"The focus is on versatility, simplicity, and readability so over time a rich ecosystem of libraries has emerged. \n",
"It is used extensively in:\n",
"- [complex architectures](https://www.djangoproject.com/download/)\n",
"- [web scraping](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)\n",
"- [Telegram bots](https://github.com/python-telegram-bot/python-telegram-bot)\n",
"- data science\n",
"- machine learning\n",
"- image analysis\n",
"- numerical analysis\n",
"- and more...\n",
"\n",
"### What is a programming language?\n",
"\n",
"A **programming language** is a written language used to program a computer.\n",
"\n",
"A **computer** is a machine that can calculate and process data automatically, and is universal for such tasks. \n",
"That is, it is a machine that can autonomously perform any computation that can be described. \n",
"A programming language is a language that allows to describe a computation so that it can be carried out by a computer.\n",
"\n",
"### Problem!\n",
"\n",
"Different computers are built and operate in various different ways. \n",
"Moreover, the elementary instructions they execute describe simple operations in terms of the machine's functionality, not in terms of the problems that are intended to be solved.\n",
"\n",
"A simplistic model for a computer is a processor that reads and executes elementary instructions and has access to a memory in which it can hold numbers. \n",
"However, the computations we are interested in may involve non-numeric data, and often these data are not individual but grouped into collections. \n",
"In order to trace such computations back to a set of elementary instructions, it is necessary to solve data encoding and memory management problems.\n",
"\n",
"Fortunately, reusable solutions exist for these problems. \n",
"These solutions make it possible to **abstract**, that is, to separate, or at least distance, the logic of the problem to be solved from the details of the machine’s functioning. \n",
"A programming language that allows a computation to be described in terms of these abstractions is said to be a **high-level language**.\n",
"\n",
"Python is a high-level language. \n",
"It allows non-numerical data and collections of data to be handled transparently.\n",
"\n",
"### How?\n",
"\n",
"Python is a (usually) interpreted language. \n",
"That is, a Python program is typically a text file that is passed to another program, the **interpreter**, which is executed directly. \n",
"The interpreter's task is to read the instructions and perform the computation described.\n",
"\n",
"This layer of indirection allows the problem of describing the abstractions at a low-level to be shifted to the interpreter’s implementation. \n",
"As high-level language users, implementation details are hidden to us and we can usually ignore them.\n",
"\n",
"On the other hand, indirection reduces efficiency and a program well written in a non-interpreted language usually runs faster.\n",
"\n",
"### When to write in Python\n",
"\n",
"It is a great tool for quickly writing something that works. \n",
"It is a relatively simple and expressive language so it is quick to learn and write.\n",
"\n",
"### When _not_ to write in Python\n",
"\n",
"When you need a solution that runs very fast or when you have to be careful about the use of computational resources. \n",
"Other technologies are a good answer to that type of problem.\n",
"\n",
"This disadvantage is reduced by the fact that libraries written in C and C++ can be used in Python. \n",
"Python can therefore still play a role as a glue for parts written in different programming languages: parts for which efficiency is paramount.\n",
"\n",
"Being Python a very expressive language, it becomes difficult to guarantee that a very long program written in Python will do what it is supposed to do. \n",
"Therefore, if correctness is an important factor, other technologies are maybe more suitable.\n",
"For example a language with an expressive type system such as OCaml or Rust, or purely declarative languages such as LISP or Prolog.\n",
"\n",
"### The interpreter\n",
"\n",
"The fact that Python is an interpreted language makes it possible to run a program in Python on any computer on which an interpreter is available. \n",
"Thus, a program in Python is **portable**.\n",
"\n",
"The interpreter has a command-line interface. It can be invoked from a terminal by typing `python` or `python3` followed by the name of the file in which the program to be executed is written.\n",
"\n",
"If invoked without arguments the interpreter enters **interactive mode**, in which it reads an expression, evaluates it, prints the result and repeats. \n",
"To exit interactive mode write `quit()` or press the keys ctrl + D.\n",
"\n",
"Interactive mode is also called **REPL** from\n",
"- Read\n",
"- Evaluate\n",
"- Print\n",
"- Loop \n",
"and it is excellent for exploring how the language works.\n",
"\n",
"A notebook can be used to put together a program while experimenting and tweaking. It is also a good solution for interactive presentations. \n",
"A **notebook** is a file that contains text, images and parts of code and it is manipulated by means of a program with a graphic interface. \n",
"An example of such a program is Jupyter, which displays the notebook in a web page.\n",
"\n",
"A notebook is divided into cells and code cells can be run individually. \n",
"Once a cell has been run, the result is shown below it."
]
},
{
"cell_type": "markdown",
"id": "43edbb6e",
"metadata": {},
"source": [
"# >>> First guided trip"
]
},
{
"cell_type": "markdown",
"id": "c4d884dc",
"metadata": {},
"source": [
"Let's start exploring in a Python REPL or in a notebook.\n",
"\n",
"This is a notebook, here a code cell can be run pressing ctrl + enter (shift + enter runs the cell and moves to the next one).\n",
"The code can also be copy-pasted in a REPL to be executed."
]
},
{
"cell_type": "markdown",
"id": "1265f1fe",
"metadata": {},
"source": [
"## Comments"
]
},
{
"cell_type": "markdown",
"id": "c5da1ea4",
"metadata": {},
"source": [
"Anything after an `#` is a **comment**, until the line ends"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "67ffca57",
"metadata": {},
"outputs": [],
"source": [
"# this is a comment"
]
},
{
"cell_type": "markdown",
"id": "6712d49b",
"metadata": {},
"source": [
"## How to see the values"
]
},
{
"cell_type": "markdown",
"id": "30eaea90",
"metadata": {},
"source": [
"A REPL prints the value of each line containing an expression. \n",
"The same happens in a notebook with code blocks but only for the last meaningful line of the block.\n",
"\n",
"In any context to explicitly print the value of an expression one can use the funcion `print`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eeb42d1a",
"metadata": {},
"outputs": [],
"source": [
"\"expr 1\"\t# this is an expression; not displayed in a notebook\n",
"print(42)\n",
"print(\"Hello there\")\n",
"\"expr 2\""
]
},
{
"cell_type": "markdown",
"id": "634e429a",
"metadata": {},
"source": [
"If we prefer to print the **value representation**, not the value itself, we can define and use an ad hoc function. \n",
"We will ignore how that works, for the moment."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e008ec3c",
"metadata": {},
"outputs": [],
"source": [
"prir = lambda *xs, **kw: print(*map(repr, xs), **kw)\n",
"\n",
"prir(\"Hello there\")\n",
"prir(42)\n",
"\n",
"print(\"General Kenobi\")\n",
"42"
]
},
{
"cell_type": "markdown",
"id": "3439a662",
"metadata": {},
"source": [
"## Some compound expressions"
]
},
{
"cell_type": "markdown",
"id": "8d2327a9",
"metadata": {},
"source": [
"Some \"words\" are directly interpreted as data, those are called **literals**.\n",
"\n",
"For example:\n",
"- a sequence of digits gives a natural number\n",
"- a sequence of characters between single quotes `'`...`'` (or double quotes `\"`...`\"`) gives a **string**, i.e. a piece of text\n",
"- `True` and `False` give the truth values true and false respectively.\n",
"\n",
"Numbers, text and truth values are some types of data that can be easily represented in Python."
]
},
{
"cell_type": "markdown",
"id": "b2b11d8f",
"metadata": {},
"source": [
"A way to compose data in complex expressions is to use **operators**. \n",
"Some of those are:\n",
"- `+`: addition and string concatenation\n",
"- `-`: subtraction\n",
"- `*`: product (between a string and an integer repeats the string that many times)\n",
"- `/`: division\n",
"- `//`, `%`: integer division and reminder\n",
"- `**`: exponentiation.\n",
"\n",
"In a compound expression `(` and `)` force the **evaluation order** (as they do in arithmetic).\n",
"\n",
"With those operator a REPL can be used as a fancy calculator."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7615ca17",
"metadata": {},
"outputs": [],
"source": [
"1 + (2 * 3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a8050b3",
"metadata": {},
"outputs": [],
"source": [
"(1 + 2) * 3"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f1e9bc9d",
"metadata": {},
"outputs": [],
"source": [
"1 + 2 * 3 ** 4 / 5"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3a16e3b3",
"metadata": {},
"outputs": [],
"source": [
"5 // 3"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7290dc0",
"metadata": {},
"outputs": [],
"source": [
"5 % 3"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ed5d6e4a",
"metadata": {},
"outputs": [],
"source": [
"\"abc\" * 3"
]
},
{
"cell_type": "markdown",
"id": "76794649",
"metadata": {},
"source": [
"There are also comparison operators"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6ae8f635",
"metadata": {},
"outputs": [],
"source": [
"1 + 2 == 3"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bbd67c3e",
"metadata": {},
"outputs": [],
"source": [
"4 >= 5"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "52ad024a",
"metadata": {},
"outputs": [],
"source": [
"\"abc\" < \"z\""
]
},
{
"cell_type": "markdown",
"id": "ac37d3f8",
"metadata": {},
"source": [
"## Function calls and `None`"
]
},
{
"cell_type": "markdown",
"id": "8b1db08b",
"metadata": {},
"source": [
"We will use some functions defined in the language: those are names interpreted as a special kind of values: function objects.\n",
"\n",
"Function objects are **callable**, i.e. they can form a special kind of compound expressions. \n",
"An expression evaluated to a function object, followed by a comma separated list of expressions (even empty) enclosed by pharenteses is a **function call**. \n",
"Function calls are expressions and as such they have a value.\n",
"\n",
"We have seen the function `print` but it appears to not have a value when called! \n",
"That is because some functions, called **procedures**, _do stuff_ that is not related to a computation; that is the case for `print`. \n",
"The value of a function that has no significant value when called is, by convention, the value **None**.\n",
"\n",
"The value None is also the value of the expression given by the literal `None`. \n",
"Usually neither notebooks nor REPLs display the value None."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dba98042",
"metadata": {},
"outputs": [],
"source": [
"print"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "801852f4",
"metadata": {},
"outputs": [],
"source": [
"print(print)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "09748c3d",
"metadata": {},
"outputs": [],
"source": [
"print(\"test\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "74630a97",
"metadata": {},
"outputs": [],
"source": [
"print(print(\"test\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7eba28ef",
"metadata": {},
"outputs": [],
"source": [
"None"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d76bf75d",
"metadata": {},
"outputs": [],
"source": [
"print(None)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7413bb92",
"metadata": {},
"outputs": [],
"source": [
"print(\"test\") is None\t# we will see the meaning of this in a bit"
]
},
{
"cell_type": "markdown",
"id": "8736a914",
"metadata": {},
"source": [
"## Everything is an object"
]
},
{
"cell_type": "markdown",
"id": "5c65816e",
"metadata": {},
"source": [
"How can the interpreter know that a `+` between numbers does something different than a `+` between strings?\n",
"\n",
"Data are represented in memory as **objects**. \n",
"An object _stores information_ and has a **type**.\n",
"\n",
"It is the type of an object that says to the interpreter what to do."
]
},
{
"cell_type": "markdown",
"id": "e3967634",
"metadata": {},
"source": [
"## Examining objects"
]
},
{
"cell_type": "markdown",
"id": "8965fd60",
"metadata": {},
"source": [
"The expression `type(`something`)` gives an object representing the type of the something, those kind of objects are called **classes**."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0ebcd2d2",
"metadata": {},
"outputs": [],
"source": [
"type(100)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa56c244",
"metadata": {},
"outputs": [],
"source": [
"type(\"abc\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "70ca5d2d",
"metadata": {},
"outputs": [],
"source": [
"type(True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e1261ae5",
"metadata": {},
"outputs": [],
"source": [
"type(type(True))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d541e831",
"metadata": {},
"outputs": [],
"source": [
"type(type)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c1d60ad5",
"metadata": {},
"outputs": [],
"source": [
"type(print)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c2a4a349",
"metadata": {},
"outputs": [],
"source": [
"type(type(100))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dac0bd8b",
"metadata": {},
"outputs": [],
"source": [
"type(type(type(100)))"
]
},
{
"cell_type": "markdown",
"id": "6ad6150d",
"metadata": {},
"source": [
"To know whether an object is of some type the function `isinstance` can be used."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "08eb6037",
"metadata": {},
"outputs": [],
"source": [
"isinstance(123, int)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "894df909",
"metadata": {},
"outputs": [],
"source": [
"isinstance(\"123\", int)"
]
},
{
"cell_type": "markdown",
"id": "b09d431e",
"metadata": {},
"source": [
"The function `id` gives a number that univocally identifies an object.\n",
"\n",
"Different objects can have the same id at different times, the uniqueness of id is valid only among existing objects \n",
"(usually the id is the position in memory of the object)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8fb82521",
"metadata": {},
"outputs": [],
"source": [
"id(\"test\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "63fcf755",
"metadata": {},
"outputs": [],
"source": [
"id(10**100)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "241ed657",
"metadata": {},
"outputs": [],
"source": [
"id(10**100)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9e47a832",
"metadata": {},
"outputs": [],
"source": [
"id(\"test\")"
]
},
{
"cell_type": "markdown",
"id": "895525c8",
"metadata": {},
"source": [
"Different objects may represent the same value!\n",
"\n",
"There are two concepts that must be treated separately:\n",
"- **equality**: the objects represent the same value\n",
"- **identity**: the objects are the same.\n",
"\n",
"Those relations are tested with different operators:\n",
"- `==` tests for equality, `!=` is its negation\n",
"- `is` tests for identity, `is not` is its negation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6bc404b0",
"metadata": {},
"outputs": [],
"source": [
"10**100 == 10**100"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8577c723",
"metadata": {},
"outputs": [],
"source": [
"10**100 is 10**100"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bbbac50f",
"metadata": {},
"outputs": [],
"source": [
"%%capture --no-display\n",
"# the line above is notebook specific\n",
"# it hides a warning issued by the interpreter as identity comparison is almost meaningless for literals\n",
"\n",
"\"abc\" is \"abc\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "edb1ab86",
"metadata": {},
"outputs": [],
"source": [
"123 != 123"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dbde417e",
"metadata": {},
"outputs": [],
"source": [
"%%capture --no-display\n",
"\n",
"123 is not 123"
]
},
{
"cell_type": "markdown",
"id": "fb0c68cd",
"metadata": {},
"source": [
"While the identity is fixed by the interpreter, the equality comparison is defined by the object types and can be customised.\n",
"\n",
"One of the requirements for equality is **reflexivity**: identical objects must compare equal. \n",
"An important exception is the floating point value NaN that always compares unequal to anything, even itself."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a19babd4",
"metadata": {},
"outputs": [],
"source": [
"x = float(\"nan\")\t# we will see the meaning of this in a bit\n",
"x is x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "396d3616",
"metadata": {},
"outputs": [],
"source": [
"x == x"
]
},
{
"cell_type": "markdown",
"id": "e8e10207",
"metadata": {},
"source": [
"The objects `True`, `False`, `None` are example of **singletons** (etymologically from the French _sengle_: \"alone\"). \n",
"This means that every way to produce an object equal to None, for example, will give the same object (it is the only one of its kind: it is alone).\n",
"\n",
"It is idiomatic then to check for equality against `None` with `is`.\n",
"One should not usually check for equality directly against `True` or `False` as the values themselves can be used in logical computation and the check can probably be simplified away."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "666f8da1",
"metadata": {},
"outputs": [],
"source": [
"10**100 is 10**100"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bdf06e5b",
"metadata": {},
"outputs": [],
"source": [
"True is True"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6d7fc989",
"metadata": {},
"outputs": [],
"source": [
"False is False"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "80dfffa2",
"metadata": {},
"outputs": [],
"source": [
"None is None"
]
},
{
"cell_type": "markdown",
"id": "abd88203",
"metadata": {},
"source": [
"## Naming objects"
]
},
{
"cell_type": "markdown",
"id": "7d993c1a",
"metadata": {},
"source": [
"To use a datum multiple times it is useful to be able to refer to an object representing it, instead of computing the same expression every time.\n",
"\n",
"To assign a reference of an object to a name the **assignment** operator `=` is used. \n",
"The name is on the left of the operator and an expression on the right.\n",
"\n",
"An assigned name is a new kind of expression and it is evaluated to the object assigned to the name. \n",
"An assigned name is known as a **variable**, the evaluation of a variable name is a **variable lookup**.\n",
"\n",
"A variable name is not linked forever to an object. \n",
"A different object can be assigned to a name with an assignment to the same name. \n",
"A name can be de-assigned using the keyword `del`.\n",
"\n",
"An object without references to it cannot be accessed anymore and will eventually be purged from memory (with few exceptions)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dd3a990e",
"metadata": {},
"outputs": [],
"source": [
"x = \"abc\"\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "45106037",
"metadata": {},
"outputs": [],
"source": [
"\"test \" + x * 2"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fdf0c4fc",
"metadata": {},
"outputs": [],
"source": [
"x = 100\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "144dadb9",
"metadata": {},
"outputs": [],
"source": [
"x + 23"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "35cdc759",
"metadata": {},
"outputs": [],
"source": [
"x = x + 23\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9721881b",
"metadata": {},
"outputs": [],
"source": [
"del x\n",
"x"
]
},
{
"cell_type": "markdown",
"id": "3ac75b18",
"metadata": {},
"source": [
"Assignments and `del` statements are not expressions!\n",
"\n",
"They do something but no object is produced. \n",
"So they cannot be used in compound expressions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1db471e3",
"metadata": {},
"outputs": [],
"source": [
"(x = 1) + 2"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c42978d4",
"metadata": {},
"outputs": [],
"source": [
"x = 1\n",
"print(del x)"
]
},
{
"cell_type": "markdown",
"id": "9f15f2f5",
"metadata": {},
"source": [
"Since Python 3.8 there is an assignment operator that also forms expressions giving the assigned object as a value; the so called **walrus operator** (ITA: warlus significa tricheco)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "50e44dea",
"metadata": {},
"outputs": [],
"source": [
"x = 1\n",
"print(x := 2)\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a3b2cfb9",
"metadata": {},
"outputs": [],
"source": [
"(x := 3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e44ec692",
"metadata": {},
"outputs": [],
"source": [
"x"
]
},
{
"cell_type": "markdown",
"id": "1c9fda92",
"metadata": {},
"source": [
"## More on assignments..."
]
},
{
"cell_type": "markdown",
"id": "9f644424",
"metadata": {},
"source": [
"Standard assignments are not expressions, nevertheless they can be concatenated"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6c3e79e7",
"metadata": {},
"outputs": [],
"source": [
"x = y = 100\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a2f3e10b",
"metadata": {},
"outputs": [],
"source": [
"y"
]
},
{
"cell_type": "markdown",
"id": "a473fbe9",
"metadata": {},
"source": [
"Multiple names can be assigned at the same time"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "becf9831",
"metadata": {},
"outputs": [],
"source": [
"x, y = 1, 2\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f7a7d5b7",
"metadata": {},
"outputs": [],
"source": [
"y"
]
},
{
"cell_type": "markdown",
"id": "43933c11",
"metadata": {},
"source": [
"And multiple values to the same name?!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "72d22611",
"metadata": {},
"outputs": [],
"source": [
"z = 10, 20\n",
"x, y = z\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "863dad64",
"metadata": {},
"outputs": [],
"source": [
"y"
]
},
{
"cell_type": "markdown",
"id": "7d98d64c",
"metadata": {},
"source": [
"## Tuples, first encounter"
]
},
{
"cell_type": "markdown",
"id": "c60d3ff0",
"metadata": {},
"source": [
"The expression on the right hand side of the assingment in the previous example creates an object of a kind we have not yet seen, a **tuple**. \n",
"A comma separated list of expressions (eventually between parentheses) is interpreted as a new tuple.\n",
"\n",
"A tuple is an ordered, finite, collection of objects."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1503b260",
"metadata": {},
"outputs": [],
"source": [
"1, 2, 3"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "951f580d",
"metadata": {},
"outputs": [],
"source": [
"1, 2, 3,"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d6243e5e",
"metadata": {},
"outputs": [],
"source": [
"(1, 2, 3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "666a3274",
"metadata": {},
"outputs": [],
"source": [
"1, (2, 3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "badb269f",
"metadata": {},
"outputs": [],
"source": [
"1, ((), ((), 2)), (), 3"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9c1725bf",
"metadata": {},
"outputs": [],
"source": [
"1,"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "21db9c46",
"metadata": {},
"outputs": [],
"source": [
"()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5618c6dd",
"metadata": {},
"outputs": [],
"source": [
"x = 1, 2, 3\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "10e2d2ff",
"metadata": {},
"outputs": [],
"source": [
"type(x)"
]
},
{
"cell_type": "markdown",
"id": "07150942",
"metadata": {},
"source": [
"The `len` function gives the number of elements in a tuple \n",
"(or in any of the many kinds of collections where the idea of number of elements makes sense)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "42b7445d",
"metadata": {},
"outputs": [],
"source": [
"len((1, 2, 3))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "26fde020",
"metadata": {},
"outputs": [],
"source": [
"len(())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5f7113e0",
"metadata": {},
"outputs": [],
"source": [
"len((1, (2, 3)))"
]
},
{
"cell_type": "markdown",
"id": "75af5458",
"metadata": {},
"source": [
"The object at position $k$ in a tuple can be retrieved directly using square brackets: writing `[`$k$`]` after the tuple. \n",
"This access is called **indexing** and is similar to function calls that instead use `(`...`)` after the function object.\n",
"\n",
"Indexing starts with index $0$ and ends with $n-1$, where $n$ is the length of the tuple. \n",
"Negative indices are allowed: to them the lenght is added. So $-1$ is the index corresponding to the last element of the tuple."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3a0e0bea",
"metadata": {},
"outputs": [],
"source": [
"t = 1, 2, 3\n",
"t[1], t[2], t[0]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "51363e58",
"metadata": {},
"outputs": [],
"source": [
"t[-3], t[-2], t[-1]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4c1d7676",
"metadata": {},
"outputs": [],
"source": [
"(1, 2, 3)[1]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4910840e",
"metadata": {},
"outputs": [],
"source": [
"[1,(2,[3,4])][1][1][1]"
]
},
{
"cell_type": "markdown",
"id": "cf6d74d9",
"metadata": {},
"source": [
"## ... more on assignments..."
]
},
{
"cell_type": "markdown",
"id": "47484b07",
"metadata": {},
"source": [
"If\n",
"- on the left hand side of the `=` there is a non-empty comma separated list of names\n",
"- on the right hand side a tuple (or any iterable collection)\n",
"- the iterable has as many elements as the list of names\n",
"\n",
"then the elements of the tuple are assigned in order to the names."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6394d373",
"metadata": {},
"outputs": [],
"source": [
"x, = 1,\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b04c2fb3",
"metadata": {},
"outputs": [],
"source": [
"x, y = 1, 2\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8dfa8741",
"metadata": {},
"outputs": [],
"source": [
"y"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a4bf8ad4",
"metadata": {},
"outputs": [],
"source": [
"t = 3, 2, 1\n",
"x, y, z = t\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b9a4a04a",
"metadata": {},
"outputs": [],
"source": [
"y"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d8091a4e",
"metadata": {},
"outputs": [],
"source": [
"z"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3c010025",
"metadata": {},
"outputs": [],
"source": [
"x, y = 1, (2, 3)\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a34e3689",
"metadata": {},
"outputs": [],
"source": [
"y"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7f877470",
"metadata": {},
"outputs": [],
"source": [
"x, y, = 1, 2\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2053f65d",
"metadata": {},
"outputs": [],
"source": [
"y"
]
},
{
"cell_type": "markdown",
"id": "e9178fb0",
"metadata": {},
"source": [
"If\n",
"- on the left hand side of the `=` there is a non-empty comma separated list of names\n",
"- exactly one of those names is preceded by `*`\n",
"- on the right hand side there is a tuple (or any iterable collection)\n",
"- the iterable has at least as many elements as one less than the number of names\n",
"\n",
"then:\n",
"- the elements of the tuple are assigned in order to the names before the `*`\n",
"- a list object (?!) containing some elements is assigned to the `*`-name\n",
"- the remaining elements are assigned to the remaining names (the list object absorbs just the exact number of objects, even $0$)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "98dad69b",
"metadata": {},
"outputs": [],
"source": [
"v, x, *y, z, w = 1, 2, 3, 4, 5, 6\n",
"v, x, y, z, w"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8a32f586",
"metadata": {},
"outputs": [],
"source": [
"x, *y, z = 1, 2, 3\n",
"x, y, z"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b07386b4",
"metadata": {},
"outputs": [],
"source": [
"x, *y, z = 1, 2\n",
"x, y, z"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8a613841",
"metadata": {},
"outputs": [],
"source": [
"x, *x, x = 1, 2, 3\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6e331c7f",
"metadata": {},
"outputs": [],
"source": [
"*x = 1"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dcbcdd93",
"metadata": {},
"outputs": [],
"source": [
"*x, = 1"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "42d05da0",
"metadata": {},
"outputs": [],
"source": [
"*x, = 1,\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b38bd1d9",
"metadata": {},
"outputs": [],
"source": [
"*x, = ()\n",
"x"
]
},
{
"cell_type": "markdown",
"id": "474d7673",
"metadata": {},
"source": [
"## Lists, first encounter"
]
},
{
"cell_type": "markdown",
"id": "9083cefc",
"metadata": {},
"source": [
"Lists behave as tuples do: they are ordered, finite, collections of objects. \n",
"The main important difference is that lists are **mutable**. \n",
"Instead, objects of every type seen up to now are immutable.\n",
"\n",
"List objects can be created with a comma separated list of objects enclosed by square brackets."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0a74452c",
"metadata": {},
"outputs": [],
"source": [
"1, 2, 3"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4f3b4665",
"metadata": {},
"outputs": [],
"source": [
"[1, 2, 3]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e2d1cd15",
"metadata": {},
"outputs": [],
"source": [
"[]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b3c7e9ea",
"metadata": {},
"outputs": [],
"source": [
"[1, (2, 3), [4]], 5"
]
},
{
"cell_type": "markdown",
"id": "2cc971d3",
"metadata": {},
"source": [
"List objects can be indexed, exactly as tuple objects. \n",
"But a list indexing can appear on the left side of an assignment operator as if it was a name!\n",
"\n",
"An assignment to a list position changes the object at that position in the list; the position must be at a valid index (from $0$ to `len(`list object`)`).\n",
"\n",
"Similarly, the `del` statement can be used to remove an element (all the following elements will shift one position to the left)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "99b0b6af",
"metadata": {},
"outputs": [],
"source": [
"x = [1, 2, 3, 4]\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3e3b67b7",
"metadata": {},
"outputs": [],
"source": [
"x[2] = 30\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0621b2cc",
"metadata": {},
"outputs": [],
"source": [
"x[2], y = 300, 100\n",
"x, y"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da8bbd27",
"metadata": {},
"outputs": [],
"source": [
"del x[3]\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be271340",
"metadata": {},
"outputs": [],
"source": [
"x = [0, 1, 2]\n",
"[10, x, 100][1][2] = 20\n",
"x"
]
},
{
"cell_type": "markdown",
"id": "ac3e771a",
"metadata": {},
"source": [
"## ... more on assignments"
]
},
{
"cell_type": "markdown",
"id": "a2e0fe40",
"metadata": {},
"source": [
"In an assignment expression given by `=`, if in the list of names (or, we should rather say, identifiers) a sub-list of identifiers appears enclosed by parentheses or square brackets, then all the rules already seen are recursively applied."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a5ad634f",
"metadata": {},
"outputs": [],
"source": [
"x, *y = t = 1, 2, 3\n",
"x, y, t"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a53df81",
"metadata": {},
"outputs": [],
"source": [
"y[0], *y[1] = t\n",
"y"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "16343d02",
"metadata": {},
"outputs": [],
"source": [
"a, (b, *c), *d = 0, t\n",
"a, b, c, d"
]
},
{
"cell_type": "markdown",
"id": "2befd3a3",
"metadata": {},
"source": [
"## Mutability and aliasing"
]
},
{
"cell_type": "markdown",
"id": "b7c59f4f",
"metadata": {},
"source": [
"Mutability of objects can have unexpected consequences."
]
},
{
"cell_type": "markdown",
"id": "730ee30b",
"metadata": {},
"source": [
"For example a mutable container, such as a list, could have itself as element. \n",
"This possibility is not particularly useful but can be a source for bugs, so it should be kept in mind."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c4e2377c",
"metadata": {},
"outputs": [],
"source": [
"l = [0, 1, 2]\n",
"l[1] = l\n",
"l"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c8062de7",
"metadata": {},
"outputs": [],
"source": [
"l[1]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "03371d8d",
"metadata": {},
"outputs": [],
"source": [
"l[1][1][1][1][1][0]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e86b0454",
"metadata": {},
"outputs": [],
"source": [
"l[1][1][2] = 20\n",
"l"
]
},
{
"cell_type": "markdown",
"id": "ee681752",
"metadata": {},
"source": [
"A variable name can be re-assigned but this is not the only way to change the value represented by the object a name refers to.\n",
"\n",
"If the referred object changes value, then the updated value will be the one represented by the expression given by the name of the variable, _NOT_ the value represented at the moment of the assignment!\n",
"\n",
"Moreover, the same object can be assigned to many variable names so a change to the object operated to it with a name will reflect also to the values retrieved by the other names. \n",
"This phenomenon, an object having many names, is called **aliasing**.\n",
"\n",
"One should keep in mind that variable names, objects, and values are different things. \n",
"Variables are not names for values. \n",
"Variables are references to objects representing values."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7b7c64f5",
"metadata": {},
"outputs": [],
"source": [
"x = [1, 2, 3, 4]\n",
"y = x\n",
"y"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "59c20236",
"metadata": {},
"outputs": [],
"source": [
"x[2] = 30\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ec11653c",
"metadata": {},
"outputs": [],
"source": [
"del x[3]\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0431acac",
"metadata": {},
"outputs": [],
"source": [
"y"
]
},
{
"cell_type": "markdown",
"id": "07cc4dc8",
"metadata": {},
"source": [
"Can we at least think about variables referring to immutable objects as the values represented?\n",
"\n",
"Well yes, but actually no. \n",
"A tuple, for example, is immutable but can have as elements mutable objects..."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f943d085",
"metadata": {},
"outputs": [],
"source": [
"x = 1, [2, 3, 4]\n",
"y = x\n",
"y"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "142f20ee",
"metadata": {},
"outputs": [],
"source": [
"x[1][1] = 30\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "95d58056",
"metadata": {},
"outputs": [],
"source": [
"del x[1][2]\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b06d1418",
"metadata": {},
"outputs": [],
"source": [
"y"
]
},
{
"cell_type": "markdown",
"id": "666fee66",
"metadata": {},
"source": [
"## Hashes"
]
},
{
"cell_type": "markdown",
"id": "fe04ac0e",
"metadata": {},
"source": [
"Since non-identical objects could represent the same value and equality comparison can be expensive (for containers it must be done recursively element by element), it would be useful to have an easier comparison that works most of the time... \n",
"A hash comparison is the answer.\n",
"\n",
"An immutable object that does not contain references to mutable objects usually has a hash. \n",
"A **hash** is a number used to rapidly find out if two objects represent different values.\n",
"\n",
"The requirement for objects that have a hash is that if they evaluate equal they must have the same hash. \n",
"Moreover the hash of an object must remain the same for the lifetime of the object.\n",
"\n",
"Ideally different objects should have the same hash with a very low probability. \n",
"This is not a strict requirement but it _will_ affect performance of some container objects.\n",
"\n",
"Given those requirements, mutable objects (as lists) cannot have a sensible hash, and an immutable container object cannot have a sensible hash if any of its elements does not have a hash. \n",
"In fact, if two different mutable objects had the same hash and they could be modified to evaluate equal, then - since their hash would still be the same (as they are constant) - the rule that equal objects have equal hashes would be broken."
]
},
{
"cell_type": "markdown",
"id": "5a20ee83",
"metadata": {},
"source": [
"The funcion `hash` gives the hash of an object, if available."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24ef5797",
"metadata": {},
"outputs": [],
"source": [
"hash(10**100)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "72e71aa3",
"metadata": {},
"outputs": [],
"source": [
"x, y = 10**100, 10**100\n",
"hash(x) == hash(y)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "13eb92ae",
"metadata": {},
"outputs": [],
"source": [
"id(x) == id(y)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bc2e2cfa",
"metadata": {},
"outputs": [],
"source": [
"hash((1, 2, 3))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "620d1afb",
"metadata": {},
"outputs": [],
"source": [
"hash((1, 2, [3]))"
]
},
{
"cell_type": "markdown",
"id": "b0a1c71e",
"metadata": {},
"source": [
"An object that has a hash can almost be expected to not suffer from the problems seen before due to mutability... almost. \n",
"It is a good habit to always keep in mind of mutability and aliasing, also for hashable objects."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f768db0",
"metadata": {},
"outputs": [],
"source": [
"class BadList(list):\n",
" def __hash__(self):\n",
" return 42\n",
"\n",
"x = BadList([1,2,3])\n",
"y = x\n",
"y"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bf1a5686",
"metadata": {},
"outputs": [],
"source": [
"hash(x)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "548c8583",
"metadata": {},
"outputs": [],
"source": [
"del x[0]\n",
"hash(x)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1c51d466",
"metadata": {},
"outputs": [],
"source": [
"y"
]
},
{
"cell_type": "markdown",
"id": "bd19503a",
"metadata": {},
"source": [
"## Pure functions and maps"
]
},
{
"cell_type": "markdown",
"id": "a7f64fd3",
"metadata": {},
"source": [
"A function object when called can do almost anything. \n",
"In the context of mathematics a function is something different and more \"well behaved\": a function is a relation from a set of arguments to a set of values such that to any argument corresponds one and only one value.\n",
"\n",
"(Some of) those \"mathematical functions\" can be represented by the so called **pure functions**:\n",
"- called to the same arguments they give always the same value (same as value: under equality)\n",
"- they have no side-effects, they represent computations (the opposite of a procedure).\n",
"\n",
"Those argument-value relations conceptually are sets: collections of argument-value pairs. \n",
"There are two distinct ways to define a set:\n",
"- **intensional** definition: a property, i.e. some rule that says what is and what is not in the set\n",
"- **extensional** definition: a list of elements.\n",
"\n",
"Intensional and ext\n",
"ensional definitions give different ways to represent \"mathematical functions\": the first is by pure functions, the second is by maps."
]
},
{
"cell_type": "markdown",
"id": "c45c6ea1",
"metadata": {},
"source": [
"## Dictionaries, first encounter"
]
},
{
"cell_type": "markdown",
"id": "c3c46f24",
"metadata": {},
"source": [
"On a computer an extensional definition can only work for a finite set. \n",
"A way to represent extensionally a \"mathematical function\" on a finite set is using a **map**, i.e. an explicit list of key-value pairs with a way to obtain the value corresponding to a given key. \n",
"In Python maps are represented by **dict** (dictionary) objects, which _are not function_: they are not callable but provide a way to access values given a key."
]
},
{
"cell_type": "markdown",
"id": "0761b063",
"metadata": {},
"source": [
"A dictionary object can be created writing a comma separated list, even empty, of pairs of expressions between curly braces. \n",
"A key-value pair is separated by a `:`.\n",
"If more couples have the same key only the last one counts (at most one value per key).\n",
"\n",
"To access a value an analogue of list indexing is used: an expression representing a dict is followed by an expression representing the key, enclosed in square brackets. \n",
"This expression represents the value corresponding to the key.\n",
"It is an error to access the value of a key that is not present."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b99d06f9",
"metadata": {},
"outputs": [],
"source": [
"grades = {\"Lisa\": 10, \"Bart\": 5, \"Bart\": 6}\n",
"grades[\"Lisa\"], grades[\"Bart\"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "26a3dc77",
"metadata": {},
"outputs": [],
"source": [
"grades[\"Homer\"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da890b88",
"metadata": {},
"outputs": [],
"source": [
"grades"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5d0e600",
"metadata": {},
"outputs": [],
"source": [
"type(grades)"
]
},
{
"cell_type": "markdown",
"id": "ab2c1d3d",
"metadata": {},
"source": [
"As the keys must appear at most once they must be hashable. \n",
"If a key could change value, then every time that happens the interpreter would need to compare it to every other key of every dict the key belongs to.\n",
"\n",
"As this is complicated, dicts assume that their keys, having a hash, do not change value and will not collide in the future."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "27316b74",
"metadata": {},
"outputs": [],
"source": [
"{[]: 0}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "95d7b6e0",
"metadata": {},
"outputs": [],
"source": [
"class BadList(list):\n",
" def __hash__(self):\n",
" return 42\n",
"\n",
"x, y = BadList(), BadList([1])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e7cdc603",
"metadata": {},
"outputs": [],
"source": [
"d = {x: 0, y: 1, y: 10}\n",
"d"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "55e526a6",
"metadata": {},
"outputs": [],
"source": [
"del y[0]\n",
"y == x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2aa29aa2",
"metadata": {},
"outputs": [],
"source": [
"d"
]
},
{
"cell_type": "markdown",
"id": "0f054ef0",
"metadata": {},
"source": [
"Dictionaries are mutable objects.\n",
"\n",
"As for lists, the access expression can be used with assignment and `del` statements, to reassign or delete the value corresponding to a key. \n",
"An important difference with lists is that a value can be assigned to a fresh key while, in a list, only the positions that are already occupied can be re-assigned. \n",
"Moreover, there is no shift of the following values after a deletion, since the keys, differently from indices, are not ordered.\n",
"\n",
"Iterations on a dict (as in assignments to multiple names) iterates on keys! \n",
"From Python 3.7 the order of iteration is guaranteed to be the order of insertion."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9d0ad768",
"metadata": {},
"outputs": [],
"source": [
"l, d = [], {}\n",
"l[0] = 10"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "40b66b2d",
"metadata": {},
"outputs": [],
"source": [
"d[0] = 10\n",
"d"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4df682cf",
"metadata": {},
"outputs": [],
"source": [
"d[0], d[1], d[5] = 100, 10, 50\n",
"d"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "05eabecc",
"metadata": {},
"outputs": [],
"source": [
"del d[1]\n",
"d"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "caba7e1f",
"metadata": {},
"outputs": [],
"source": [
"del d[2]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "062161c7",
"metadata": {},
"outputs": [],
"source": [
"*l, = d\n",
"l"
]
},
{
"cell_type": "markdown",
"id": "f460620c",
"metadata": {},
"source": [
"## Unpacking"
]
},
{
"cell_type": "markdown",
"id": "f8ac8ff4",
"metadata": {},
"source": [
"We have seen that a name preceded by `*`, in an assignment statement, collects multiple values (in the form of a list). \n",
"A similar notation, in a different context, is used for the opposite phenomenon: **list unpacking**. \n",
"Wherever a comma separated list of \"values\" is required, we can instead write a `*` followed by an expression giving an iterable object which represents the values.\n",
"\n",
"The same is true for lists of pairs, they can be substituted by a dict preceded by `**`. \n",
"This is called **dictionary unpacking**."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8b9586e6",
"metadata": {},
"outputs": [],
"source": [
"t = (1, 1), (2, 4), (3, 9)\n",
"l = [*t, (4, 16)]\n",
"l"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7cdf90e",
"metadata": {},
"outputs": [],
"source": [
"t1 = *l, (5, 25)\n",
"t1"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "97940087",
"metadata": {},
"outputs": [],
"source": [
"d = dict(t1)\n",
"d"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "677846d2",
"metadata": {},
"outputs": [],
"source": [
"d = {**d, 6: 36}\n",
"d"
]
},
{
"cell_type": "markdown",
"id": "f3dcc7f2",
"metadata": {},
"source": [
"## Namespaces and attributes"
]
},
{
"cell_type": "markdown",
"id": "df853bfa",
"metadata": {},
"source": [
"Dictionaries (or similar mapping containers) are used by Python to store the value of variables in a context. \n",
"In fact in different contexts a name can be used in different ways, each context will define a different **namespace**.\n",
"\n",
"Assignments and variables lookup sometimes are, under the hood, operations on a dict. \n",
"The function `globals` returns the dictionary for global variables."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c4eb98bb",
"metadata": {},
"outputs": [],
"source": [
"x = 999\n",
"d = globals()\n",
"d[\"x\"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b8358b7f",
"metadata": {},
"outputs": [],
"source": [
"type(d)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cae0fad5",
"metadata": {},
"outputs": [],
"source": [
"d[\"x\"] = x + 1\n",
"x"
]
},
{
"cell_type": "markdown",
"id": "99691eb5",
"metadata": {},
"source": [
"To every object is associated a namespace and names there are called **attributes** of the object. \n",
"Some attributes are defined by the type of the object while, for some kinds of objects, other attributes are stored in a dictionary.\n",
"\n",
"The attributes of an object, and only them, determine how the object behaves, therefore its nature. \n",
"This point of view is called **duck typing**: \"If it walks like a duck and it quacks like a duck, then it must be a duck\".\n",
"\n",
"Attribute access is done with the operator `.` followed by the attribute name. \n",
"The function `getattr` takes an object and a string representing an attribute name, and gives the attribute value. \n",
"A third argument can be given to getattr as default value for when the name is not in the attributes namespace; otherwise accessing a non-existent attribute is an error.\n",
"\n",
"As for dict access expressions, attribute expressions also can be used in assignment and del statements.\n",
"The corresponding functions for (re-)assignment and deletion are `setattr` and `delattr`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2883aefb",
"metadata": {},
"outputs": [],
"source": [
"class A:\n",
" pass\n",
"\n",
"a = A()\n",
"a.x = 42\n",
"a.x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a4721ffa",
"metadata": {},
"outputs": [],
"source": [
"getattr(a, \"x\"), getattr(a, \"y\", 123)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bbfe86f3",
"metadata": {},
"outputs": [],
"source": [
"setattr(a, \"x\", 456)\n",
"setattr(a, \"y\", 1729)\n",
"getattr(a, \"x\"), getattr(a, \"y\", 123)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f533ff89",
"metadata": {},
"outputs": [],
"source": [
"del a.y\n",
"delattr(a, \"x\")\n",
"getattr(a, \"y\", 123)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11e54f98",
"metadata": {},
"outputs": [],
"source": [
"getattr(a, \"x\")"
]
},
{
"cell_type": "markdown",
"id": "bbcfd77b",
"metadata": {},
"source": [
"If the namespace of the object can accept fresh names that part of the namespace is usually implemented with a dictionary accessible at the attribute `__dict__` and is given by the funcion `vars`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f8647270",
"metadata": {},
"outputs": [],
"source": [
"class A:\n",
" pass\n",
"\n",
"a = A()\n",
"d = a.__dict__\n",
"d is vars(a)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5d2730d",
"metadata": {},
"outputs": [],
"source": [
"a.x = 42\n",
"d"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7782a43f",
"metadata": {},
"outputs": [],
"source": [
"d[\"y\"] = 1729\n",
"d[\"x\"], a.y\n",
"del d[\"x\"]\n",
"getattr(a, \"x\", 123)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b2ead596",
"metadata": {},
"outputs": [],
"source": [
"vars(a)"
]
},
{
"cell_type": "markdown",
"id": "e96a5eea",
"metadata": {},
"source": [
"Not all the attributes of an object are in a dictionary, some (as `__dict__` itself) are defined by the object’s type. \n",
"A, partial, list of the attributes accessible from an object is given by the function `dir`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "173a206c",
"metadata": {},
"outputs": [],
"source": [
"class A:\n",
" pass\n",
"\n",
"a = A()\n",
"dir(a)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "be0b9730",
"metadata": {},
"outputs": [],
"source": [
"dir(123)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "314d67fd",
"metadata": {},
"outputs": [],
"source": [
"dir(type(123))"
]
},
{
"cell_type": "markdown",
"id": "a74d252c",
"metadata": {},
"source": [
"Some attributes can store a callable object and can be called as if they were functions. \n",
"There are in fact no restrictions to the kind of object that can be associated to a name. \n",
"Callable attributes are indeed quite common and are called **methods**.\n",
"\n",
"Usually a method does a computation using the object it belongs to or does something to it. \n",
"Usually a method, retrieved as an attribute, is a callable object that contains a reference to the object it belongs to. This way the object it operates on does not need to be passed to it as an argument, as it would be for a generic function."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d9835471",
"metadata": {},
"outputs": [],
"source": [
"class MyList(list):\n",
" pass\n",
"\n",
"l = MyList([0,1,2])\n",
"l"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ad61821b",
"metadata": {},
"outputs": [],
"source": [
"type(l)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "45e76684",
"metadata": {},
"outputs": [],
"source": [
"isinstance(l, list)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "642b53d0",
"metadata": {},
"outputs": [],
"source": [
"dir(l)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b54cd844",
"metadata": {},
"outputs": [],
"source": [
"vars(l)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e041a43c",
"metadata": {},
"outputs": [],
"source": [
"l.f = print\n",
"vars(l)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7fe84d70",
"metadata": {},
"outputs": [],
"source": [
"l.f(\"test\")\n",
"l.f(l)\n",
"l.f"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7afc09eb",
"metadata": {},
"outputs": [],
"source": [
"l.__str__"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bb9ce33e",
"metadata": {},
"outputs": [],
"source": [
"type(l.__str__)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b5b153ea",
"metadata": {},
"outputs": [],
"source": [
"l.__str__()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "52e99367",
"metadata": {},
"outputs": [],
"source": [
"l.append(100)\n",
"l"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "530692d9",
"metadata": {},
"outputs": [],
"source": [
"list.append(l, 200)\n",
"l"
]
},
{
"cell_type": "markdown",
"id": "1b2c471c",
"metadata": {},
"source": [
"## Functions, first encounter"
]
},
{
"cell_type": "markdown",
"id": "ba26510d",
"metadata": {},
"source": [
"Function objects, differently than any other, are created already bound to a name in the current scope.\n",
"A function definition is composed by:\n",
"- the keyword `def`\n",
"- the variable name\n",
"- a comma separated list of names of arguments (possibly empty) enclosed in parentheses\n",
"- the symbol `:`\n",
"- the function body: a non-empty sequence of lines preceded by a new level of indentation.\n",
"\n",
"These are just the simplest rules for function definition, the official documentation gives more information.\n",
"\n",
"Note: as the function body cannot be empty the do-nothing instruction `pass` can be used when a block of code should do nothing"
]
},
{
"cell_type": "markdown",
"id": "3353ff05",
"metadata": {},
"source": [
"When a function call is evaluated the lines in the function body are executed until the end or until a `return` statement is reached. The value of the expression following `return` will be the value of the function call (or None if the end of the function body is reached).\n",
"\n",
"A function body can contain any kind of statement, even other function definitions (**nested functions**) or calls to the function itself (in this case the function is said to be a **recursive function**)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "321b811d",
"metadata": {},
"outputs": [],
"source": [
"def f():\n",
" print(\"called f\")\n",
" def g():\n",
" print(\"called g\")\n",
" return g\n",
"\n",
"x = f\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e62b8a15",
"metadata": {},
"outputs": [],
"source": [
"y = x()\n",
"y"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e10eec8b",
"metadata": {},
"outputs": [],
"source": [
"y == x()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7f073a1d",
"metadata": {},
"outputs": [],
"source": [
"z = y()\n",
"z"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "846a8999",
"metadata": {},
"outputs": [],
"source": [
"print(z)\n",
"y()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a4067e83",
"metadata": {},
"outputs": [],
"source": [
"x()()"
]
},
{
"cell_type": "markdown",
"id": "a9f30a51",
"metadata": {},
"source": [
"When a function is called, a new namespace is created for the variables in the body of the function. \n",
"This namespace is initialised with the passed arguments bound to the argument names given at function declaration. \n",
"If, in the function declaration, the name of an argument is followed by `=` and an expression, then when that argument is omitted in a function call _the value_ of the expression is used instead (the evaluation is done _at declaration_).\n",
"\n",
"The function `locals` in a function body (actually in any scope) gives a dict representing the current namespace.\n",
"This is similar to how `globals` works but a change to the value given by `locals` _may or may not_ reflect in a change of the actual namespace. \n",
"In nested function calls `globals` and `locals` give dictionaries for the most external and most internal namespace repectively.\n",
"\n",
"When a function object is created the code in the body is known so the interpreter can find out what are the names involved in an assignment in the body; such names are considered **local** (even if the assignment can never be reached in a function call) and the assignments will affect the local namespace. \n",
"Similarly, for nested function declaration, the interpreter can figure out in which internal scope a variable is assigned. \n",
"The variable lookup will then jump directly to the most internal namespace where the name is assigned. If the name is not assigned in a function body the lookup will be an access to the dictionary of the global namespace."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f91acb6",
"metadata": {},
"outputs": [],
"source": [
"x = 100\n",
"def f1():\n",
" def f2():\n",
" print(x)\n",
" x = 101\n",
" f2()\n",
"\n",
"f1()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3aefdf7b",
"metadata": {},
"outputs": [],
"source": [
"x = 100\n",
"def f1():\n",
" def f2():\n",
" print(x)\n",
" f2()\n",
" x = 101\n",
"\n",
"f1()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "337d2cd3",
"metadata": {},
"outputs": [],
"source": [
"print(\"setting x0, x1, x2...\")\n",
"x0, x1, x2 = 0, 1, 2\n",
"def f1():\n",
" print(\"-- called f1\")\n",
" print(\"setting x2, x3...\")\n",
" x2, x3 = 102, 103\n",
" print(locals())\n",
" def f2(n):\n",
" print(\"-- called f2\")\n",
" print(\"setting x1, x3...\")\n",
" x1, x3 = 1000 * n + 1, 1000 * n + 3\n",
" print(\"setting x1, x2 using globals\")\n",
" globals()[\"x1\"] = 1000 * n + 1\n",
" globals()[\"x2\"] = 1000 * n + 2\n",
" print(x0, x1, x2, x3)\n",
" print(locals())\n",
" f2(1)\n",
" print(\"-- back to f1\")\n",
" print(x0, x1, x2, x3)\n",
" print(locals())\n",
" f2(2)\n",
" print(\"-- back to f1\")\n",
" print(x0, x1, x2, x3)\n",
" print(locals())\n",
"\n",
"f1()\n",
"print(\"-- back to top level\")\n",
"print(x0, x1, x2)"
]
},
{
"cell_type": "markdown",
"id": "8f8f34d8",
"metadata": {},
"source": [
"## Docstrings"
]
},
{
"cell_type": "markdown",
"id": "15c5068f",
"metadata": {},
"source": [
"If the first line of a function body is a string literal, then that string, called **docstring**, becames the `__doc__` attribute of the function object and is displayed by the function `help`. \n",
"Every function (and class) defined by the language or the standard library has a doctring that explains its use."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8886d931",
"metadata": {},
"outputs": [],
"source": [
"def f(x):\n",
" \"This is a function!\"\n",
" return x + 1\n",
"\n",
"help(f)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "832bde8e",
"metadata": {},
"outputs": [],
"source": [
"help(locals)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "102ee90b",
"metadata": {},
"outputs": [],
"source": [
"help(print)"
]
},
{
"cell_type": "markdown",
"id": "ddccfc06",
"metadata": {},
"source": [
"## Anonymous functions"
]
},
{
"cell_type": "markdown",
"id": "285d5764",
"metadata": {},
"source": [
"It is also possible to create function objects not directly bound to a name, those are called **anonymous functions** or **lambdas**. \n",
"They are declared using the keyword `lambda` followed by the list of argument names (without parentheses), a `:`, then an axpression. \n",
"When called an anonymous function behaves as a function which body consist of a return statement followed by the expression after the `:`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7a7b1f1",
"metadata": {},
"outputs": [],
"source": [
"def norm2(x, y):\n",
" return (x**2 + y**2)**.5\n",
"\n",
"norm2"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ee505f6b",
"metadata": {},
"outputs": [],
"source": [
"norm2(3, 4)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "edf72755",
"metadata": {},
"outputs": [],
"source": [
"norm2 = lambda x, y: (x**2 + y**2)**.5\n",
"norm2"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e6784fa4",
"metadata": {},
"outputs": [],
"source": [
"norm2(3, 4)"
]
},
{
"cell_type": "markdown",
"id": "c8979152",
"metadata": {},
"source": [
"Usually an anonymous function is used for a small pure function. \n",
"One should not use them otherwise but there are no thecnical constraints against it."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "07f74fbe",
"metadata": {},
"outputs": [],
"source": [
"test_result = lambda grade: ((grade >= 18) or print(\"fail\")) and print(\"pass\")\n",
"test_result(30)\n",
"test_result(15)"
]
},
{
"cell_type": "markdown",
"id": "10625e45",
"metadata": {},
"source": [
"## Modules"
]
},
{
"cell_type": "markdown",
"id": "f3df5e09",
"metadata": {},
"source": [
"A file ending in `.py` can be run from the interpreter using the keyword `import` followed by the name of the file without extension. \n",
"This statement will:\n",
"- run the file (only once per file, even if imported multiple times) in a clean namespace\n",
"- create a module object\n",
"- assigns the namespace to that object\n",
"\n",
"So common definitions can be written in a separate file and imported when needed.\n",
"\n",
"There are some variants (this list is not comprehensive):\n",
"- `import` module_name `as` name: assigns the module object to the name\n",
"- `from` module_name `import` x1, x2, ...: assigns to names x1, x2, ... the objects in the context of the module with corresponding names\n",
"- `from` module_name `import *`: as above but for every name defined in the context of the module\n",
"- `from` module_name `import` x `as` y: assigns to name y the object in the context of the module with name x\n",
"\n",
"The standard library has a rich collection of useful modules."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6f403aba",
"metadata": {},
"outputs": [],
"source": [
"import module_example\n",
"module_example"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fd9be580",
"metadata": {},
"outputs": [],
"source": [
"module_example.x, module_example.y"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5bb033fd",
"metadata": {},
"outputs": [],
"source": [
"import module_example as m\n",
"m"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3dd840cd",
"metadata": {},
"outputs": [],
"source": [
"x = 123\n",
"from module_example import x\n",
"x"
]
},
{
"cell_type": "markdown",
"id": "adc37ff5",
"metadata": {},
"source": [
"A common idiom for a script is to define a function that does what it is intended to be done but is executed only if `__name__ == \"__main__\"`. \n",
"In fact when a module is run the context is set with the variable `__name__` bound to a string representing the module name; when instead a file is run by the interpreter the variable `__name__` is bound to the string `\"__main__\"`. \n",
"That check therefore run the script when the file is executed directly and not when it is imported as a module so the declaration in the script can be accessed without unintentionally \"running\" the script."
]
},
{
"cell_type": "markdown",
"id": "b590f598",
"metadata": {},
"source": [
"## The Zen of Python"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3c6ded93",
"metadata": {},
"outputs": [],
"source": [
"import this"
]
},
{
"cell_type": "markdown",
"id": "9081daec",
"metadata": {},
"source": [
"# >>> Homework 1"
]
},
{
"cell_type": "markdown",
"id": "c8646eea",
"metadata": {},
"source": [
"Review the previous part trying to predict the effect of every code cell before running it."
]
},
{
"cell_type": "markdown",
"id": "381c1b4d",
"metadata": {},
"source": [
"# >>> The language"
]
},
{
"cell_type": "markdown",
"id": "557c6ce3",
"metadata": {},
"source": [
"Python language is full of feature so we will only show a subset of what there is. \n",
"We suggest to explore the online documentation at https://docs.python.org/3/"
]
},
{
"cell_type": "markdown",
"id": "d5030da2",
"metadata": {},
"source": [
"## Strings"
]
},
{
"cell_type": "markdown",
"id": "3977382e",
"metadata": {},
"source": [
"Strings are of type `str`; that class can be called on an object to get a nice representation, as a string, of the object value (a \"faithful\" representation is instead given by the function `repr`)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0d177585",
"metadata": {},
"outputs": [],
"source": [
"str([1,2,3]), str(\"abc\"), repr(\"abc\")"
]
},
{
"cell_type": "markdown",
"id": "bc2f1b37",
"metadata": {},
"source": [
"Strings literals can be created with `'`...`'` or `\"`...`\"`, if a literal spans multiple lines a triple delimiter must be used. If the delimiter is preceded by `f` then when `{`expression`}` appears it is repplaced by the string given by `str(`expression`)` (`{`expression`:` format-specifier`}` can be used to customize certain representations).\n",
"\n",
"Some escape sequences are available:\n",
"- `\\\\`: \\\n",
"- `\\n`: newline\n",
"- `\\t`: tabulation\n",
"- `'` : '\n",
"- `\"` : \""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ea6423cc",
"metadata": {},
"outputs": [],
"source": [
"\"\", \"abc\", \"\"\"x\n",
"yz\n",
"w\"\"\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "89c45266",
"metadata": {},
"outputs": [],
"source": [
"f'{123} - {123:x} - {123:o} - {123:b}'"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1947c4ca",
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"x = 1.23\n",
"f\"{x} - {x:06.04} - {x= }\""
]
},
{
"cell_type": "markdown",
"id": "b42d466c",
"metadata": {},
"source": [
"Strings support unicode symbols and they can be put directly in a string literal as Python default ancoding for souurce is UTF8."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a493f0b8",
"metadata": {},
"outputs": [],
"source": [
"\"Pizza time! 🍕🍕🍕\""
]
},
{
"cell_type": "markdown",
"id": "39c8f84a",
"metadata": {},
"source": [
"Some useful methods for string are `find`, `strip`, `join`, `split`, and many others."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "156d1797",
"metadata": {},
"outputs": [],
"source": [
"\" - \".join(\"This is a very nice day indeed!\".split())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "41b76839",
"metadata": {},
"outputs": [],
"source": [
"dir(str)"
]
},
{
"cell_type": "markdown",
"id": "08ef6f75",
"metadata": {},
"source": [
"## Numbers"
]
},
{
"cell_type": "markdown",
"id": "1dea0b66",
"metadata": {},
"source": [
"The classes `int`, `float`, `complex` are used to represent integer, floating point, and complex numbers; when called they will try to convert their argument.\n",
"\n",
"Integer numbers are encoded with arbitrary precision as 2-complement. \n",
"Literals made by digits (possibly with a sign) evaluate to an `int`; a `_` can be used as digit separator while `0x`, `0o`, `0b` prefixes specify base hexadecimal, octal, and binary.\n",
"\n",
"Floating point numbers are represented as IEEE 754 64 bit \"double precision\" numbers. \n",
"Literals made by digits evaluate to a `float`; the symbol `e` or `E` is used in literals to use scientific notation.\n",
"\n",
"Complex numbers can be thought as pairs of float.\n",
"Literals of digits ending with `j` represents imaginary numbers, if they compare in an arithmetic expression the evaluation will, usually, be a `complex`. \n",
"The real and imaginary parts can be accessed as the attributes `real` and `imag`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "36603c82",
"metadata": {},
"outputs": [],
"source": [
"-0x_c0ffe, 0o1_234_567, 0b101010, 2**789"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef091de6",
"metadata": {},
"outputs": [],
"source": [
"-.123, 1E3, 4.2e-1"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a9adf4c",
"metadata": {},
"outputs": [],
"source": [
"3j, .1e1+0j, 9*0j, [(3j).real, (3j).imag]"
]
},
{
"cell_type": "markdown",
"id": "4b9ddb1c",
"metadata": {},
"source": [
"## Booleans"
]
},
{
"cell_type": "markdown",
"id": "b2142e81",
"metadata": {},
"source": [
"The only instances of the class `bool` are the objects given by the literals `True` and `False`. \n",
"When called on a number it gives False if the value is 0, True otherwise. \n",
"When called on a collection it will, usually, give False if the there are no elemnts, True otherwise. \n",
"When called on None gives False."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "053bccd3",
"metadata": {},
"outputs": [],
"source": [
"bool({}), bool(5)"
]
},
{
"cell_type": "markdown",
"id": "53bffed7",
"metadata": {},
"source": [
"## Logic computations"
]
},
{
"cell_type": "markdown",
"id": "9cc85895",
"metadata": {},
"source": [
"There are $3$ logical operators:\n",
"- `not`: gives True if bool of its argument is False\n",
"- `or` : gives the first argument if bool of it is True, else the second argument\n",
"- `and`: gives the first argument if bool of it is False, else the second argument.\n",
"\n",
"Note that `or` and `and` evaluate the expression on their right if and only if they have to return it. \n",
"This behaviour is called **shortcircuiting**.\n",
"\n",
"The functions `any` and `all` behave similarly to `or` and `and` respectively, but take as argument a collection and iterate on it. \n",
"`any` gives True iff there is an element for which `bool` gives True. \n",
"`all` gives False iff there is an element for which `bool` gives False (in particular it gives True for empty collections). \n",
"For both functions the iteration stops as soon as the result is determined."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c8d3b31b",
"metadata": {},
"outputs": [],
"source": [
"not 5, .0 and [1,2,3], None or 0, [1,2,3] or print(\"hello\"), print(\"hi\") or 42"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c731222",
"metadata": {},
"outputs": [],
"source": [
"any(()), all([1,2,3])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d6985d7d",
"metadata": {},
"outputs": [],
"source": [
"from itertools import count\n",
"any(count()), all(count())"
]
},
{
"cell_type": "markdown",
"id": "73083cd7",
"metadata": {},
"source": [
"## I/O and files"
]
},
{
"cell_type": "markdown",
"id": "37cc5412",
"metadata": {},
"source": [
"The function `input` prints its argument (defaults to `''`) and stops the execution until a line is provided from standard input then returns that line (stripped of the last `'\\n'`).\n",
"\n",
"The function `print` takes multiple arguments and writes their `str` representation on standard output, separated by the `sep` keyword argument (defaults to `' '`) and terminated by the `end` keyword argument (defaults to `'\\n'`).\n",
"\n",
"The `open` function takes a filename (or a path) and returns an object representing such file. \n",
"Such object can be iterated upon as a collection of `'\\n'` terminated lines. \n",
"It should always be closed using the method `close`. \n",
"The [online documentation](https://docs.python.org/3/tutorial/inputoutput.html) is useful to work with files.\n",
"\n",
"More info in the relevant `help` pages (use `dir` on the result of `open` to find the relevant methods)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "867cda6f",
"metadata": {},
"outputs": [],
"source": [
"print(\"Hello,\")\n",
"name = input(\"Input your name: \")\n",
"print(f\"Hello {name}!\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aef62a06",
"metadata": {},
"outputs": [],
"source": [
"f = open(\"module_example.py\")\n",
"print(\"\", *f, sep = \"> \")\n",
"f.close()"
]
},
{
"cell_type": "markdown",
"id": "99838805",
"metadata": {},
"source": [
"## `if` statements"
]
},
{
"cell_type": "markdown",
"id": "c0046a27",
"metadata": {},
"source": [
"The construct:\n",
"\n",
"`if` condition`:` \n",
" if-block\n",
"\n",
", where condition is an expression and code is a a not-empty sequence of lines preceded by a new level of indentation, \n",
"is the simplest form of an **if statement**. \n",
"When executed the condition is evaluated and if would be True as a `bool` then the if-block is executed. \n",
" \n",
"\n",
"The construct can be immediately followed by\n",
"\n",
"`else:` \n",
" else-block\n",
"\n",
"then the else-block is executed if instead the condition is False as a `bool`. \n",
" \n",
"\n",
"Similarly any number of\n",
"\n",
"`elif` condition`:` \n",
" else-block\n",
"\n",
"can be after an `if` block (and eventually before an `else`). \n",
"In an `elif` chain the condition are evaluated until one of them is True as a `bool`, then the corresponding block is executed (the `else` block if present and no condition is True)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "80bfbd76",
"metadata": {},
"outputs": [],
"source": [
"if input():\n",
" print(\"non-empty\")\n",
"else:\n",
" print(\"empty\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9e824663",
"metadata": {},
"outputs": [],
"source": [
"x = [1]\n",
"if not x:\n",
" print(\"no elements\")\n",
" x = None\n",
"elif len(x) == 1:\n",
" x = x[0]\n",
"else:\n",
" print(\"too many elements\")\n",
" x = None\n",
"print(x)"
]
},
{
"cell_type": "markdown",
"id": "463cd45e",
"metadata": {},
"source": [
"## `if` expressions"
]
},
{
"cell_type": "markdown",
"id": "33dc0268",
"metadata": {},
"source": [
"The expression: \n",
"exp1 `if` cond `else` exp2 \n",
"evaluates the cond as a `bool`, if it is True exp1 the whole expression is evaluated to exp1, else to exp2. \n",
"In other languages this construct is known as **ternary operator**."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b682c720",
"metadata": {},
"outputs": [],
"source": [
"\"a\" if 5 else \"b\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a38c1367",
"metadata": {},
"outputs": [],
"source": [
"grades = {\"Lisa\": 10, \"Bart\": 5, \"Bart\": 6}\n",
"name = \"Homer\"\n",
"x = grades[name] if name in grades else print(f\"grade of {name} is unknown\")\n",
"print(x)"
]
},
{
"cell_type": "markdown",
"id": "6e57bead",
"metadata": {},
"source": [
"## `while` statements"
]
},
{
"cell_type": "markdown",
"id": "157780f0",
"metadata": {},
"source": [
"A `while` statement works as an `if` statement but when the end of the main block is reached the statement is run again: the condition is evaluated and so on. \n",
"It admits an `else` block but no `elif` blocks.\n",
"\n",
"Two statements in the main block change the behaviour:\n",
"- `continue` stops the execution and re-run the statement\n",
"- `break` stops the execution"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4184e2ba",
"metadata": {},
"outputs": [],
"source": [
"n = 5\n",
"while n:\n",
" n -= 1\t# this is equivalent to n = n - 1\n",
" print(f\"main:\\t{n}\")\n",
" if n % 2:\n",
" continue\n",
" print(f\"main 2:\\t{n}\")\n",
"else:\n",
" print(f\"else:\\t{n}\")\n",
"\n",
"n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2d474947",
"metadata": {},
"outputs": [],
"source": [
"n = 5\n",
"while True:\n",
" n -= 1\n",
" if not n:\n",
" break\n",
" print(f\"main:\\t{n}\")"
]
},
{
"cell_type": "markdown",
"id": "a9ec2f05",
"metadata": {},
"source": [
"## Exceptions"
]
},
{
"cell_type": "markdown",
"id": "5e056870",
"metadata": {},
"source": [
"When an error or an unanticipated situation occurs the current execution flow halts untill something up in the call stack manages the error. Otherwise the interpreter gives up and the execution stops.\n",
"\n",
"For somebody to manage an exceptional situation the problem must be represented as data and passed around. \n",
"For this purpose **exception objects** can be created using an exception class, there are many and they take as optional argument a string that is used as error message. \n",
"To stop the execution and bubble up an exception the `raise` statement is used, followed by an expression that evaluates to an error object or an error class (if a class it is then called without arguments)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c9493413",
"metadata": {},
"outputs": [],
"source": [
"if 'x' in globals():\n",
" del x\n",
"\n",
"x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a47f41b",
"metadata": {},
"outputs": [],
"source": [
"NameError, NameError(\"test\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7fa10908",
"metadata": {},
"outputs": [],
"source": [
"type(NameError), type(NameError(\"test\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e5696c9d",
"metadata": {},
"outputs": [],
"source": [
"raise NameError(\"this is an error message\")"
]
},
{
"cell_type": "markdown",
"id": "f55a9e49",
"metadata": {},
"source": [
"## `assert` statements"
]
},
{
"cell_type": "markdown",
"id": "6d57a669",
"metadata": {},
"source": [
"The form is: \n",
"`assert` cond \n",
"`assert` cond, msg\n",
"\n",
"An assertion raises an `AssertionError` if cond evaluates to False (as `bool`); if so the optional msg is evaluated and used as message.\n",
"\n",
"If the global variable `__debug__` is set to False the assertions are ignored."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4b20f1e5",
"metadata": {},
"outputs": [],
"source": [
"assert 1, False"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "441389b8",
"metadata": {},
"outputs": [],
"source": [
"assert None"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c54e098a",
"metadata": {},
"outputs": [],
"source": [
"assert [], \"hello\""
]
},
{
"cell_type": "markdown",
"id": "37319444",
"metadata": {},
"source": [
"## `try` statements"
]
},
{
"cell_type": "markdown",
"id": "09af7709",
"metadata": {},
"source": [
"If we foresee that in the execution of some code an exception will be raised then we can enclose that code in a `try` block to stop the exception and do something else: the code in the `except` block. \n",
"If there is an exception type after the `except` keyword only exception objects of that type will be collected. \n",
"Multiple `except` blocks can follow a `try` block.\n",
"\n",
"A bare `raise` in an except block re-raises the caught exception.\n",
"\n",
"A `finally` block can be added to do \"cleen up\" operations, it is run as soon as the execution leaves one of the other blocks."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe69d8bc",
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" print(1)\n",
" assert False, \"test message\"\n",
" print(2)\n",
"except:\n",
" print(3)\n",
" raise\n",
" print(4)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5c879e4d",
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" f = None\n",
" f = open(\"module_example.py\")\n",
" raise KeyError(\"message 1\")\n",
" raise NameError(\"message 2\")\n",
"except NameError as e:\n",
" print(f\"collect 1: exception {type(e)} with message {e}\")\n",
"except KeyError as e:\n",
" print(f\"collect 2: exception {type(e)} with message {e}\")\n",
"finally:\n",
" print(\"finally block\")\n",
" f.close()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d0eed1cf",
"metadata": {},
"outputs": [],
"source": [
"grades = {\"Lisa\": 10, \"Bart\": 5, \"Bart\": 6}\n",
"name = \"Homer\"\n",
"try:\n",
" x = grades[name]\n",
"except KeyError:\n",
" x = None\n",
" print(f\"grade of {name} is unknown\")\n",
"print(x)"
]
},
{
"cell_type": "markdown",
"id": "d8b5b9f7",
"metadata": {},
"source": [
"## `with` statements"
]
},
{
"cell_type": "markdown",
"id": "eb29e8eb",
"metadata": {},
"source": [
"Some objects represent files or network connections or other things that \"interact with the world\"; as such they may require doing certain things to keep a consistent external state. \n",
"Those objects can have the necessary instructions to clean up when they are not required anymore (to flush updates, for example or close a connection), in that case they are called **context managers**.\n",
"\n",
"To invoke the clean-up behaviour of an object a special binding statement can be used, `with`:\n",
"\n",
"`with` expr `as` name`:` \n",
" with-block\n",
"\n",
"then expr is evaluated and the result of its `__enter__` method is assigned to name, the with-block is executed, and then the `__exit__` method of the expr value is called. \n",
"Anything that will make the interpreter leave the with-block will be kept aside to first run the `__exit__` procedure."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1eceb099",
"metadata": {},
"outputs": [],
"source": [
"try:\n",
" with open(\"module_example.py\") as f:\n",
" assert False\n",
" print(\"\", *f, sep = \"> \")\n",
"except AssertionError:\n",
" print(\"an error occurred\")\n",
"\n",
"f.closed"
]
},
{
"cell_type": "markdown",
"id": "63befd6d",
"metadata": {},
"source": [
"## Iterators and `for` loops"
]
},
{
"cell_type": "markdown",
"id": "6d17ee84",
"metadata": {},
"source": [
"An iterator is an object that represents the act of enumerating a collection. \n",
"They can be created with the function `iter`.\n",
"\n",
"An iterator does nothing per se until an element is required from it. \n",
"So an iterator is **lazy** as it does not act until required. \n",
"This gives some advantages as an iterator tries to produce an element, not to remember it. Therefore it does not require much memory and can work with collections potentially infinite. \n",
"On the other hand when an iterator keeps a reference to a mutable object and that object changes then an element is requested to the iterator, the generated element could be from the \"new\" value while the previous were from the \"old\" value of the collection.\n",
"\n",
"A collection on which `iter` can be called is said to be **iterable**. \n",
"Some iterables are: strings, lists, tuples, dictionaries (iterate over keys), sets... \n",
"The class `enumerate` similarly produces an iterator that gives tuple (index, element) with the index increasing (starting from the second argument, default is $0$).\n",
"\n",
"The function `iter` can also be called with a callable and a value that act as a sentinel. \n",
"When an item is generated then the callable is called and its value is returned unless it is equal to the sentinel, then the iteration is stopped.\n",
"\n",
"When the function `next` is called on an iterator a new element is generated and returned. \n",
"If there are no more elements a `StopIteration` exception is raised (unless the call to `next` has a second argument, then that is returned instead)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30ad0be2",
"metadata": {},
"outputs": [],
"source": [
"l = [1,2,3]\n",
"it = iter(l)\n",
"print(next(it))\n",
"l[1] = 20\n",
"print(next(it), next(it))\n",
"print(next(it))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "615dc0c9",
"metadata": {},
"outputs": [],
"source": [
"it = enumerate(\"abc\", 10)\n",
"print(next(it))\n",
"print(next(it))\n",
"it"
]
},
{
"cell_type": "markdown",
"id": "77b247ba",
"metadata": {},
"source": [
"The statement\n",
"```\n",
"for name in collection:\n",
" for-block\n",
"```\n",
"is logically equivalent to\n",
"```\n",
"it = iter(collection)\n",
"done = False\n",
"while not done:\n",
" try:\n",
" name = next(it)\n",
" except StopIteration:\n",
" done = True\n",
" continue\n",
" for-block\n",
"```\n",
"\n",
"It admits an `else` clause."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "998a9293",
"metadata": {},
"outputs": [],
"source": [
"for n in [1,2,3]:\n",
" print(n)\n",
"else:\n",
" print(\"done\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7c78bb9d",
"metadata": {},
"outputs": [],
"source": [
"for n in [1,2,3]:\n",
" print(n)\n",
" break\n",
"else:\n",
" print(\"done\")"
]
},
{
"cell_type": "markdown",
"id": "000d8dab",
"metadata": {},
"source": [
"## Containers"
]
},
{
"cell_type": "markdown",
"id": "a509872c",
"metadata": {},
"source": [
"A collection that supports the `in` operator is a container (`not in` is the nagated operator). \n",
"Those are usually collections: strings, lists, tuples, sets, dicts are containers.\n",
"\n",
"On a dict `in` says if a key belongs to the dictionary. \n",
"On a string `in` says if the left operand is a substring of the right operand."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4491550f",
"metadata": {},
"outputs": [],
"source": [
"print(f'{\"a\" in {\"a\": 97} = }', f'{\"ab\" in \"cabala\" = }', f\"{10 in [1,2,3] = }\", sep = '\\n')"
]
},
{
"cell_type": "markdown",
"id": "ec7a590d",
"metadata": {},
"source": [
"## Ranges"
]
},
{
"cell_type": "markdown",
"id": "02dcf4a7",
"metadata": {},
"source": [
"The class `range` creates an iterable that represent a range of integer. \n",
"With one argument $n$ the range is $0,\\dots n-1$. \n",
"With two arguments $n, m$ the range is $n,\\dots m-1$. \n",
"With three arguments $n, m, k$ the range is $n,\\dots m-1$ with steps of $k$."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da4c3a84",
"metadata": {},
"outputs": [],
"source": [
"print(*range(5))\n",
"print(*range(2,5))\n",
"print(*range(-2,5,2))\n",
"print(*range(3,-1,-1))"
]
},
{
"cell_type": "markdown",
"id": "73bf6dc8",
"metadata": {},
"source": [
"## Tuples and lists"
]
},
{
"cell_type": "markdown",
"id": "573eac1c",
"metadata": {},
"source": [
"`tuple` and `list` we have already encountered. \n",
"Both classes can be called on an iterable to make a tuple or list with the elements of the iterable.\n",
"\n",
"The elements of a tuple or list can be indexed with `[`...`]`. The same notation accepts up to three numbers separated by `:` and produces a tuple / list indexed by the indeces as produces by `range`. \n",
"This accessing is called **slice**."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff0049b3",
"metadata": {},
"outputs": [],
"source": [
"l = list(range(0,31,10))\n",
"print(l)\n",
"print(l[2:-1])\n",
"t = tuple(l)\n",
"print(t)\n",
"print(t[::-1])"
]
},
{
"cell_type": "markdown",
"id": "8b5d44d7",
"metadata": {},
"source": [
"A list has many methods available, the most used is surely `append` that add its argument at the end of the list."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "83a09e86",
"metadata": {},
"outputs": [],
"source": [
"*l, = range(3)\n",
"l.append(10)\n",
"del l[0]\n",
"l"
]
},
{
"cell_type": "markdown",
"id": "9faf912b",
"metadata": {},
"source": [
"## Sets and frozensets"
]
},
{
"cell_type": "markdown",
"id": "f1b2a82f",
"metadata": {},
"source": [
"`set` and `frozenset` are classes used to represent finite sets, frozensets are immutable. \n",
"Both requires their elements to be hashable.\n",
"\n",
"A _non-empty_ list of expressions enclosed by curly braces is a literal for a set (`{}` is a `dict`, to make an empty set use `set()`).\n",
"\n",
"A set has a method `add` to insert an element."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ebe67dc",
"metadata": {},
"outputs": [],
"source": [
"res = set()\n",
"for i in range(13):\n",
" for j in range(13):\n",
" if i * j == 12:\n",
" res.add(frozenset((i,j)))\n",
"\n",
"res"
]
},
{
"cell_type": "markdown",
"id": "fc0585bf",
"metadata": {},
"source": [
"## `map` and comprehensions"
]
},
{
"cell_type": "markdown",
"id": "59d47c54",
"metadata": {},
"source": [
"Sometimes one wants to do the same thing to multiple times and for that a loop is a good solutions.\n",
"\n",
"The function `map` provides a different point ov view on the idea idea of doing something while iterating on a collection: it takes a callable (usually a lambda) and an iterable and produces an iterator. \n",
"To generate the next object, the iterator will:\n",
"- take the next element of the collection\n",
"- call che callable on the element\n",
"- return the result\n",
"\n",
"The main difference over a normal loop is that iterators are lazy so the computation will be done only when required."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6696e013",
"metadata": {},
"outputs": [],
"source": [
"def f(x):\n",
" print(f\"-- doing some computation on {x}\")\n",
" return x**2\n",
"\n",
"l = list(range(-4,4))\n",
"print(f\"{l = }\")\n",
"it = map(f, l)\n",
"print(f\"{it = }\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "77171724",
"metadata": {},
"outputs": [],
"source": [
"print(f\"{next(it) = }\")\n",
"print(f\"{next(it) = }\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6aa130f4",
"metadata": {},
"outputs": [],
"source": [
"{*it}"
]
},
{
"cell_type": "markdown",
"id": "f3ebb575",
"metadata": {},
"source": [
"A **generator expression** is a special notation that behaves as a map with a lambda..."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d032f197",
"metadata": {},
"outputs": [],
"source": [
"it = map(lambda x: x**2, range(-4,4))\n",
"print(*it)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af702deb",
"metadata": {},
"outputs": [],
"source": [
"it = (x**2 for x in range(-4,4))\n",
"print(*it)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2f3fb0d6",
"metadata": {},
"outputs": [],
"source": [
"\" \".join(f\"{n}...\" for n in range(3,0,-1))"
]
},
{
"cell_type": "markdown",
"id": "fb9f9bb2",
"metadata": {},
"source": [
"... that also support filtering"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f464308",
"metadata": {},
"outputs": [],
"source": [
"it = (x**2 for x in range(-4,4) if x%2 == 0)\n",
"print(*it)"
]
},
{
"cell_type": "markdown",
"id": "3ab9edc9",
"metadata": {},
"source": [
"**List comprehensions** and **set comprehensions** are special notations to produce lists and sets, respectively, using a generator expression."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6e0ba04a",
"metadata": {},
"outputs": [],
"source": [
"[x**2 for x in range(-4,4) if x%2 == 0]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a60294bc",
"metadata": {},
"outputs": [],
"source": [
"{x**2 for x in range(-4,4) if x%2 == 0}"
]
},
{
"cell_type": "markdown",
"id": "56f45dd6",
"metadata": {},
"source": [
"A **dict comprehension** is similar to a set comprehension but produces a dictionary and requires a key-value pair as the \"lambda\" expression. \n",
"To distinguish them from set comprehensions they have two expressions separated by a `:`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "57e893aa",
"metadata": {},
"outputs": [],
"source": [
"{x: x**2 for x in range(-4,4) if x%2 == 0}"
]
},
{
"cell_type": "markdown",
"id": "a59ce561",
"metadata": {},
"source": [
"## Classes"
]
},
{
"cell_type": "markdown",
"id": "9607675f",
"metadata": {},
"source": [
"Objects have a type and that type says how to interact with the object. \n",
"In Python almost everything is an object, and the objects that represent types are the **class object**. \n",
"Class objects are callable and they produce an object; sensible ones produce an object of the type they represent, such objects are called **instances**.\n",
"\n",
"Actually it is the set of attribute of an object that says how to interact with it but many of those are provided by the class of the object. \n",
"In fact an object has a namespace which names, the attributes of the object, can be accessed using the `.` operator. \n",
"We have seen that when object has a `__dict__` attribute there will be stored part of the namespace and that part can be modified. \n",
"The rest of the namespace is determined by the type of the object.\n",
"\n",
"To create a simple class object:\n",
"```\n",
"class name:\n",
" class-block\n",
"```\n",
"the class block will be run immediately and defines the namespace shared by the instances. \n",
"A modification to an instance namespace will be stored on the `__dict__` of that instance while a modification to che class namespace will refplect on all instances."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ec987e95",
"metadata": {},
"outputs": [],
"source": [
"class A:\n",
" \"This is a class\"\n",
" print(\"-- the class body is running\")\n",
" x = 42\n",
" def f(something):\n",
" return something.x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a3906a12",
"metadata": {},
"outputs": [],
"source": [
"a = A()\n",
"b = A()\n",
"print(A.x, a.x, b.x, sep = '\\t')\n",
"a.x = 1729\n",
"print(A.x, a.x, b.x, sep = '\\t')\n",
"A.x = 1024\n",
"print(A.x, a.x, b.x, sep = '\\t')\n",
"print(\"\", f\"{vars(a) = }\", f\"{vars(b) = }\", f\"{vars(A) = }\", sep = '\\n')"
]
},
{
"cell_type": "markdown",
"id": "1980f3b1",
"metadata": {},
"source": [
"A function defined in a class will be wrapped when accessed by an instance, the resulting object is a callable that when called calls the function with the instance as the first argument. \n",
"A function defined in a class is the most common way to define a method.\n",
"\n",
"A strong convention says that the first argument name, that will be bound to the instance, has to be `self`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0334ffdf",
"metadata": {},
"outputs": [],
"source": [
"print(f\"{A.f = }\", f\"{a.f = }\", f\"{b.f = }\", \"\", sep = '\\n')\n",
"print(a.f(), b.f())\n",
"print(A.f(a), A.f(b))"
]
},
{
"cell_type": "markdown",
"id": "73e3bd33",
"metadata": {},
"source": [
"Some methods with special names are used by the interpreter to implement some basic operations of the language as equality comparison or arithmetic operations. \n",
"Those methods have names that begin and end with a double underscore so they are named **dunder methods** or **magic methods**.\n",
"\n",
"In particular the `__init__` method is used to initialize an instance object as soon as it is created and any argument to a class call will be passed to the init method (after the first one that will be the uninitialised object)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "029af153",
"metadata": {},
"outputs": [],
"source": [
"class A:\n",
" def __init__(self, x = 42):\n",
" self.x = x\n",
" def __add__(self, other):\n",
" return f\"{self.x}: {other.x}\"\n",
"\n",
"a = A(\"answer\")\n",
"b = A()\n",
"\n",
"print(f\"{vars(A) = }\", f\"{vars(a) = }\", f\"{vars(b) = }\", sep = '\\n')\n",
"a + b"
]
},
{
"cell_type": "markdown",
"id": "3648bc71",
"metadata": {},
"source": [
"A type can be defined to represent a special subset of the values represented by another type. \n",
"The corresponding class is a subclass of the other and that relation can be evaluated by the `issubclass` function. \n",
"An attribute will be searched first of all in the `__dict__` of the object then in the namespace of the classes starting from the `type` of the object going up in the subclass relation.\n",
"\n",
"A class can be \"child\" of more than one class so the relation of subclass is not linear. A linearized inheritance line is provided by the `mro` (**method resolution order**) method of a class; that line is used in the attribute lookup."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2b8c88b8",
"metadata": {},
"outputs": [],
"source": [
"class A:\n",
" x, y, z = 10, 20, 30\n",
"\n",
"class B:\n",
" y, z, w = 200, 300, 400\n",
"\n",
"class C(A, B):\n",
" v, z = 123, 3000\n",
"\n",
"c = C()\n",
"c.v = 42\n",
"print(C.mro())\n",
"c.v, c.x, c.y, c.z, c.w"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "308165d7",
"metadata": {},
"outputs": [],
"source": [
"print(f\"{type(c) = }\")\n",
"{Y.__name__: isinstance(c, Y) for Y in type(c).mro()}"
]
},
{
"cell_type": "markdown",
"id": "0997ad79",
"metadata": {},
"source": [
"## Decorators"
]
},
{
"cell_type": "markdown",
"id": "dad8c1df",
"metadata": {},
"source": [
"A class or function definition can be preceded by `@` and an expression that gives a callable.\n",
"\n",
"```\n",
"@callable\n",
"def f(...):\n",
" ...\n",
"```\n",
"is equivalent to:\n",
"```\n",
"@callable\n",
"def f(...):\n",
" ...\n",
"f = callable(f)\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5d85e7db",
"metadata": {},
"outputs": [],
"source": [
"def fib(n):\n",
" print(f\"computing fib of {n}\")\n",
" if n < 0:\n",
" raise ValueError()\n",
" if n < 2:\n",
" return n\n",
" return fib(n-1) + fib(n-2)\n",
"\n",
"fib(5)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bc0abe72",
"metadata": {},
"outputs": [],
"source": [
"def memoize(f):\n",
" memory = {}\n",
" def wrapper(n):\n",
" if n in memory:\n",
" return memory[n]\n",
" else:\n",
" value = f(n)\n",
" memory[n] = value\n",
" return value\n",
" return wrapper\n",
"\n",
"@memoize\n",
"def fib(n):\n",
" print(f\"computing fib of {n}\")\n",
" if n < 0:\n",
" raise ValueError()\n",
" if n < 2:\n",
" return n\n",
" return fib(n-1) + fib(n-2)\n",
"\n",
"fib(5)"
]
},
{
"cell_type": "markdown",
"id": "4406033a",
"metadata": {},
"source": [
"A useful decorator is `staticmethod` that, in a class definition, changes a method such that it is not wrapped when called from an instance."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "05629c66",
"metadata": {},
"outputs": [],
"source": [
"class A:\n",
" @staticmethod\n",
" def f(x):\n",
" print(f\"method called with argument {x}\")\n",
"\n",
"a = A()\n",
"a.f(5)\n",
"a.f"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3e13f83d",
"metadata": {},
"outputs": [],
"source": [
"a.f()"
]
},
{
"cell_type": "markdown",
"id": "3dbec494",
"metadata": {},
"source": [
"# >>> Homework 2"
]
},
{
"cell_type": "markdown",
"id": "500cfc21",
"metadata": {},
"source": [
"In the file `tic_tac_toe.py` there are some functions to play the game noughts and crosses. \n",
"When run directly (`python3 tic_tac_toe.py`) the function `main` is called and a game starts.\n",
"\n",
"Try it out! \n",
"(try: `1 1`, `2 1`, `2 2` if you want to win)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f1d0f709",
"metadata": {},
"outputs": [],
"source": [
"from tic_tac_toe import main\n",
"help(main)\n",
"main()"
]
},
{
"cell_type": "markdown",
"id": "7227f84e",
"metadata": {},
"source": [
"Inspect the source file and tinker with it.\n",
"\n",
"Then implement a function that:\n",
"- asks the number of human players (even 0)\n",
"- asks the player names\n",
"- starts a game\n",
"- when the game ends asks to play again."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "05479c9a",
"metadata": {},
"outputs": [],
"source": [
"import tic_tac_toe as ttt\n",
"\n",
"def prompt_and_play():\n",
" n = int(input(\"Number of players: \"))\n",
" ...\n",
" raise NotImplementedError(\"write the code!\")\n",
" ...\t# ask for names and create the player functions\n",
" again = True\n",
" while again:\n",
" ttt.game(player1, player2)\n",
" ...\t# ask to play again\n",
" again = answer.lower() in \"yes\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e412ec2d",
"metadata": {},
"outputs": [],
"source": [
"# remember ro reload the previous cell before running this one\n",
"prompt_and_play()"
]
},
{
"cell_type": "markdown",
"id": "6ceebef1",
"metadata": {},
"source": [
"There are no global variables where a state can be stored but the engine seems to learn... where is the knowledge stored between different calls to the function `engine`?\n",
"\n",
"When called with the parameter `verbose` set to True the engine \"explains\" its reasoning (the default value is False), you can use that functionality to find out that there is actually new informations on new calls. \n",
"Note that there is no source of randomness; the answers are exactly the same when the whole program is re-run.\n",
"\n",
"The `engine` function calls itself to explore the tree of moves and passes along, as `memory` parameter, the same object that has been passed to it (and the default value is a tuple of empty dictionaries, isn't it?). \n",
"The `fuel` argument is reduced to sub-calls, otherwise the engine would never lose (by [Zermelo's theorem](https://en.wikipedia.org/wiki/Zermelo%27s_theorem_%28game_theory%29) in at least one of the starting positions, for this specific game in both)."
]
},
{
"cell_type": "markdown",
"id": "66648d96",
"metadata": {},
"source": [
"
Thank you!
\n",
"\n",
"
\n",
"\n",
"\n",
"\n",
"
Licensed under Creative Commons Attribution-ShareAlike 4.0 International \n",
" Notebook source code available in this repo