class: center, middle # What's New in Python 3.8 and 3.9 ## Menno Finlay-Smits --- # f-string expression expansion - Great for print debugging - New in Python 3.8 The old way... ```python >>> def double(v): ... return v * 2 ... >>> print(f"double(8)={double(8)}") double(8)=16 ``` The better way... ```python >>> def double(v): ... return v * 2 ... >>> print(f"{double(8)=}") double(8)=16 ``` --- # Walrus operator - Assignment in expressions - Allows a output of a subexpression to be reused within the same expression - Similar features exist in other languages - New in Python 3.8 - `:=` looks like a sideways walrus --- # Avoiding duplicate code From this... ```python chunk = resource.read(8192) while chunk: process(chunk) chunk = resource.read(8192) ``` Or this... ```python while True: chunk = resource.read(8192) if not chunk: break process(chunk) ``` To this... ```python while chunk := resource.read(8192): process(chunk) ``` Note how the assigned variable is available after the expression (it leaks). --- # Reusing a value that expensive to create ```python filtered_data = [ f(x) for x in data if f(x) is not None ] ``` - `f(x)` is run twice for every item in `data` - Don't use a list comprehension? - We can! Engage walrus! ```python filtered_data = [ y for x in data if (y := f(x)) is not None ] ``` --- # Readability A common pattern... ```python m = re.match(pattern, line) if m: print(m.group(1)) ``` `m` only matters if it is not None. Using the walrus operator... ```python if m := re.match(pattern, line): print(m.group(1)) ``` Highlights the scope of the `m` variable (although it does leak). --- # More examples Doesn't have to be just truthy expressions... ```python if (y := f(x)) > 5: print(f"danger!: {y}") ``` Multiple walruses (a herd?)... ```python >>> if (x := 42) > (y := 13): ... print(x, y) ... 42 13 ``` --- # Dictionary Unions - Nice syntax for merging dictionaries - Uses `|` - New in Python 3.9 ```python >>> d = {1: "one", 2: "two"} >>> e = {2: "two", 3: "three"} >>> d | e {1: 'one', 2: 'two', 3: 'three'} ``` Creates a new dict (`d` and `e` are unchanged). Similar to set union syntax... ```python >>> {1, 2, 3} | {2, 4} {1, 2, 3, 4} ``` --- # Order matters - In the case of the duplicate keys, the rightmost value wins - Not commutative ```python >>> d = {1: "one", 2: "two"} >>> e = {2: "TWO", 3: "three"} >>> d | e {1: 'one', 2: 'TWO', 3: 'three'} >>> e | d {2: 'two', 3: 'three', 1: 'one'} ``` - Dict unions are **lossy** - (so are other dict merging methods) --- # Dict union alternatives ### update method For example, `d.update(e)` but this updates `d` in-place which isn't always desirable. ### Dict unpacking It's unobvious, hard to read and doesn't work for some subclasses such as `defaultdict`. ```python >>> d = {1: "one", 2: "two"} >>> e = {2: "TWO", 3: "three"} >>> {**d, **e} {1: 'one', 2: 'TWO', 3: 'three'} ``` --- # removeprefix and removesuffix - Useful new string methods - New in Python 3.9 Before... ```python if name.startswith("test_"): print(name[5:]) else: print(name) ``` Now... ```python print(name.removeprefix("test_")) ``` Faster, more readable, more descriptive. --- # Positional-only parameters - Require that some parameters must be passed positionally - New in Python 3.8 ```python >>> def foo(a, /, b, c=42): ... print(a, b, c) ... >>> foo(1, 2) 1 2 42 >>> foo(1, b=99) 1 99 42 >>> foo(a=44, b=99) Traceback (most recent call last): File "
", line 1, in
TypeError: foo() got some positional-only arguments passed as keyword arguments: 'a' ``` --- # Interaction with keyword only args Mixes with `*` feature for keyword-only arguments ```python >>> def bar(a, /, b, c=42, *, d=99): ... print(a, b, c, d) ... >>> bar(1, 2, 3) 1 2 3 99 >>> bar(1, 2, 3, 4) Traceback (most recent call last): File "
", line 1, in
TypeError: bar() takes from 2 to 3 positional arguments but 4 were given >>> bar(1, 2, 3, d=4) 1 2 3 4 >>> bar(1, b=2, c=3, d=4) 1 2 3 4 ``` --- # Why positional-only arguments? - For some APIs, only one specific ordering makes sense (e.g. `range(start, stop, step)`) - Library authors can safely change the name of positional-only parameters without breaking callers - Parsing positional-only args is faster - Allows for optimisations - Ensuring compatiblity between subclasses --- # continue in finally As of Python 3.8, `continue` is now allowed in `finally` blocks. In older Pythons... ```python >>> for x in range(4): ... try: ... pass ... finally: ... continue ... File "
", line 5 SyntaxError: 'continue' not supported inside 'finally' clause ``` In Python 3.8 and later... ```python >>> for x in range(4): ... try: ... pass ... finally: ... continue ... >>> ``` --- # zoneinfo - Time zone database support is now in the standard library - No need to reach for an external package - New in Python 3.9 - Will use system time zone data if available - Can fallback to tzdata on PyPI (maintained by core Python team) ```python >>> from zoneinfo import ZoneInfo >>> tz = ZoneInfo("Pacific/Auckland") >>> tz zoneinfo.ZoneInfo(key='Pacific/Auckland') >>> from datetime import datetime, timedelta >>> dt = datetime(2021, 9, 6, 12, tzinfo=tz) >>> print(dt) 2021-09-06 12:00:00+12:00 >>> dt.tzname() 'NZST' ``` --- # Even more! - `math.prod` (like `sum` but multiplies) - Lots of new functions in the `statistics` package - New graphlib module (topological sorting) - New Parsing Expression Grammar (PEG) parser - and much more...