diff --git a/pyproject.toml b/pyproject.toml index 127679e962..a716c1a056 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ exclude = [ "agents-md/run1_main.py", "python315-frozendict/", "python315-lazy-imports", + "python315-new-features", "ai-benchmark" ] diff --git a/python315-new-features/README.md b/python315-new-features/README.md new file mode 100644 index 0000000000..b46e2e51be --- /dev/null +++ b/python315-new-features/README.md @@ -0,0 +1,3 @@ +# Python 3.15: Cool New Features for You to Try + +This folder provides the code examples for the Real Python tutorial [Python 3.15: Cool New Features for You to Try](https://realpython.com/python315-new-features/) diff --git a/python315-new-features/ctxmgr.py b/python315-new-features/ctxmgr.py new file mode 100644 index 0000000000..309a90711e --- /dev/null +++ b/python315-new-features/ctxmgr.py @@ -0,0 +1,15 @@ +from contextlib import contextmanager + +@contextmanager +def logged(label): + print(f"enter {label}") + yield + print(f"exit {label}") + +@logged("reading") +def read_lines(): + yield "first" + yield "second" + +for line in read_lines(): + print(line) diff --git a/python315-new-features/lazy_json.py b/python315-new-features/lazy_json.py new file mode 100644 index 0000000000..bda3ea33e0 --- /dev/null +++ b/python315-new-features/lazy_json.py @@ -0,0 +1,8 @@ +import sys + +lazy import json + +print("json loaded:", "json" in sys.modules) +config = json.dumps({"theme": "dark", "autosave": True}) +print("json loaded:", "json" in sys.modules) +print(config) diff --git a/python315-new-features/menu.txt b/python315-new-features/menu.txt new file mode 100644 index 0000000000..b14a0ca005 --- /dev/null +++ b/python315-new-features/menu.txt @@ -0,0 +1 @@ +Crème brûlée — 7 € diff --git a/python315-new-features/python315_in_15_lines.py b/python315-new-features/python315_in_15_lines.py new file mode 100644 index 0000000000..f27d798ac3 --- /dev/null +++ b/python315-new-features/python315_in_15_lines.py @@ -0,0 +1,18 @@ +import sys +from pathlib import Path +lazy import json + +defaults = frozendict({"theme": "light", "autosave": True}) +MISSING = sentinel("MISSING") + +def resolve(overrides, name, default=MISSING): + value = (defaults | overrides).get(name, default) + return "unset" if value is MISSING else value + +profiles = [{"theme": "crème brûlée"}, {"autosave": False}] +merged = {**profile for profile in profiles} +print("json imported:", "json" in sys.modules) +Path("user.json").write_text(json.dumps(defaults | merged, ensure_ascii=False)) +print("json imported:", "json" in sys.modules) +print("theme:", resolve(merged, "theme"), "| font:", resolve(merged, "font")) +print(Path("user.json").read_text()) diff --git a/python315-new-features/requirements.txt b/python315-new-features/requirements.txt new file mode 100644 index 0000000000..5cd7b748f9 --- /dev/null +++ b/python315-new-features/requirements.txt @@ -0,0 +1 @@ +pyright==1.1.414 diff --git a/python315-new-features/typeddict.py b/python315-new-features/typeddict.py new file mode 100644 index 0000000000..292d772d8c --- /dev/null +++ b/python315-new-features/typeddict.py @@ -0,0 +1,14 @@ +from typing import TypedDict + +class Movie(TypedDict, closed=True): + title: str + year: int + +class Headers(TypedDict, extra_items=str): + content_type: str + +movie: Movie = {"title": "Brazil", "year": 1985, "rating": 8.0} +headers: Headers = {"content_type": "text/html", "x-trace-id": "abc123"} +bad_headers: Headers = {"content_type": "text/html", "x-retries": 3} + +print(Movie.__closed__, Headers.__extra_items__) diff --git a/python315-new-features/typeform_check.py b/python315-new-features/typeform_check.py new file mode 100644 index 0000000000..36e1d49d29 --- /dev/null +++ b/python315-new-features/typeform_check.py @@ -0,0 +1,11 @@ +from typing import TypeForm, reveal_type + +def parse_old[T](value: str, typ: type[T]) -> T: ... + +def parse_new[T](value: str, typ: TypeForm[T]) -> T: ... + +reveal_type(parse_old("42", int)) +reveal_type(parse_old("42", int | None)) +reveal_type(parse_new("42", int | None)) +reveal_type(parse_new("42", list[int])) +parse_new("42", 42) diff --git a/python315-new-features/wordstats.py b/python315-new-features/wordstats.py new file mode 100644 index 0000000000..a8c7426f7a --- /dev/null +++ b/python315-new-features/wordstats.py @@ -0,0 +1,8 @@ +from collections import Counter + +def most_common_words(text, n=3): + words = [word.strip(".,").lower() for word in text.split()] + return Counter(words).most_common(n) + +sample = "The quick brown fox jumps over the lazy dog. " * 100_000 +print(most_common_words(sample))