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 .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ repos:
- id: check-added-large-files
args: [--maxkb=6000]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.4
rev: v0.12.2
hooks:
# Ruff fix
- id: ruff
Expand All @@ -23,7 +23,7 @@ repos:
types_or: [python, pyi]
name: ruff (format)
- repo: https://github.com/google/yamlfmt
rev: v0.16.0
rev: v0.17.2
hooks:
- id: yamlfmt
name: YAML (format)
Expand Down
48 changes: 23 additions & 25 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
[project]
name = "python-tutorial"
requires-python = ">=3.10"

# pytest
[tool.pytest.ini_options]
addopts = "-v --tb=short"

[tool.ruff]
# Line length (Black default)
line-length = 88

# Python target version
target-version = "py38"

# Ignored rules for the entire project
[tool.ruff.lint]
ignore = [
"E501", # Line too long
"E203", # Whitespace before ':'
# "TRY301", # Raise within try block (this is actually a good practice)
# "W503" # Line break before binary operator (not PEP8 enforced, so not implemented in Ruff)
"E501", # Line too long
"E203", # Whitespace before ':'
# "TRY301", # Raise within try block (this is actually a good practice)
# "W503" # Line break before binary operator (not PEP8 enforced, so not implemented in Ruff)
]

# flake8 plugins to enable:
# - flake8-bugbear B
# - flake8-builtins A
Expand All @@ -24,39 +28,33 @@ ignore = [
# - pyflakes F
# - tryceratops TRY
select = [
"A", # flake8-builtins
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"T10", # flake8-debugger
"G", # flake8-logging-format
"N", # pep8-naming
"F", # pyflakes
"TRY", # tryceratops
"I", # isort
"E", # pycodestyle errors
"UP", # pyupgrade
"A", # flake8-builtins
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"T10", # flake8-debugger
"G", # flake8-logging-format
"N", # pep8-naming
"F", # pyflakes
"TRY", # tryceratops
"I", # isort
"E", # pycodestyle errors
"UP", # pyupgrade
]

# Per-file rule ignores
[tool.ruff.lint.per-file-ignores]
# Trailing whitespace in comment
"binder/ipython_config.py" = ["E266"]

# suppress `raise ... from err`
# Why we ignore B904 from the object-oriented tests?
# We do want to raise an assertion error if the check on the solution function attributes fails,
# but Python by default will raise a TypeError via vars(solution_result)
# if the result is not a class and therefore doesn't have a __dict__ attribute.
"tutorial/tests/test_object_oriented_programming.py" = ["B904"]

# Ignore invalid names like `import torch.nn.functional as F`
"25_library_pytorch_language_modeling.ipynb" = ["N812"]

# Ruff formatting
[tool.ruff.format]
quote-style = "double"
indent-style = "space"

# pytest
[tool.pytest.ini_options]
addopts = "-v --tb=short"
2 changes: 2 additions & 0 deletions tutorial/pandas_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def low_med_high_bins_viz(data, column, ylabel, title, figsize=(15, 3)):
["low", "med", "high"],
["///", "", "\\\\\\"],
pd.cut(data[column], bins=3).unique().categories.values,
strict=False,
):
plt.axhspan(
bounds.left,
Expand Down Expand Up @@ -45,6 +46,7 @@ def quartile_bins_viz(data, column, ylabel, title, figsize=(15, 8)):
[r"$Q_1$", r"$Q_2$", r"$Q_3$", r"$Q_4$"],
["\\\\\\", "", "///", "||||"],
pd.qcut(data.volume, q=4).unique().categories.values,
strict=False,
):
plt.axhspan(
bounds.left,
Expand Down
10 changes: 6 additions & 4 deletions tutorial/tests/test_01_basic_datatypes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import copy
import math
from typing import Any, Callable, Hashable, Iterable
from collections.abc import Callable, Hashable, Iterable
from typing import Any

import pytest

Expand Down Expand Up @@ -364,7 +365,7 @@ def reference_dict_return_value(my_dict: dict[Hashable, Any], key: Any) -> Any:


@pytest.mark.parametrize(
"my_dict, key", list(zip(copy.deepcopy(DICTS1), ["b"] * len(DICTS1)))
"my_dict, key", list(zip(copy.deepcopy(DICTS1), ["b"] * len(DICTS1), strict=False))
)
def test_dict_return_value(my_dict, key, function_to_test):
my_dict_to_try = my_dict.copy()
Expand All @@ -380,7 +381,7 @@ def reference_dict_return_delete_value(my_dict: dict[Hashable, Any], key: Any) -


@pytest.mark.parametrize(
"my_dict, key", list(zip(copy.deepcopy(DICTS1), ["b"] * len(DICTS1)))
"my_dict, key", list(zip(copy.deepcopy(DICTS1), ["b"] * len(DICTS1), strict=False))
)
def test_dict_return_delete_value(my_dict, key, function_to_test):
my_dict_original1 = my_dict.copy()
Expand All @@ -400,7 +401,8 @@ def reference_update_one_dict_with_another(


@pytest.mark.parametrize(
"my_dict1, my_dict2", list(zip(copy.deepcopy(DICTS1), copy.deepcopy(DICTS2)))
"my_dict1, my_dict2",
list(zip(copy.deepcopy(DICTS1), copy.deepcopy(DICTS2), strict=False)),
)
def test_update_one_dict_with_another(my_dict1, my_dict2, function_to_test):
my_dict1_original1 = my_dict1.copy()
Expand Down
40 changes: 20 additions & 20 deletions tutorial/tests/test_02_control_flow.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import contextlib
import pathlib
from math import isclose, sqrt
from typing import Any, List, Optional, Tuple
from typing import Any

import pytest

Expand All @@ -16,7 +16,7 @@ def read_data(name: str, data_dir: str = "data") -> pathlib.Path:
#


def reference_indexed_string(string: str) -> List[Tuple[str, int]]:
def reference_indexed_string(string: str) -> list[tuple[str, int]]:
"""Reference solution warm-up 1"""
result = []
for i, char in enumerate(string):
Expand Down Expand Up @@ -45,7 +45,7 @@ def test_indexed_string(string: str, function_to_test) -> None:
assert reference_indexed_string(string) == result


def reference_range_of_nums(start: int, end: int) -> List[int]:
def reference_range_of_nums(start: int, end: int) -> list[int]:
"""Reference solution warm-up 2"""
step = 1 if start < end else -1
return list(range(start, end + step, step))
Expand All @@ -67,7 +67,7 @@ def test_range_of_nums(start: int, end: int, function_to_test) -> None:
)


def reference_sqrt_of_nums(numbers: List[int]) -> List[float]:
def reference_sqrt_of_nums(numbers: list[int]) -> list[float]:
"""Reference solution warm-up 3"""
result = []
for num in numbers:
Expand Down Expand Up @@ -104,7 +104,7 @@ def test_sqrt_of_nums(nums: list[int], function_to_test) -> None:
assert len(reference) == len(result), (
"The function should return a list of the same length"
)
assert all(isclose(x, y) for x, y in zip(reference, result)), (
assert all(isclose(x, y) for x, y in zip(reference, result, strict=False)), (
"The function should return the square root of each number"
)

Expand All @@ -128,7 +128,7 @@ def test_divide_until(num: int, function_to_test) -> None:
#


def reference_filter_by_position(numbers: List[int]) -> List[int]:
def reference_filter_by_position(numbers: list[int]) -> list[int]:
result = set()
for pos, number in enumerate(numbers, start=1):
if number > pos:
Expand All @@ -149,7 +149,7 @@ def reference_filter_by_position(numbers: List[int]) -> List[int]:
[10, 20, 1, 2, 3], # Mixed large and small numbers = {10, 20}
],
)
def test_filter_by_position(numbers: List[int], function_to_test) -> None:
def test_filter_by_position(numbers: list[int], function_to_test) -> None:
"""Test filtering numbers by position."""
assert function_to_test(numbers) == reference_filter_by_position(numbers)

