Skip to content

Commit

Permalink
feat: update
Browse files Browse the repository at this point in the history
  • Loading branch information
uhasker committed Jan 1, 2024
1 parent 30e45c9 commit 68a3254
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 59 deletions.
53 changes: 31 additions & 22 deletions src/03-functions/chapter.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"source": [
"The most important concept in Python and arguably in all of programming is that of a **function**. Functions are *reusable blocks of code*.\n",
"\n",
"Consider the following example. We are writing an application for handling tournaments and want to compute player ratings using the [ELO rating system](https://en.wikipedia.org/wiki/Elo_rating_system). This system works by first calculating the probabilities of the players winning (i.e. the expected scores) and then updating the player's rating from those probabilities and their old ratings.\n",
"Consider the following example. We are writing an application for handling tournaments and want to compute player ratings using the [ELO rating system](https://en.wikipedia.org/wiki/Elo_rating_system). This system works by first calculating the probabilities of the players winning (i.e. the expected scores) and then updating the player's ratings from those probabilities and their old ratings.\n",
"\n",
"Consider an example with two players, where the first player has the rating `1000` and the second player has the rating `1500`. Let's store the rating of the first player in a variable `rating1` and the rating of the second player in a variable `rating2`:"
]
Expand Down Expand Up @@ -72,7 +72,7 @@
"id": "bd2ca6f0",
"metadata": {},
"source": [
"Don't spend too much time worrying about the details of this formula. Basically, the higher the difference between `rating2` and `rating1`, the lower the expected score of the first player should be. After all a high value of `rating2 - rating1` means that the first player has a substantially lower rating than the second player, i.e. we don't expect the first player to achieve a high score.\n",
"Don't spend too much time worrying about the details of the formula. Basically, the higher the difference between `rating2` and `rating1`, the lower the expected score of the first player should be. After all a high value of `rating2 - rating1` means that the first player has a substantially lower rating than the second player, i.e. we don't expect the first player to achieve a high score.\n",
"\n",
"Additionally, the score is normalized to be between `0` and `1`. An expected score of `0` means that the first player has no chance of winning. An expected score of `1` means that the first player will definitely win.\n",
"\n",
Expand Down Expand Up @@ -233,7 +233,7 @@
"id": "b868a24b",
"metadata": {},
"source": [
"We **define** a function using the `def` keyword followed by the function name and parentheses `()`. After that a **function body** follows which contains the function implementation (i.e. the code that we want to store inside the function).\n",
"We **define** a function using the `def` keyword followed by the function name and parentheses `()`. This should be followed by a **function body** which contains the function implementation (i.e. the code that we want to store inside the function).\n",
"\n",
"For example, here is a function that prints a greeting. This function is not super useful, but it will serve to illustrate a few important concepts:"
]
Expand Down Expand Up @@ -296,7 +296,7 @@
"id": "28a2b127",
"metadata": {},
"source": [
"> PEP8 note: Code should always be indented using *4 spaces*. However when working in a REPL it's common practice to indent code using tabs, because you need to type less characters that way. In addition editors like PyCharm will automatically insert 4 spaces when you press the tab key.\n",
"> PEP8 note: Code should always be indented using *4 spaces*. However, when working in a REPL it's common practice to indent code using tabs, because you need to type less characters that way. In addition, you can configure most code editors to automatically insert 4 spaces when you press the tab key.\n",
"\n",
"If the function body has multiple statements, you must *indent them all*:"
]
Expand Down Expand Up @@ -338,7 +338,7 @@
"id": "6bf4ed9f",
"metadata": {},
"source": [
"We can execute the function body with a **function call**. To call a function we write down the function name followed by parentheses `()`. No `def` keyword is present in a function call:"
"We can execute the function body with a **function call**. To call a function we write down the function name followed by parentheses `()`:"
]
},
{
Expand Down Expand Up @@ -368,7 +368,7 @@
"source": [
"Hooray! We just wrote our very first function! Go tell all your friends about it!\n",
"\n",
"However to make our functions useful, we should give them parameters that we can use to *customize their behaviour*."
"To make our functions more useful, we should give them parameters that we can use to *customize their behaviour*."
]
},
{
Expand All @@ -386,12 +386,12 @@
"source": [
"Functions can take **parameters** between the parentheses which allow us to pass additional values to the function in a function call. These parameters may then be used inside the function body just like regular variables.\n",
"\n",
"Consider a function that should print a greeting given a name. We would write it like this:"
"Consider a function that should print a greeting which contains a name. We would write it like this:"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"id": "22924916",
"metadata": {},
"outputs": [],
Expand Down Expand Up @@ -500,7 +500,7 @@
"id": "e0737b50",
"metadata": {},
"source": [
"The number of arguments must be exactly equal to the number of parameters. Passing more or less arguments won't do. For example, if we pass too few arguments, Python will *calmly* inform us that we are missing arguments:"
"The number of arguments must be exactly equal to the number of parameters. Passing more or less arguments won't do. For example, if we pass too few arguments, Python will *calmly* yell at us that we are missing arguments:"
]
},
{
Expand Down Expand Up @@ -560,7 +560,7 @@
"source": [
"Now these arguments are **keyword arguments** because the parameters they will be assigned to are determined from the argument names. This means that `\"John\"` will be assigned to `first_name` and `\"Doe\"` will be assigned to `last_name`.\n",
"\n",
"If we use keyword arguments, the order of the arguments plays no role. For example this function call is equivalent to the preceding function call: "
"If we use keyword arguments, the order of the arguments plays no role. For example, this function call is equivalent to the preceding function call: "
]
},
{
Expand All @@ -578,7 +578,7 @@
"id": "418000a3",
"metadata": {},
"source": [
"However if we use positional arguments, the order of arguments *does play a role*. For example this function call is *not* equivalent to the previous function calls:"
"If we use positional arguments, the order of arguments *does play a role*. For example this function call is *not* equivalent to the previous function calls:"
]
},
{
Expand Down Expand Up @@ -703,15 +703,23 @@
"source": [
"Something that often trips up beginners is that they confuse `print` and `return`. However these are *two completely different and unrelated things*. The first one - `print` - is a *function which outputs a value to the console*. The second one - `return` - is a *keyword which allows a function to return a value to the code that calls it*.\n",
"\n",
"Therefore `print_greeting` and `get_greeting` are two completely different functions. The function `print_greeting` prints the greeting and doesn't return anything. For example if we try to assign a value to the result of `print_greeting`, this happens:"
"Therefore `print_greeting` and `get_greeting` are two completely different functions. The function `print_greeting` prints the greeting and doesn't return anything useful. For example if we try to assign a value to the result of `print_greeting`, this happens:"
]
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"id": "e2520e02",
"metadata": {},
"outputs": [],
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Hello John\n"
]
}
],
"source": [
"another_greeting = print_greeting(\"John\")"
]
Expand Down Expand Up @@ -777,7 +785,7 @@
"id": "a761d665",
"metadata": {},
"source": [
"This is also the point at which we note that returning a value from a function is *far more common* than printing something. After all, if we *print* a value, we can't do anything useful with the output later on. However, if we *return* a value, we can assign that value to a variable and manipulate it further. Here is a pattern you will see quite often:"
"This is the point at which we note that returning a value from a function is *far more common* than printing something. After all, if we *print* a value, we can't do anything useful with the output later on. However, if we *return* a value, we can assign that value to a variable and manipulate it further. Here is a pattern you will see quite often:"
]
},
{
Expand Down Expand Up @@ -820,15 +828,15 @@
"id": "159de4c4",
"metadata": {},
"source": [
"However if we would have printed `full_name` instead of returning it we wouldn't have been able to pass it to `get_greeting`."
"However, if we would have printed `full_name` instead of returning it we wouldn't have been able to pass it to `get_greeting`."
]
},
{
"cell_type": "markdown",
"id": "1cd99e40",
"metadata": {},
"source": [
"## Writing a complex function"
"## Writing a Complex Function"
]
},
{
Expand Down Expand Up @@ -978,8 +986,9 @@
" \"\"\"\n",
" Calculate the expected scores of two players given their ratings.\n",
" \n",
" The expected scores are calculated using the formula\n",
" E = 1 / (1 + 10 ** ((RA - RB) / 400)).\n",
" The expected scores are calculated using the standard ELO formula\n",
" E = 1 / (1 + 10 ** ((R_A - R_B) / 400)).\n",
" R_A and R_B are the ratings of the players respectively.\n",
" \"\"\"\n",
" expected1 = 1 / (1 + 10 ** ((rating2 - rating1) / 400))\n",
" expected2 = 1 / (1 + 10 ** ((rating1 - rating2) / 400))\n",
Expand All @@ -1002,7 +1011,7 @@
"id": "d4d57c64",
"metadata": {},
"source": [
"## Useful built-in functions"
"## Useful Built-in Functions"
]
},
{
Expand All @@ -1012,7 +1021,7 @@
"source": [
"There is a number of useful built-in functions.\n",
"\n",
"In fact you already encountered two of them - namely `print` and `type`:"
"In fact, you already encountered two of them - namely `print` and `type`:"
]
},
{
Expand Down Expand Up @@ -1040,7 +1049,7 @@
"id": "cb23797d",
"metadata": {},
"source": [
"However `print` and `type` are not the only useful built-in functions.\n",
"However, `print` and `type` are not the only useful built-in functions.\n",
"\n",
"For example you can use the `int` function (not to be confused with the `int` data type) to convert a value to an integer:"
]
Expand Down
64 changes: 43 additions & 21 deletions src/04-classes/chapter.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"source": [
"An **object** is a data structure that contains *state* and *behaviour*.\n",
"\n",
"Before we can create objects, we (usually) need to define a **class** which will be our *a template for creating objects*. The objects which we create from that template are called **instances** of the class.\n",
"Before we can create objects, we (usually) need to define a **class** which will be our *template for creating objects*. The objects which we create from that template are called **instances** of the class.\n",
"\n",
"Consider the following example:\n",
"\n",
Expand Down Expand Up @@ -52,11 +52,11 @@
"source": [
"Here the state consists of the variables `pos` and `speed`. Generally speaking, variables that are associated with objects (and represent its state) are called **attributes**.\n",
"\n",
"The `__init__` function (commonly referred to as **constructor**) is a special function which **initializes** an object with some *initial state*.\n",
"> PEP8 note: Class names should normally use the CapWords convention, e.g. `Ball` or `MyAwesomeClass`.\n",
"\n",
"> PEP8 Note: Class names should normally use the CapWords convention, e.g. `Ball` or `MyAwesomeClass`.\n",
"The `__init__` function (commonly referred to as **constructor**) is a special function which **initializes** an object with some *initial state*.\n",
"\n",
"For example if we want to create a ball with the position `10` and with speed `8`, we write:"
"For example if we want to create a ball with the position `10` and the speed `8`, we write:"
]
},
{
Expand Down Expand Up @@ -183,7 +183,7 @@
"\n",
"We can define functions on classes - these functions are called **methods** and define the *behaviour* of the objects of that class. The first parameter of every method is an instance of the current object. By convention this parameter is called `self`.\n",
"\n",
"For example here is how we could implement a `move` method on the `Ball` class:"
"For example, here is how we could implement a `move` method on the `Ball` class:"
]
},
{
Expand All @@ -207,9 +207,9 @@
"id": "a21552e9",
"metadata": {},
"source": [
"Here `self` will be the ball object the method is invoked on. For example if we call `move` on the object `my_ball` then `self` will be `my_ball`. If we call `move` on the object `my_other_ball` then `self` will be `my_other_ball`.\n",
"Here, `self` will be the ball object the method is invoked on. For example if we call `move` on the object `my_ball` then `self` will be `my_ball`. If we call `move` on the object `my_other_ball` then `self` will be `my_other_ball`.\n",
"\n",
"Note if you are working in the REPL, then after creating the new class you need to *recreate* the `my_ball` object, otherwise you will still be using the old class!"
"If you are working in the REPL, after creating the new class you need to *recreate* the `my_ball` object, otherwise you will still be using the old class!"
]
},
{
Expand Down Expand Up @@ -250,15 +250,15 @@
"id": "0ce938a4",
"metadata": {},
"source": [
"## The \\_\\_str\\_\\_ and \\_\\_repr\\_\\_ Methods"
"## The `__str__` and `__repr__` Methods"
]
},
{
"cell_type": "markdown",
"id": "06bc86fd",
"metadata": {},
"source": [
"Quite often, we want to obtain a string representation of an object (e.g. using the `print` function) that shows its current state. Unfortunately at the moment, if we try to print or get the representation of an object, we currently get a string that's pretty useless."
"Quite often, we want to obtain a string representation of an object (e.g. using the `print` function) that shows its current state. Unfortunately, at the moment, if we try to print or get the representation of an object, we will receive a string that's pretty useless."
]
},
{
Expand Down Expand Up @@ -286,15 +286,15 @@
"id": "692f8c9e",
"metadata": {},
"source": [
"> The weird string at the end of the output (yours will probably be different) is the memory address of the object represented as a hexadecimal number.\n",
"> The weird string at the end of the output (yours will probably be different) is the memory address of the object represented as a hexadecimal number (because we use the CPython runtime).\n",
"\n",
"Luckily, Python allows us to change this behaviour. Classes may implement two special methods called `__str__` and `__repr__`.\n",
"\n",
"The `__str__` method produces a human-readable string for consumption by the end user. The `__repr__` method produces an *unambiguous representation* of the object. Basically `__str__` is intended to produce an \"informal\" representation of an object, while `__repr__` is intended to produce a *formal representation* of an object.\n",
"\n",
"This is reflected in the circumstances under which these methods are called. For example if you just output an object in the REPL, you will see the output of a call to the `__repr__` method of that object. If you `print` an object, you will see the result of a call to the `__str__` method of that object.\n",
"\n",
"This is because in the REPL we generally want to see an *unambiguous representation* of our objects. On the other hand, when we call `print` we usually want a human-readable string that displays the information we primarily care about.\n",
"This makes sense - in the REPL we generally want to see an *unambiguous representation* of our objects. On the other hand, when we call `print` we usually want a human-readable string that displays the information we primarily care about.\n",
"\n",
"These two methods often have the same implementation (since the end user often wants to see the *unambiguous representation*), but this doesn't have to be the case. For example, we could argue, that the end user doesn't care about the speed of the ball and only cares about its current position. Then the `__str__` method would return a string that doesn't contain the `speed` attribute. However the `__repr__` method should still return all attributes:"
]
Expand Down Expand Up @@ -395,7 +395,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"id": "5c5dc8a8",
"metadata": {},
"outputs": [],
Expand Down Expand Up @@ -482,7 +482,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 3,
"id": "badb7d56",
"metadata": {},
"outputs": [],
Expand Down Expand Up @@ -525,7 +525,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 4,
"id": "5ac84166",
"metadata": {},
"outputs": [],
Expand All @@ -543,10 +543,21 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 5,
"id": "73b7dacf",
"metadata": {},
"outputs": [],
"outputs": [
{
"data": {
"text/plain": [
"Ball(pos=26, speed=8)"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ball1"
]
Expand All @@ -561,10 +572,21 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 6,
"id": "e4b9ed84",
"metadata": {},
"outputs": [],
"outputs": [
{
"data": {
"text/plain": [
"Ball(pos=10, speed=8)"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ball2"
]
Expand Down Expand Up @@ -603,7 +625,7 @@
"id": "bda77c96",
"metadata": {},
"source": [
"This means that whenever we make a change to that object using the name `ball1`, that change will be visible when we access the object using the name `ball2` as well:"
"This means that whenever we make a change to that object using the name `ball1`, that change will be visible when we access the object using the name `ball2`. Consider this statement:"
]
},
{
Expand Down Expand Up @@ -679,7 +701,7 @@
"source": [
"We can also see that `ball1` and `ball2` are the same object using the `id` function. Generally speaking, this function hands us a number that is unique for each object (for the lifetime of that object). In the Python interpreter we downloaded in chapter 1 (which is the CPython interpreter) this is achieved by returning the **memory address** of the object.\n",
"\n",
"In this case `ball1` and `ball2` point to the same object, so their memory address is the same:"
"In this case `ball1` and `ball2` point to the same object, so their memory addresses are the same:"
]
},
{
Expand Down Expand Up @@ -717,7 +739,7 @@
"id": "01a27019",
"metadata": {},
"source": [
"However if we have two names that refer to different objects, then their memory address will not be the same and the `is` operator will return `False` even *if the object attributes are all equal*:"
"However, if we have two names that refer to different objects, then their memory addresses will not be the same and the `is` operator will return `False` even *if the object attributes are all equal*:"
]
},
{
Expand Down
Loading

0 comments on commit 68a3254

Please sign in to comment.