Skip to content

Commit

Permalink
Add examples from Python 3.13: Cool New Features
Browse files Browse the repository at this point in the history
  • Loading branch information
gahjelle committed Sep 26, 2024
1 parent 4f9996d commit e116b67
Show file tree
Hide file tree
Showing 18 changed files with 319 additions and 1 deletion.
19 changes: 18 additions & 1 deletion python-313/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Note that for testing the free-threading and JIT features, you'll need to build

You can learn more about Python 3.13's new features in the following Real Python tutorials:

<!-- - [Python 3.13: Cool New Features for You to Try](https://realpython.com/python313-new-features/) -->
- [Python 3.13: Cool New Features for You to Try](https://realpython.com/python313-new-features/)
- [Python 3.13 Preview: Free Threading and a JIT Compiler](https://realpython.com/python313-free-threading-jit/)
- [Python 3.13 Preview: A Modern REPL](https://realpython.com/python313-repl)

Expand All @@ -30,11 +30,28 @@ The following examples are used to demonstrate different features of the new REP
- [`multiline_editing.py`](repl/multiline_editing.py)
- [`power_factory.py](repl/power_factory.py)
- [`guessing_game.py](repl/guessing_game.py)
- [`roll_dice.py`](repl/roll_dice.py)

### Error messages

Run the scripts in the `errors/` folder to see different error messages produced by Python 3.13.

### Free-Threading and JIT

You need to enable a few build options to try out the free-threading and JIT features in Python 3.13. You can find more information in the dedicated [README file](free-threading-jit/README.md).

## Static typing

Run the scripts in the `typing/` folder to try out the new static typing features.

## Other features

The following scripts illustrate other new features in Python 3.13:

- [`replace.py`](replace.py): Use `copy.replace()` to update immutable data structures.
- [`paths.py`](paths.py) and [`music/`](music/): Glob patterns are more consistent.
- [`docstrings.py`](docstrings.py): Common leading whitespace in docstrings is stripped.

## Authors

- **Bartosz Zaczyński**, E-mail: [bartosz@realpython.com](bartosz@realpython.com)
Expand Down
16 changes: 16 additions & 0 deletions python-313/docstrings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import dataclasses


@dataclasses.dataclass
class Person:
"""Model a person with a name, location, and Python version."""

name: str
place: str
version: str


print(Person.__doc__)

print(len(dataclasses.replace.__doc__))
print(dataclasses.replace.__doc__)
5 changes: 5 additions & 0 deletions python-313/errors/inverse.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def inverse(number):
return 1 / number


print(inverse(0))
4 changes: 4 additions & 0 deletions python-313/errors/kwarg_suggest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
numbers = [2, 0, 2, 4, 1, 0, 0, 1]

# print(sorted(numbers, reversed=True))
print(sorted(numbers, reverse=True))
14 changes: 14 additions & 0 deletions python-313/errors/random.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import random

num_faces = 6

print("Hit enter to roll die (q to quit, number for # of faces) ")
while True:
roll = input()
if roll.lower().startswith("q"):
break
if roll.isnumeric():
num_faces = int(roll)

result = random.randint(1, num_faces)
print(f"Rolling a d{num_faces:<2d} - {result:2d}")
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
27 changes: 27 additions & 0 deletions python-313/paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import glob
import re
from pathlib import Path

print('\nUsing glob("*"):\n')
for path in Path("music").glob("*"):
print(" ", path)

print('\nUsing glob("**"):\n')
for path in Path("music").glob("**"):
print(" ", path)

print('\nUsing glob("**/*"):\n')
for path in Path("music").glob("**/*"):
print(" ", path)

print('\nUsing glob("**/"):\n')
for path in Path("music").glob("**/"):
print(" ", path)

print("\nglob.translate()\n")
pattern = glob.translate("music/**/*.txt")
print(pattern)

print(re.match(pattern, "music/opera/flower_duet.txt"))
print(re.match(pattern, "music/progressive_rock/"))
print(re.match(pattern, "music/progressive_rock/fandango.txt"))
14 changes: 14 additions & 0 deletions python-313/repl/roll_dice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import random

num_faces = 6

print("Hit enter to roll die (q to quit, number for # of faces) ")
while True:
roll = input()
if roll.lower().startswith("q"):
break
if roll.isnumeric():
num_faces = int(roll)

result = random.randint(1, num_faces)
print(f"Rolling a d{num_faces:<2d} - {result:2d}")
23 changes: 23 additions & 0 deletions python-313/replace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import copy
from datetime import date
from typing import NamedTuple


class Person(NamedTuple):
name: str
place: str
version: str


person = Person(name="Geir Arne", place="Oslo", version="3.12")
person = Person(name=person.name, place=person.place, version="3.13")
print(person)

today = date.today()
print(today)
print(today.replace(day=1))
print(today.replace(month=12, day=24))

person = Person(name="Geir Arne", place="Oslo", version="3.12")
print(copy.replace(person, version="3.13"))
print(copy.replace(today, day=1))
79 changes: 79 additions & 0 deletions python-313/typing/deprecations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""Demonstration of PEP 702: Marking deprecations using the type system
Use PyLance in VS Code by setting Python › Analysis: Type Checking Mode or run
the Pyright CLI:
$ python -m pip install pyright $ pyright --pythonversion 3.13 .
Note that showing warnings requires setting the reportDeprecated option in
Pyright. This is done in pyproject.toml.
"""

from typing import overload
from warnings import deprecated


@deprecated("Use + instead of calling concatenate()")
def concatenate(first: str, second: str) -> str:
return first + second


@overload
@deprecated("add() is only supported for floats")
def add(x: int, y: int) -> int: ...
@overload
def add(x: float, y: float) -> float: ...


def add(x, y):
return x + y


class Version:
def __init__(self, major: int, minor: int = 0, patch: int = 0) -> None:
self.major = major
self.minor = minor
self.patch = patch

@property
@deprecated("Use .patch instead")
def bugfix(self):
return self.patch

def bump(self, part: str) -> None:
if part == "major":
self.major += 1
self.minor = 0
self.patch = 0
elif part == "minor":
self.minor += 1
self.patch = 0
elif part == "patch":
self.patch += 1
else:
raise ValueError("part must be 'major', 'minor', or 'patch'")

@deprecated("Use .bump() instead")
def increase(self, part: str) -> None:
return self.bump(part)

def __str__(self):
return f"{self.major}.{self.minor}.{self.patch}"


@deprecated("Use Version instead")
class VersionType:
def __init__(self, major: int, minor: int = 0, patch: int = 0) -> None:
self.major = major
self.minor = minor
self.patch = patch


concatenate("three", "thirteen")
add(3, 13)
VersionType(3, 13)

version = Version(3, 13)
version.increase("patch")
print(version)
print(version.bugfix)
38 changes: 38 additions & 0 deletions python-313/typing/generic_queue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from collections import deque


class Queue[T]:
def __init__(self) -> None:
self.elements: deque[T] = deque()

def push(self, element: T) -> None:
self.elements.append(element)

def pop(self) -> T:
return self.elements.popleft()


# %% Python 3.13
#
# class Queue[T=str]:
# def __init__(self) -> None:
# self.elements: deque[T] = deque()
#
# def push(self, element: T) -> None:
# self.elements.append(element)
#
# def pop(self) -> T:
# return self.elements.popleft()

# %% Use the queue
#
string_queue = Queue()
integer_queue = Queue[int]()

string_queue.push("three")
string_queue.push("thirteen")
print(string_queue.elements)

integer_queue.push(3)
integer_queue.push(13)
print(integer_queue.elements)
2 changes: 2 additions & 0 deletions python-313/typing/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.pyright]
reportDeprecated = true
45 changes: 45 additions & 0 deletions python-313/typing/readonly.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Demonstration of PEP 705: TypedDict: read-only items
Use PyLance in VS Code by setting Python › Analysis: Type Checking Mode or run
the Pyright CLI:
$ python -m pip install pyright $ pyright --pythonversion 3.13 .
Extension of TypedDict:
https://realpython.com/python38-new-features/#more-precise-types
"""

from typing import NotRequired, ReadOnly, TypedDict

# class Version(TypedDict):
# version: str
# release_year: NotRequired[int | None]


# class PythonVersion(TypedDict):
# version: str
# release_year: int

class Version(TypedDict):
version: ReadOnly[str]
release_year: ReadOnly[NotRequired[int | None]]


class PythonVersion(TypedDict):
version: ReadOnly[str]
release_year: ReadOnly[int]

py313 = PythonVersion(version="3.13", release_year=2024)

# Alternative syntax, using TypedDict as an annotation
# py313: PythonVersion = {"version": "3.13", "release_year": 2024}


def get_version_info(ver: Version) -> str:
if "release_year" in ver:
return f"Version {ver["version"]} released in {ver["release_year"]}"
else:
return f"Version {ver["version"]}"

# Only allowed to use PythonVersion instead of Version if the fields are ReadOnly
print(get_version_info(py313))
34 changes: 34 additions & 0 deletions python-313/typing/tree.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from typing import TypeGuard

type Tree = list[Tree | int]


def is_tree(obj: object) -> TypeGuard[Tree]:
return isinstance(obj, list)


def get_left_leaf_value(tree_or_leaf: Tree | int) -> int:
if is_tree(tree_or_leaf):
return get_left_leaf_value(tree_or_leaf[0])
else:
return tree_or_leaf


# %% Python 3.13
#
# from typing import TypeIs
#
# type Tree = list[Tree | int]
#
# def is_tree(obj: object) -> TypeIs[Tree]:
# return isinstance(obj, list)
#
# def get_left_leaf_value(tree_or_leaf: Tree | int) -> int:
# if is_tree(tree_or_leaf):
# return get_left_leaf_value(tree_or_leaf[0])
# else:
# return tree_or_leaf

# %% Use the tree
#
print(get_left_leaf_value([[[[3, 13], 12], 11], 10]))

0 comments on commit e116b67

Please sign in to comment.