Expand All @@ -159,7 +159,7 @@ def test_filter_by_position(numbers: List[int], function_to_test) -> None:
#


def reference_find_even_multiple_three(numbers: List[int]) -> Optional[int]:
def reference_find_even_multiple_three(numbers: list[int]) -> int | None:
result = None
for number in numbers:
if number % 2 == 0 and number % 3 == 0:
Expand All @@ -179,7 +179,7 @@ def reference_find_even_multiple_three(numbers: List[int]) -> Optional[int]:
[1, 3, 5, 7, 12], # Valid number at the end = 12
],
)
def test_find_even_multiple_three(numbers: List[int], function_to_test) -> None:
def test_find_even_multiple_three(numbers: list[int], function_to_test) -> None:
"""Test finding first even multiple of 3."""
assert function_to_test(numbers) == reference_find_even_multiple_three(numbers)

Expand Down Expand Up @@ -233,7 +233,7 @@ def test_is_pure_number(text: str, function_to_test) -> None:
#


def reference_find_factors(num: int) -> List[int]:
def reference_find_factors(num: int) -> list[int]:
"""Reference solution to find the factors of an integer"""
factors = []
for m in range(1, num + 1):
Expand All @@ -257,21 +257,21 @@ def test_find_factors(num: int, function_to_test) -> None:
)


