Skip to content

Commit

Permalink
fix: some touchups before class
Browse files Browse the repository at this point in the history
  • Loading branch information
henryiii committed Jan 10, 2022
1 parent ebc1a18 commit 1d2ab3c
Show file tree
Hide file tree
Showing 11 changed files with 532 additions and 128 deletions.
10 changes: 4 additions & 6 deletions notebooks/0 Intro.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
"\n",
"[![Henryiii's github stats](https://github-readme-stats.vercel.app/api?username=henryiii)](https://github.com/anuraghazra/github-readme-stats)\n",
"\n",
"Most important link: <https://iscinumpy.gitlab.io>\n",
"Most important link: <https://iscinumpy.dev>\n",
"\n",
"[Scikit-HEP](https://scikit-hep.org) admin, [scikit-build](https://github.com/scikit-build) admin, member of [IRIS-HEP](https://iris-hep.org). [PyPA](https://github.com/pypa) member.\n",
"[PyPA](https://github.com/pypa) member. [Scikit-HEP](https://scikit-hep.org) admin, [scikit-build](https://github.com/scikit-build) admin, member of [IRIS-HEP](https://iris-hep.org).\n",
"\n",
"### Projects\n",
"\n",
Expand Down Expand Up @@ -163,9 +163,7 @@
"source": [
"## Notebooks\n",
"\n",
"We will be using notebooks today. Notebooks are fantastic for teaching, quick experimentation, for developing, or for driving a final analysis product. They are not for serious programming - that happens in `.py` files. Once you write something and get it working, move it to a `.py` file and add a test. Then import it into your notebook!\n",
"\n",
"We'll be in JupyterLab 3; it's fairly new and there have been a few bugs related to code completion. Hopefully everything will be smoothed out soon. (IPython and the Jedi library seem to be at odds)."
"We will be using notebooks today. Notebooks are fantastic for teaching, quick experimentation, for developing, or for driving a final analysis product. They are not for serious programming - that happens in `.py` files. Once you write something and get it working, move it to a `.py` file and add a test. Then import it into your notebook!"
]
},
{
Expand All @@ -174,7 +172,7 @@
"source": [
"## Python version\n",
"\n",
"Also, everything will be in Python 3.9; I'll try to point out when something is newer than 3.7, but we'll be running in 3.9. [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html) mandates that data science libraries currently support 3.8+ (support dropped 42 months after release), while [general Python EOL is 3.7+](https://endoflife.date/python) (5 year support window).\n",
"Everything (minus pattern matching) will be in Python 3.9; I'll try to point out when something is newer than 3.7, but we'll be running in 3.9. [NEP 29](https://numpy.org/neps/nep-0029-deprecation_policy.html) mandates that data science libraries currently support 3.8+ (support dropped 42 months after release), while [general Python EOL is 3.7+](https://endoflife.date/python) (5 year support window).\n",
"\n",
"Key upcoming dates:\n",
"\n",
Expand Down
110 changes: 97 additions & 13 deletions notebooks/1.1 Memory Model.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,33 @@
"outputs": [],
"source": [
"def f(x: float) -> float:\n",
" \"I am a square!\"\n",
" return x ** 2\n",
"\n",
"\n",
" \"\"\"I am a square!\"\"\"\n",
" return x ** 2"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The help of an object includes its signature and its docstring:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"help(f)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can see a list of methods (or use `<tab>` in iPython or the Python REPL, but underscored methods often require you start by typing an underscore first):"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand All @@ -144,6 +164,13 @@
"dir(f)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The inspect module is a built-in module that can provide a lot of other information:"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down Expand Up @@ -190,12 +217,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"> Try adding different keyword arguments to `rich.inspect`."
"> Try adding different keyword arguments to `rich.inspect`. Shift-tab in IPython to see options. `methods=True` on the int, for example."
]
},
{
"cell_type": "markdown",
"metadata": {},
"metadata": {
"tags": []
},
"source": [
"## Mutability\n",
"\n",
Expand All @@ -222,7 +251,8 @@
"source": [
"> Add an `f` before the string to see the answer (remove the \"=\" inside the brackets for Python <3.8).\n",
"\n",
"Now, let's try a mutable object. Lists, sets, and dicts are the most notable mutable objects in `builtins`."
"`bool`, `int`, `float`, `str`, `bytes`, `tuple`, and `frozenset` are immutable built-ins in Python. Singletons (like `None`, `Ellipsis`, `True`, and `False`) are immutable, too.\n",
"Now, let's try a mutable object. `list`, `set`, `dict`, and generic classes/objects are mutable."
]
},
{
Expand All @@ -243,14 +273,42 @@
"source": [
"Why?\n",
"\n",
"The problem was that when the object was immutable, the inplace operator `+=` actually behaved like `x = x + 1`, which is a new object. When it was a mutable object (a list), then it was able to change it in-place."
"The problem was that when the object was immutable, it does not define in-place operations. Inplace operators like `+=` actually fall back to out-of-place operations and assignment, like `x = x + 1`; they create new objects. When it was a mutable object (a list), that does have in-place operations, so it was able to change it in-place."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Advanced aside: Why did this work?\n",
"Here's a quick example, showing the fall-back behavior of inplace operations if `__iadd__` is missing:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class Addable:\n",
" def __init__(self, value: int) -> None:\n",
" self.value = value\n",
"\n",
" # Leaving off the return type to avoid discussing it here\n",
" def __add__(self, number: int):\n",
" return Addable(self.value + number)\n",
"\n",
"\n",
"x = Addable(3)\n",
"y = x\n",
"x += 4\n",
"x is y"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Advanced aside: Why did list inplace addition work?\n",
"\n",
"Quick aside for advanced Pythonistas, this is tricky. `x[0]` returns an int. So why is this any different than before? Let's explore, using mock:"
]
Expand All @@ -273,7 +331,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"You can see that this has special support for this syntax, it pulls out the item inside, and sets it, then stores it. There are other special syntax treatments in Python as well, all designed to make the language more friendly and powerful:\n",
"You can see that Python has special support for this syntax, it pulls out the item inside, and sets it, then stores it. There are other special syntax treatments in Python as well, all designed to make the language more friendly and powerful:\n",
"\n"
]
},
Expand Down Expand Up @@ -309,7 +367,7 @@
"x.y = 3 # __setattr__\n",
"```\n",
"\n",
"That's it. These are _not_ valid assignments:\n",
"That's it. These are _not_ valid assignments in Python (they are valid in the C family, for example):\n",
"\n",
"```python\n",
"x(y) = 1 # There is no assignment for __call__\n",
Expand All @@ -329,7 +387,7 @@
"source": [
"## Scope\n",
"\n",
"Python has scope, but not in very many places. Functions and class definitions create scope, and modules have scope. (Generator expressions now have scope too). That's about it. So you can write this:"
"Python has the concept of scope, but not in very many places. Functions and class definitions create scope, and modules have scope. (Generator expressions have scope too in Python 3, though they didn't before). That's about it. So you can write this:"
]
},
{
Expand Down Expand Up @@ -449,7 +507,33 @@
"\n",
"**Always pass variables out explicitly, be cautious with using anything not clearly global in a function.**\n",
"\n",
"This means you should never see `global`, as it's only needed for setting variables. Global read-only variables (the only safe kind) are sometimes ALL_CAPS. (Hint: for typed code, you can add `Final`)."
"This means you should never see `global`, as it's only needed for setting variables. Global read-only variables (the only safe kind) are sometimes ALL_CAPS. (Hint: for typed code, you can add `Final`).\n",
"\n",
"For example, a better way to write the above function:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x = 1\n",
"\n",
"\n",
"def f(x: int) -> int:\n",
" return 2\n",
"\n",
"\n",
"x = f(x)\n",
"print(x)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now it's completely explicit, _at the call site_, that it's going to modify `x`. And, as a bonus, you could even use it on other variables now, not just `x`!"
]
},
{
Expand Down
44 changes: 44 additions & 0 deletions notebooks/1.1b Intro to Classes.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,50 @@
"source": [
"> Aside: This is not at all how you'd normally call this, but this is useful later when reasoning about how other features work, and for a sneaky trick: _any object_ can be passed in, not just an instance of the class (was not true in Python 2). In this case, the object simply needs to have a `.msg` attribute to be used in `Simple.function`. If you are tempted to write a library that provides both free functions and methods to do the same thing, this means they can be one and the same, avoiding duplication for you and learning two APIs for your users."
]
},
{
"cell_type": "markdown",
"id": "1fc445e7-90c5-4df7-aa05-8e4ff3cde464",
"metadata": {},
"source": [
"## Dataclasses\n",
"\n",
"If you don't have much familiarity with classes, you should really consider learning dataclasses pretty early on. This is a simpler, less-boilerplate way to define classes that aligns better with common usage. We will discuss dataclasses in the next section, and we will cover the special syntax bits used later on, but here's an example of the above class as a dataclass:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0595d8ed-4737-419e-95b3-a61432b673c3",
"metadata": {},
"outputs": [],
"source": [
"from dataclasses import dataclass\n",
"\n",
"\n",
"@dataclass\n",
"class SimpleDC:\n",
" msg: str\n",
"\n",
" def function(self) -> None:\n",
" print(self.msg)"
]
},
{
"cell_type": "markdown",
"id": "81728dc9-a11a-406f-bfe4-27eb6e5bba97",
"metadata": {},
"source": [
"The dataclass decorator `@dataclass` transforms the class-level attribute `msg: str` into an `__init__` method and an instance attribute. And it also adds several things we did not add, like a nice `__repr__`, Python 3.10 pattern matching support, support for conversion to other types (like a dict), comparison support (`__eq__`), and more!"
]
},
{
"cell_type": "markdown",
"id": "51f6828c-68a9-41de-9ff9-8f5c15421e60",
"metadata": {},
"source": [
"Dataclasses were added in 3.7 but backported to 3.6 as a library. You can also use the library that dataclasses were designed from, which is still much more powerful: attrs."
]
}
],
"metadata": {
Expand Down
Loading

0 comments on commit 1d2ab3c

Please sign in to comment.