-
Notifications
You must be signed in to change notification settings - Fork 5.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add examples from Python 3.13: Cool New Features #587
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
|
||
|
@@ -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: [[email protected]]([email protected]) | ||
|
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__) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
def inverse(number): | ||
return 1 / number | ||
|
||
|
||
print(inverse(0)) |
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)) |
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}") |
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")) |
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}") |
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)) |
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) | ||
Comment on lines
+75
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[tool.pyright] | ||
reportDeprecated = true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
"""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 . | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should these two commands be on separate lines? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, yes, definitely. It seems that the automatic comment formatter I ran is a bit eager. |
||
|
||
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]] | ||
bzaczynski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find this counter-intuitive since the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, I believe that TypedDict is all about structural typing. The PEP talks a bit about how the capabilities of I haven't quite internalized the rules for when one TypedDict can pass as the other, but everytime I sit down and think for a while, it seems to add up 🙈 |
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])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these two commands be on separate lines?