def reference_find_pair(nums: List[int]):
def reference_find_pair(nums: list[int]):
"""
Reference solutions:
- A solution with two nested loops
- A solution using a dictionary and a single loop
"""

def find_pair_with_double_loop(nums: List[int]) -> Optional[int]:
def find_pair_with_double_loop(nums: list[int]) -> int | None:
"""Two nested loops"""
for i in nums:
for j in nums:
if i + j == 2020:
return i * j

def find_pair_with_sets(nums: List[int]) -> Optional[int]:
def find_pair_with_sets(nums: list[int]) -> int | None:
"""Using a dictionary and a single loop"""
complements = {}
for num in nums:
Expand All @@ -280,7 +280,7 @@ def find_pair_with_sets(nums: List[int]) -> Optional[int]:
complements[2020 - num] = num


def __reference_find_pair(nums: List[int]) -> Optional[int]:
def __reference_find_pair(nums: list[int]) -> int | None:
"""Reference solution (part 1)"""
complements = {}
for num in nums:
Expand All @@ -290,18 +290,18 @@ def __reference_find_pair(nums: List[int]) -> Optional[int]:


@pytest.mark.parametrize("nums", [nums_1, nums_2])
def test_find_pair(nums: List[int], function_to_test) -> None:
def test_find_pair(nums: list[int], function_to_test) -> None:
assert function_to_test(nums) == __reference_find_pair(nums)


def reference_find_triplet(nums: List[int]):
def reference_find_triplet(nums: list[int]):
"""
Reference solutions:
- A slow solution with three nested loops
- A fast solution using only two loops
"""

def find_triplet_slow(nums: List[int]) -> Optional[int]:
def find_triplet_slow(nums: list[int]) -> int | None:
"""Slow solution with a triple loop"""
n = len(nums)
for i in range(n - 2):
Expand All @@ -310,7 +310,7 @@ def find_triplet_slow(nums: List[int]) -> Optional[int]:
if nums[i] + nums[j] + nums[k] == 2020:
return nums[i] * nums[j] * nums[k]

def find_triplet_best(nums: List[int]) -> Optional[int]:
def find_triplet_best(nums: list[int]) -> int | None:
"""Fast solution with two loops"""
n = len(nums)
for i in range(n - 1):
Expand All @@ -323,7 +323,7 @@ def find_triplet_best(nums: List[int]) -> Optional[int]:
s.add(nums[j])


def __reference_find_triplet(nums: List[int]) -> Optional[int]:
def __reference_find_triplet(nums: list[int]) -> int | None:
"""Reference solution (part 2), O(n^2)"""
n = len(nums)
for i in range(n - 1):
Expand All @@ -337,7 +337,7 @@ def __reference_find_triplet(nums: List[int]) -> Optional[int]:


@pytest.mark.parametrize("nums", [nums_1, nums_2])
def test_find_triplet(nums: List[int], function_to_test) -> None:
def test_find_triplet(nums: list[int], function_to_test) -> None:
assert function_to_test(nums) == __reference_find_triplet(nums)


Expand Down
8 changes: 4 additions & 4 deletions tutorial/tests/test_03_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pathlib
from collections import Counter
from string import ascii_lowercase, ascii_uppercase
from typing import Any, List
from typing import Any

import pytest

Expand Down Expand Up @@ -270,7 +270,7 @@ def test_combine_anything(args: Any, function_to_test) -> None:
#


def reference_longest_sequence(nums: List[int]) -> int:
def reference_longest_sequence(nums: list[int]) -> int:
"""
Find the longest consecutive sequence of integers

Expand Down Expand Up @@ -301,7 +301,7 @@ def reference_longest_sequence(nums: List[int]) -> int:
[0, 2, 14, 12, 4, 18, 16, 8, 10, 6],
],
)
def test_longest_sequence(input_nums: List[int], function_to_test) -> None:
def test_longest_sequence(input_nums: list[int], function_to_test) -> None:
assert function_to_test(input_nums) == reference_longest_sequence(input_nums)


Expand All @@ -312,7 +312,7 @@ def test_longest_sequence(input_nums: List[int], function_to_test) -> None:
[int(x) for x in read_data("longest_10000.txt").read_text().splitlines()],
],
)
def test_longest_sequence_best(input_nums: List[int], function_to_test) -> None:
def test_longest_sequence_best(input_nums: list[int], function_to_test) -> None:
assert function_to_test(input_nums) == reference_longest_sequence(input_nums)


Expand Down
Loading
Loading