Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions content/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ group project.
[WebAssembly version](https://se-for-sci.github.io/live) β€’
[Binder version](https://mybinder.org/v2/gh/se-for-sci/se-for-sci.github.io/main?urlpath=lab)

Note the WebAssembly version does not have a shell, and `time.sleep` doesn't
work (the web is async).
Note the WebAssembly version does not have a shell, and `time.sleep` returns
instantly instead of sleeping (the web is async).

```{tableofcontents}

Expand Down
2 changes: 1 addition & 1 deletion content/week01_intro/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ easy to read, always strive for readability.
What does someone working on code spend time on? Rewriting code that others have
already written. The action of rewriting code to improve its readability or
performance without changing how it operates is commonly called refactoring.
Other reasons to rewrite code is because it didn't scale or was not flexible
Other reasons to rewrite code are because it didn't scale or was not flexible
enough. It also can feel like you spend a lot of time debugging. Lots of painful
debugging. Having strong unit tests and using version control can simplify or
eliminate some debugging problems.
Expand Down
8 changes: 4 additions & 4 deletions content/week01_intro/practices.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ follow them whenever possible. For example, naming in Python & C++:
If you are in a language that uses a different convention (lowerCamelCase, also
known as dromedaryCase, for example), follow what is used in that language.

For C and C++, you should loop from 0 to 1-len:
For C and C++, you should loop from 0 to n-1 (or equivalently, while i < n):

```cpp
// Good
Expand Down Expand Up @@ -157,11 +157,11 @@ This might do what you expect at first:

```python
my_list = ["start"]
print(add_to_list(my_list))
print(add_end_to_list(my_list))
```

But check the contents of `my_list` afterwards. Even better, try running it with
the default argument (`add_to_list()`) and see what it returns.
the default argument (`add_end_to_list()`) and see what it returns.

Due to the above, it's a convention in Python to never use a mutable structure
(we'll discuss mutation in detail in a few weeks) like a list or a dict for an
Expand All @@ -175,7 +175,7 @@ def add_end_to_list(x=()):

If you do need to mutate arguments, it should be well documented and clear as
possible from the function and argument names. Usually you should not return the
list
list in a mutating function:

```python
def append_end_to_list(x=None):
Expand Down
6 changes: 3 additions & 3 deletions content/week01_intro/programming_basics.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"\n",
"Just to keep the computational model straight, we are using:\n",
"\n",
"* Python 3.12 (3.11+ recommended), a interpreted programming language\n",
"* Python 3.12 (3.11+ recommended), an interpreted programming language\n",
"* A REPL, which **R**eads a line, **E**valuates it, then **P**rints it (in a **L**oop)\n",
" - `print` not needed to see the last un-captured value in a cell\n",
"* IPython, which is an enhancement to make Python more **I**nteractive\n",
Expand Down Expand Up @@ -619,7 +619,7 @@
"f = g(f)\n",
"```\n",
"\n",
"So `g` is a function that takes a function and (hopefully) returns a function, probably a very similar one since you are giving it the same name as the old `f`. In Python 2.5, we gained the ability to write this instead:\n",
"So `g` is a function that takes a function and (hopefully) returns a function, probably a very similar one since you are giving it the same name as the old `f`. In Python 2.4, we gained the ability to write this instead:\n",
"\n",
"```python\n",
"@g\n",
Expand Down Expand Up @@ -679,7 +679,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"It's best to think of this as a \"modifier\" (or \"decorator\") for functions (and classes). Don't worry to much about how it works, and especially how to write them. My favorite is a \"decorator factory\", which is simply a function that returns a function that takes a function that returns a function! But in practice, it just looks like a decorator that takes arguments.\n",
"It's best to think of this as a \"modifier\" (or \"decorator\") for functions (and classes). Don't worry too much about how it works, and especially how to write them. My favorite is a \"decorator factory\", which is simply a function that returns a function that takes a function that returns a function! But in practice, it just looks like a decorator that takes arguments.\n",
"\n",
"Let's look at an example: `functools.lru_cache`:"
]
Expand Down
27 changes: 14 additions & 13 deletions content/week01_intro/python_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ For Linux, if you use your system Python, make sure it is new enough, and never
modify the base environment except though your package manager (generally true,
but more so here). The system Python is really intended for use in other system
packages, and is not intended for you to modify. Modern pip and modern systems
(like Ubuntu 24.04+) now work together to provide safegaurds for this.
(like Ubuntu 24.04+) now work together to provide safeguards for this.

### Virtual environments

Expand All @@ -52,7 +52,7 @@ than one, your virtual environment should be at the root of your project with
the name `.venv`. Never check it into git; it should be listed in your
`.gitignore`. If you have the third-party package `virtualenv`, it's just faster
and the pre-installed pip is updated more regularly than the Python standard
library allows - it has the same interface).
library allows (it has the same interface).

(Notice I didn't mention `python3 -m ensurepip`? That's because you don't need
it with a virtualenv, the virtualenv will come with pip installed, even if it's
Expand Down Expand Up @@ -88,11 +88,11 @@ Virtual environments work great for projects, but what about applications that
you find on PyPI that you want to use? There's a simple solution for this: pipx,
which is pip's counterpart for "executables". When you run
`pipx install <package>`, pipx will create a managed virtual environment for
just that application, and only expose it's applications on the command line. So
just that application, and only expose its applications on the command line. So
`pipx install twine` will allow you to run `twine` anywhere, but you will not be
able to `import twine`, since it really lives in it's own virtual environment.
able to `import twine`, since it really lives in its own virtual environment.

Even better, `pip run <app>` will combine the two steps of installing and
Even better, `pipx run <app>` will combine the two steps of installing and
running an application into one command; pipx will install the app into a
temporary virtual environment (reused if you rerun the same command less than a
week later), and then run it. With `pipx run`, you never have to think about
Expand Down Expand Up @@ -120,11 +120,11 @@ that name.

The last common need is to run a series of commands in a specific environment.
This can be your tests, your documentation, or various other tasks. The original
tool for this is `tox`, but due to it's custom configuration format, the
tool for this is `tox`, but due to its custom configuration format, the
Python-based tool `nox` is recommended instead for newcomers as well as
experienced users.

You write a `noxfile.py` with functions that represent the tasks you want to to
You write a `noxfile.py` with functions that represent the tasks you want to
run. It looks something like this:

```python
Expand Down Expand Up @@ -160,11 +160,12 @@ easier or faster. Here are some popular ones:

- `poetry` - The first major attempt to make a modern package manager. It's
become a bit too opinionated in some areas, like it is the only one to force
you to use it's build-backend, and is behind on following standards.
you to use its build-backend, and is behind on following standards.
- `pdm`: A mostly drop-in replacement for poetry that is more flexible and
follows standards better. It can also do things like install Python for you.
- `hatch`: The only tool in this list that can do multiple environments properly
(uv might later), but also the only one to not have built-in locking yet.
(uv might support this later), but also the only one to not have built-in
locking yet.
- `uv` - The most interesting new tool, it will be covered in depth below.

Each tool has strengths and drawbacks. Before uv, the best tool for projects
Expand All @@ -176,8 +177,8 @@ interesting entry, so let's cover that below.

The team at astral-sh has been developing Rust-based tooling for Python. They
introduced `uv`, which started out as a drop-in replacement for venv, quite
drop-in), and had many long-requested features added (to be fair, uv has has
more dedicated developer time than these other tools combined). Since launching,
drop-in, and had many long-requested features added (to be fair, uv has more
dedicated developer time than these other tools combined). Since launching,
they've also replaced pipx, build, Python installers, and are starting to
replace poetry/pdm. By targeting the stand alone tools first, it's easy to just
use uv for whatever you want faster without fully committing to it like, for
Expand Down Expand Up @@ -245,7 +246,7 @@ using anything besides uv and you aren't going to make SDists/wheels, you can
leave it off. `"hatchling"` is a great, flexible, and extendable backend, but
there is a built in backend as well (shown above) for very simple projects. If
you use hatchling, make sure the import name matches the project name (or look
up it's configuration options if you have a good reason not to match the names,
up its configuration options if you have a good reason not to match the names,
which you don't).

The `[project]` table should always have `name` and `version`, as they are
Expand Down Expand Up @@ -342,7 +343,7 @@ there are several installers like "miniforge" and "mambaforge" that are just
conda or mamba with conda-forge set as the default channel; you can do this
yourself with the base tools, and pixi already defaults to conda-forge.

Note that conda-forge has it's own compiler toolchain. You generally should not
Note that conda-forge has its own compiler toolchain. You generally should not
be compiling code with conda-forge packages; if you have to, make sure you get
the compiler toolchain from conda-forge as well. Wheels mostly work, but you
lose the advantages of conda's shared libraries. If you are just using conda to
Expand Down
14 changes: 7 additions & 7 deletions content/week01_intro/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ From this point on, we should all be on nearly the same page.

## Shell

The UNIX shell is Bash (or a Bashwards compatible shell like Zsh). We'll be
using that (though you might see the Fish shell occasionally, we will point out
any differences that matter). Fish is a nicer shell than Bash, but Bash is a
common default.
The UNIX shell is Bash (or a Bash-compatible shell like Zsh). We'll be using
that (though you might see the Fish shell occasionally, we will point out any
differences that matter). Fish is a nicer shell than Bash, but Bash is a common
default.

## Version control

Expand All @@ -37,8 +37,8 @@ the course, you should set up a ssh key pair so we can push to GitHub.

## Python

For Python, we'll use a recent version of Python (3.11+ highly recommended, 3.9+
is _probably_ okay). One way to get Python is via Conda (like anaconda,
For Python, we'll use a recent version of Python (3.12+ highly recommended,
3.11+ is _probably_ okay). One way to get Python is via Conda (like anaconda,
miniconda, etc). Another way is to use homebrew (often on Linux). If you are
using Ubuntu 22.04 or newer, the system Python will be fine.

Expand All @@ -51,6 +51,6 @@ installation then.
## Editor

You should have an editor you like. I'll be using a VI interface, either
natively or in VS Code. You can pick EMacs, or something else. Software
natively or in VS Code. You can pick Emacs, or something else. Software
development _is_ writing and editing text files. Learning a powerful editor will
vastly improve how you work with code.
17 changes: 9 additions & 8 deletions content/week01_intro/vcs.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ Getting a copy of the repository locally is called **cloning**.

The history of a Git repository is stored with it. Each stored point in time is
called a **commit**. Git is a bit clever in how it stores history, so making
lots of commits is fairly cheap. Multiple commits can share a parent, making the
commit history look a bit like a tree (technically a graph, since you can also
share parents, but we won't worry about that too much yet).
lots of commits is fairly cheap. Most commits have one parent, making the commit
history look like a tree. But some commits can have multiple parents (in
merges), making the history technically a directed acyclic graph (DAG). For now,
the tree mental model is fine.

You can create a moving "name" that points at a commit called a **branch**, or a
stationary name called a **tag** (at least it should be stationary). One of the
Expand All @@ -27,11 +28,11 @@ a nice, incrementing number? Because git is distributed.

## Distributed

Unlike older systems, every full git checkout is a complete, independent copy of
the repository! You have all history locally. If you clone a remote repository,
you could push to a different remote if you wished. If GitHub, GitLab, or
whatever other **hosting** service you are using disappears tomorrow, you can
simply push somewhere else and have all your history intact.
Unlike older systems, every cloned git repository is a complete, independent
copy of the repository! You have all history locally. If you clone a remote
repository, you could push to a different remote if you wished. If GitHub,
GitLab, or whatever other **hosting** service you are using disappears tomorrow,
you can simply push somewhere else and have all your history intact.

## Hosted

Expand Down
6 changes: 3 additions & 3 deletions content/week02_testing/debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ log = logging.getLogger("unique")
You'll often see this shortcut used in modules:

```python
log = logging.getLogger(__module__)
log = logging.getLogger(__name__)
```

Here are a couple of logging statements:
Expand Down Expand Up @@ -59,8 +59,8 @@ beautiful setting (for use in applications, not libraries).
The hardest part of logging is generally setting up the infrastructure for
controlling the logger, usually; it’s best if you have a flag or environment
variable that can control this, and you have to decide or allow a choice on
whether you want all loggers or just yours to change level. And you have might
want to log to a file, rotate logs, etc; everything is doable but not all that
whether you want all loggers or just yours to change level. And you might want
to log to a file, rotate logs, etc; everything is doable but not all that
pretty.

### Combining with pytest
Expand Down
28 changes: 14 additions & 14 deletions content/week02_testing/pytest.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def f(x):
class TestFunction(unittest.TestCase):
def test_f(self):
self.assertEqual(f(0), 0)
self.assertEqual(f(1), 2)
self.assertEqual(f(1), 1)
self.assertEqual(f(2), 4)
```

Expand Down Expand Up @@ -219,12 +219,12 @@ def slow():
return "world"


def test_a(something):
assert something == "world"
def test_a(slow):
assert slow == "world"


def test_b(something):
assert something == "world"
def test_b(slow):
assert slow == "world"
```

Fixtures support setup _and_ teardown, and they do so using the same "trick"
Expand Down Expand Up @@ -262,7 +262,7 @@ import sys


def test_some_function_linux(monkeypatch):
monkepatch.setattr(sys, "platform", "linux")
monkeypatch.setattr(sys, "platform", "linux")
# stuff here will think it's on linux
...
# after the test, the monkeypatching is removed!
Expand All @@ -287,7 +287,7 @@ import sys

@pytest.mark.parametrize("platform", ["linux", "win32", "darwin"])
def test_some_function_linux(monkeypatch, platform):
monkepatch.setattr(sys, "platform", platform)
monkeypatch.setattr(sys, "platform", platform)
...
```

Expand All @@ -305,13 +305,13 @@ import pytest
import sys


@pytest.fixture("platform", ["linux", "win32", "darwin"])
@pytest.fixture(params=["linux", "win32", "darwin"])
def platform(request):
return request.param


def test_some_function_linux(monkeypatch, platform):
monkepatch.setattr(sys, "platform", platform)
monkeypatch.setattr(sys, "platform", platform)
...
```

Expand All @@ -324,13 +324,13 @@ import pytest
import sys


@pytest.fixture("platform", ["linux", "win32", "darwin"])
@pytest.fixture(params=["linux", "win32", "darwin"])
def platform(request, monkeypatch):
monkepatch.setattr(sys, "platform", platform)
monkeypatch.setattr(sys, "platform", request.param)
return request.param


def test_some_function_linux(monkeypatch, platform): ...
def test_some_function_linux(platform): ...
```

Now we automatically get the monkeypatching each time, too!
Expand Down Expand Up @@ -413,7 +413,7 @@ A few plugins of note:

- `pytest-mock`: Makes the excellent `unittest.mock` built-in library nicer to
use from pytest as native fixtures.
- `pytest-ascyncio`: Allows pytest to natively test ascync functions.
- `pytest-asyncio`: Allows pytest to natively test async functions.
- `pytest-xdist`: Distributed testing, loop on failing.
- `pytest-subprocess`: Mocks subprocess calls.
- `pytest-benchmark`: Compute benchmarks as part of testing (also see
Expand Down Expand Up @@ -448,7 +448,7 @@ tests failed and were skipped, and why. `--strict-markers` will make sure you
don't try to use an unspecified fixture. And `--strict-config` will error if you
make a mistake in your config. `xfail_strict` will change the default for
`xfail` to fail the tests if it doesn't fail - you can still override locally in
a specific xfail for a flaky failure. `filter_warnings` will cause all warnings
a specific xfail for a flaky failure. `filterwarnings` will cause all warnings
to be errors (you can add allowed warnings here too). `log_cli_level` will
report `INFO` and above log messages on a failure. Finally, `testpaths` will
limit `pytest` to just looking in the folders given - useful if it tries to pick
Expand Down
6 changes: 4 additions & 2 deletions content/week02_testing/tdd.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ def test_my_max():
assert my_max([3]) == 3
```

To pass this test, we can just accept an argument and return 3.
To pass this test, we can just accept an argument and return 3. (Note that
naming the parameter `input` shadows Python's built-in `input` function; we'll
fix this in the refactor stage below.)

```python
# green
Expand Down Expand Up @@ -267,7 +269,7 @@ And to pass
import pytest


def my_max(input_list: list[int]) -> int:
def my_max(input_list: list[int]) -> int | None:
if not isinstance(input_list, list):
raise ValueError("Non-list argument passed to my_max")

Expand Down
6 changes: 3 additions & 3 deletions content/week02_testing/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ rely on.

The adversarial view --- don't be wishful. Try to find defects. Document for the
user things that could cause problems but that you are not guarding against
(a.k.a. make explicit caveat emptors). We'll discuss documentation later.
(a.k.a. make explicit caveat emptor). We'll discuss documentation later.

Write code that's amenable to atomic testing --- modularity, DRY vs. WET, etc
all help (as we'll see). The better your design and workflow play with Git, then
Expand All @@ -59,7 +59,7 @@ Tests are themselves code --- it should adhere to good development principles
(i.e. writing more and more tests is part of the code authoring process).

Test code often outnumbers real code --- or at least it should. SQLite famously
has 608 times more test code than source code.
has 590 times more test code than source code.

Testing well isn't trivial --- tools can help, but it's a skill and an art.

Expand Down Expand Up @@ -147,7 +147,7 @@ development, since you are writing smaller portions at a time.

Test driven development flips the tables on test vs. code; instead of writing
the code then testing it, you write the tests and then make them pass by writing
the code. It may seem unnatural at first, but is has some significant benefits:
the code. It may seem unnatural at first, but it has some significant benefits:

- Forces you to think about and design the _interface_ first. How should this be
used? It's easier to answer that question when making a change doesn't require
Expand Down
Loading