What's new in Python 23.7? by Stphane Wirtel EuroPython 2018 - - - PowerPoint PPT Presentation

what s new in python 23 7
SMART_READER_LITE
LIVE PREVIEW

What's new in Python 23.7? by Stphane Wirtel EuroPython 2018 - - - PowerPoint PPT Presentation

What's new in Python 23.7? by Stphane Wirtel EuroPython 2018 - Edinburgh - July 25th 2018 1 / 66 I am Stphane PythonFOSDEM CPython contributor #fellow member of @ThePSF I live in Belgium @matrixise 2 / 66 PEP 537 PEP 553, Built-in


slide-1
SLIDE 1

What's new in Python 23.7?

by Stéphane Wirtel

EuroPython 2018 - Edinburgh - July 25th 2018 1 / 66

slide-2
SLIDE 2

I live in Belgium @matrixise PythonFOSDEM CPython contributor #fellow member of @ThePSF

I am Stéphane

2 / 66

slide-3
SLIDE 3

PEP 537

PEP 553, Built-in breakpoint() PEP 557, Data Classes PEP 562, Module __getattr__ and __dir__ PEP 563, Postponed Evaluation of Annotations PEP 564, Time functions with nanosecond resolution PEP 565, Show DeprecationWarning in __main__ PEP 567, Context Variables PEP 540, UTF-8 mode https://www.python.org/dev/peps/pep-0537/ 3 / 66

slide-4
SLIDE 4

PEP 553: Built-in breakpoint()

A wonderful function

def divide(e, f): return f / e a, b = 0, 1 print(divide(a, b))

https://www.python.org/dev/peps/pep-0553/ 4 / 66

slide-5
SLIDE 5

PEP 553: Built-in breakpoint()

A wonderful function

def divide(e, f): return f / e a, b = 0, 1 print(divide(a, b))

and of course, you have a crash...

Traceback (most recent call last): File "bugs.py", line 5, in <module> print(divide(a, b)) File "bugs.py", line 2, in divide return f / e ZeroDivisionError: division by zero

https://www.python.org/dev/peps/pep-0553/ 5 / 66

slide-6
SLIDE 6

PEP 553: Built-in breakpoint()

So, how can I debug it? maybe with debugger (hello JS)?

def divide(e, f): debugger; return f / e

https://www.python.org/dev/peps/pep-0553/ 6 / 66

slide-7
SLIDE 7

PEP 553: Built-in breakpoint()

So, how can I debug it? maybe with debugger (hello JS)?

def divide(e, f): debugger; return f / e

No, it is not debugger, then what is the name of my debugger and how to add a breakpoint? pdb ipdb pudb pdbpp ... https://www.python.org/dev/peps/pep-0553/ 7 / 66

slide-8
SLIDE 8

PEP 553: Built-in breakpoint()

for example, with pdb

def divide(e, f): import pdb; pdb.set_trace() return f / e a, b = 0, 1 print(divide(a, b))

https://www.python.org/dev/peps/pep-0553/ 8 / 66

slide-9
SLIDE 9

PEP 553: Built-in breakpoint()

for example, with pdb

def divide(e, f): import pdb; pdb.set_trace() return f / e a, b = 0, 1 print(divide(a, b))

  • r with pudb

def divide(e, f): import pudb; pudb.set_trace() return f / e a, b = 0, 1 print(divide(a, b))

https://www.python.org/dev/peps/pep-0553/ 9 / 66

slide-10
SLIDE 10

PEP 553: Built-in breakpoint()

  • r just with breakpoint()

def divide(e, f): breakpoint() return f / e a, b = 0, 1 print(divide(a, b))

https://www.python.org/dev/peps/pep-0553/ 10 / 66

slide-11
SLIDE 11

PEP 553: Built-in breakpoint()

  • r just with breakpoint()

def divide(e, f): breakpoint() return f / e a, b = 0, 1 print(divide(a, b)) $ python bugs.py > /tmp/bugs.py(3)divide()

  • > return f / e

(Pdb)

By default, breakpoint will execute pdb.set_trace() https://www.python.org/dev/peps/pep-0553/ 11 / 66

slide-12
SLIDE 12

PEP 553: Built-in breakpoint()

but breakpoint() can be configured with PYTHONBREAKPOINT=0 disables debugging PYTHONBREAKPOINT='' or PYTHONBREAKPOINT= uses the default pdb.set_trace() function PYTHONBREAKPOINT=callable uses the specified callable. PYTHONBREAKPOINT=pudb.set_trace

$ PYTHONBREAKPOINT=0 python bugs.py $ PYTHONBREAKPOINT='' python bugs.py $ PYTHONBREAKPOINT=pudb.set_trace python bugs.py $ PYTHONBREAKPOINT=IPython.embed python bugs.py ...

https://www.python.org/dev/peps/pep-0553/ 12 / 66

slide-13
SLIDE 13

PEP 553: Built-in breakpoint()

  • r you can use an other callable: print()

def divide(e, f): breakpoint(e, f, end=' <-END\n') return f / e PYTHONBREAKPOINT=print python bugs.py 1 0 <-END Traceback (most recent call last): File "bugs.py", line 6, in <module> print(divide(a, b)) File "bugs.py", line 3, in divide return f / e ZeroDivisionError: division by zero

https://www.python.org/dev/peps/pep-0553/ 13 / 66

slide-14
SLIDE 14

PEP 557: Data Classes

Numerous attempts to define classes which exist primarily to store values Examples collections.namedtuple The attrs project (Hi Hynek!) with python classes ... 14 / 66

slide-15
SLIDE 15

PEP 557: Data Classes

With a tuple

>>> person = ('Stephane', 'Wirtel', 37) >>> person[0] # is it the firstname or the lastname?

15 / 66

slide-16
SLIDE 16

PEP 557: Data Classes

With a tuple

>>> person = ('Stephane', 'Wirtel', 37) >>> person[0] # is it the firstname or the lastname?

With a dict

>>> person = {'firstname': 'Stephane', 'lastname': 'Wirtel', 'age': 37} >>> person['firstname'] # would prefer the . notation

16 / 66

slide-17
SLIDE 17

PEP 557: Data Classes

with collections.namedtuple

from collections import namedtuple Person = namedtuple('Person', ['firstname', 'lastname', 'age']) person = Person('Stephane', 'Wirtel', 37) person.age person.firstname person == Person('Stephane', 'Wirtel', 37) person == ('Stephane', 'Wirtel', 37) # but person.age = 38 # will crash

17 / 66

slide-18
SLIDE 18

PEP 557: Data Classes

with a class

class Person: def __init__(self, firstname, lastname, age): self.firstname = firstname self.lastname = lastname self.age = age def __repr__(self): return f'Person(firstname={self.firstname}, lastname={self.lastname})' def __eq__(self, o): return (self.firstname == o.firstname and self.lastname == o.lastname and self.age == o.age) def __lt__(self, o): return (self.firstname < o.firstname and self.lastname < o.lastname and self.age < o.age) person = Person('Stephane', 'Wirtel', 37) person.firstname >>> person == Person('Stephane', 'Wirtel', 37) True >>> person < Person('Stephane', 'Wirtel', 37) False

18 / 66

slide-19
SLIDE 19

PEP 557: Data Classes

but now, you can replace the previous example with this dataclass

So, we can use the dataclass

from dataclasses import dataclass @dataclass class Person: firstname: str lastname: str age: int person = Person('Stephane', 'Wirtel', 37) person.firstname >>> person == Person('Stephane', 'Wirtel', 37) True >>> person < Person('Stephane', 'Wirtel', 37) False

19 / 66

slide-20
SLIDE 20

PEP 557: Data Classes

default values

from dataclasses import dataclass @dataclass class Position: name: str latitude: float = 0.0 longitude: float = 0.0 >>> europython = Position('Edinburgh') Position(name='Edinburgh', latitude=0.0, longitude=0.0)

20 / 66

slide-21
SLIDE 21

PEP 557: Data Classes

dene methods/properties

from dataclasses import dataclass @dataclass class Person: firstname: str lastname: str age: int @property def fullname(self): return f'{self.firstname} {self.lastname}'

21 / 66

slide-22
SLIDE 22

PEP 557: Data Classes

conguration of the @dataclass decorator

init=True => __init__ repr=True=> __repr__ eq=True => __eq__

  • rder=False => __lt__, __le__, __gt__, __ge__

frozen=False => __setattr__, __getattr__ ... 22 / 66

slide-23
SLIDE 23

PEP 557: Data Classes

from dataclasses import dataclass @dataclass(frozen=True) class Person: firstname: str lastname: str age: int >>> person = Person('Stephane', 'Wirtel', 37) >>> person.age = 27 # dataclasses.FrozenInstanceError: cannot assign to field 'age

23 / 66

slide-24
SLIDE 24

PEP 557: Data Classes

Inheritance

@dataclass class Person: firstname: str lastname: str @dataclass class User(Person): username: str password: str person = Person('Stephane', 'Wirtel') user = User('Stephane', 'Wirtel', 'matrixise', 's3cr3t')

24 / 66

slide-25
SLIDE 25

PEP 562: Module __getattr__ and __dir__

Customize the access to an attribute of a module, example, a deprecated function

# lib from warnings import warn deprecated_names = ['old_function'] def _deprectated_old_function(arg, other): ... def __getattr__(name: str) -> Any: if name in deprecated_names: warn(f'{name} is deprecated', DeprecationWarning) return globlals()[f'_deprectated_{name}'] raise AttributeError(f'module {__name__} has no attribute {name}')

25 / 66

slide-26
SLIDE 26

PEP 562: Module __getattr__ and __dir__

Customize the access to an attribute of a module, example, a deprecated function

# lib from warnings import warn deprecated_names = ['old_function'] def _deprectated_old_function(arg, other): ... def __getattr__(name: str) -> Any: if name in deprecated_names: warn(f'{name} is deprecated', DeprecationWarning) return globlals()[f'_deprectated_{name}'] raise AttributeError(f'module {__name__} has no attribute {name}') # main.py from lib import old_function # Works, but emits the warning

26 / 66

slide-27
SLIDE 27

PEP 562: Module __getattr__ and __dir__

Customize the listing of the items from a module

# lib.py deprecated_names = ['old_function'] __all__ = ['new_function_one', 'new_function_two'] def new_function_one(arg, other): ... def new_function_two(arg, other): ... def __dir__() -> List[str]: return sorted(__all__ + deprecated_names)

27 / 66

slide-28
SLIDE 28

PEP 562: Module __getattr__ and __dir__

Customize the listing of the items from a module

# lib.py deprecated_names = ['old_function'] __all__ = ['new_function_one', 'new_function_two'] def new_function_one(arg, other): ... def new_function_two(arg, other): ... def __dir__() -> List[str]: return sorted(__all__ + deprecated_names) # main.py import lib dir(lib) # prints ['new_function_one', 'new_function_two', 'old_function', ...]

28 / 66

slide-29
SLIDE 29

PEP 563: Postponed Evaluation of Annotations

Without the annotations

class Node: def __init__(self, left: Node, right: Node) -> None: self.left = left self.right = right

PEP 484 -- Type Hints PEP 526 -- Syntax for Variable Annotations https://www.python.org/dev/peps/pep-0563 29 / 66

slide-30
SLIDE 30

PEP 563: Postponed Evaluation of Annotations

Without the annotations

class Node: def __init__(self, left: Node, right: Node) -> None: self.left = left self.right = right Traceback (most recent call last): File "test_node.py", line 1, in <module> class Node: File "test_node.py", line 2, in Node def __init__(self, left: Node, right: Node) -> None: NameError: name 'Node' is not defined

PEP 484 -- Type Hints PEP 526 -- Syntax for Variable Annotations https://www.python.org/dev/peps/pep-0563 30 / 66

slide-31
SLIDE 31

PEP 563: Postponed Evaluation of Annotations

Without the annotations

class Node: def __init__(self, left: Node, right: Node) -> None: self.left = left self.right = right Traceback (most recent call last): File "test_node.py", line 1, in <module> class Node: File "test_node.py", line 2, in Node def __init__(self, left: Node, right: Node) -> None: NameError: name 'Node' is not defined

Problem with the forward reference, for that, we have to define the class like that

class Node: def __init__(self, left: 'Node', right: 'Node') -> None: self.left = left self.right = right

PEP 484 -- Type Hints PEP 526 -- Syntax for Variable Annotations https://www.python.org/dev/peps/pep-0563 31 / 66

slide-32
SLIDE 32

PEP 563: Postponed Evaluation of Annotations

Now, with the annotations

from __future__ import annotations class Node: def __init__(self, left: Node, right: Node) -> None: self.left = left self.right = right

and we do not need a forward reference. PEP 484 -- Type Hints PEP 526 -- Syntax for Variable Annotations https://www.python.org/dev/peps/pep-0563 32 / 66

slide-33
SLIDE 33

PEP 563: Postponed Evaluation of Annotations

With the annotations and the dataclasses

from __future__ import annotations from dataclasses import dataclass from typing import Optional @dataclass class Node: left: Optional[Node] = None right: Optiona[Node] = None root = Tree() print(root)

PEP 484 -- Type Hints PEP 526 -- Syntax for Variable Annotations https://www.python.org/dev/peps/pep-0563 33 / 66

slide-34
SLIDE 34

PEP 564: Time functions with nanosecond resolution

Float type limited to 104 days

The Python time.time() function returns the current time as a floating-point

  • numberself. Limited to 64 bits and in the IEEE 754 format. With this

limitation, the float type starts to lose nanoseconds after 104 days.

  • > Precision loss

Example

On Python microbenchmarks, it's common to see function calls taking less than 100ns. A difference of a few nanoseconds might become significant. https://www.python.org/dev/peps/pep-0564 34 / 66

slide-35
SLIDE 35

PEP 564: Time functions with nanosecond resolution

time.clock_gettime_ns time.clock_settime_ns time.monotonic_ns time.perf_counter_ns time.process_time_ns time.time_ns https://www.python.org/dev/peps/pep-0564 35 / 66

slide-36
SLIDE 36

PEP 564: Time functions with nanosecond resolution

time.clock_gettime_ns time.clock_settime_ns time.monotonic_ns time.perf_counter_ns time.process_time_ns time.time_ns

>>> time.monotonic() 154630.068913333

https://www.python.org/dev/peps/pep-0564 36 / 66

slide-37
SLIDE 37

PEP 564: Time functions with nanosecond resolution

time.clock_gettime_ns time.clock_settime_ns time.monotonic_ns time.perf_counter_ns time.process_time_ns time.time_ns

>>> time.monotonic() 154630.068913333 >>> time.monotonic_ns() 154631543907219

https://www.python.org/dev/peps/pep-0564 37 / 66

slide-38
SLIDE 38

PEP 565: Show DeprecationWarning in main

Since Python 3.2, the DeprecationWarning was hidden by default, but there was a side effect, because these warning were only visible then running tests, which meant that developers could be surprised by breaking changes in the APIs they used. 38 / 66

slide-39
SLIDE 39

PEP 565: Show DeprecationWarning in main

Since Python 3.2, the DeprecationWarning was hidden by default, but there was a side effect, because these warning were only visible then running tests, which meant that developers could be surprised by breaking changes in the APIs they used. FutureWarning: always displayed by default DeprecationWarning: displayed by default only in __main__ and when running tests. PendingDeprecationWarning: diplayed by default only when running tests. 39 / 66

slide-40
SLIDE 40

PEP 567: Context Variables

For a synchronous environment, TLS is perfect but not for an asynchronous environment, which in this case, the asynchronous tasks execute concurrently in the same OS thread. This concept is similar to the TLS, but allows correctly keeping track of values per asynchronous task.

  • ne new module: contextvars

two objects: Context and ContextVar 40 / 66

slide-41
SLIDE 41

PEP 567: Context Variables

# demo.py import contextvars var = contextvars.ContextVar('var', default=42) print('without context', var.get()) var.set('spam') def main(): print('inside', var.get()) var.set('ham') print('inside', var.get()) # create a copy of the context (associated to the OS Thread) ctx = contextvars.copy_context() ctx.run(main) print('outside', var.get()) $ python demo.py 42 inside spam inside ham

  • utside spam

41 / 66

slide-42
SLIDE 42

PEP 567: Context Variables

# in this example, asyncio will handle the context for us import contextvars current_user = contextsvars.ContextVar('current_user', default=None) async def inner(): log.debug('Current User: %s', current_user.get()) @routes.get('/{name}') async def handler(request): current_user.set(request.match_info['name']) await inner()

42 / 66

slide-43
SLIDE 43

PEP 545: Python documentation translations

You can find your favourite documenation in: English ;-) French Japanese Korean 43 / 66

slide-44
SLIDE 44

Not in PEPs

44 / 66

slide-45
SLIDE 45

"Dict keeps insertion order" is the ruling!

Thanks to INADA Naoki https://mail.python.org/pipermail/python- dev/2017-December/151283.html 45 / 66

slide-46
SLIDE 46

async & await

are

keywords!

46 / 66

slide-47
SLIDE 47

async & await are keywords!

For async

async = True $ python /tmp/test_async.py File "/tmp/test_async.py", line 1 async = True ^ SyntaxError: invalid syntax

and await

def await(): pass $ python /tmp/test_await.py File "test_await.py", line 1 def await(): ^ SyntaxError: invalid syntax

47 / 66

slide-48
SLIDE 48

asyncio has a lot of improvements

Before, when you wanted to execute a coroutine for asyncio, you needed to create a loop, etc...

# before import asyncio async def hello_world(): print('hello world') loop = asyncio.get_event_loop() try: loop.run_until_complete(hello_world()) finally: loop.close()

48 / 66

slide-49
SLIDE 49

asyncio has a lot of improvements

Before, when you wanted to execute a coroutine for asyncio, you needed to create a loop, etc...

# before import asyncio async def hello_world(): print('hello world') loop = asyncio.get_event_loop() try: loop.run_until_complete(hello_world()) finally: loop.close()

Welcome to the asyncio.run function

# hello asyncio.run() import asyncio async def hello_world(): print('hello world') asyncio.run(hello_world())

49 / 66

slide-50
SLIDE 50

asyncio has a lot of improvements

New functions

asyncio has the support for contextvars asyncio.create_task(): shortcut to asyncio.get_event_loop().create_task() loop.start_tls(): upgrade the existing connection to TLS asyncio.current_task(): returns the current task asyncio.all_tasks(): return all the tasks of a loop loop.sock_sendfile(): use the os.sendfile (when possible + performance) ... 50 / 66

slide-51
SLIDE 51

asyncio has a lot of improvements

Performance improvements

asyncio.get_event_loop has been implemented in C (+- 15 times faster). asyncio.Future callback managmenet has been optimized asyncio.gather() - 15% faster asyncio.sleep() - 2x faster ... https://docs.python.org/3.7/whatsnew/3.7.html#asyncio 51 / 66

slide-52
SLIDE 52

Gifts for the developers

> python -X importtime -c 'import asyncio' import time: self [us] | cumulative | imported package import time: 381 | 381 | zipimport import time: 2044 | 2044 | _frozen_importlib_external import time: 197 | 197 | _codecs import time: 1515 | 1712 | codecs ...

52 / 66

slide-53
SLIDE 53

Gifts for the developers

> python -X utf8 your_script.py > PYTHONUTF8=1 python your_script.py

will enable the UTF-8 mode disabled by default but automatically enabled if we use the "POSIX" locale if enabled: Python will use the utf-8 encoding, regardless the locale of the current platform. sys.getfilesystemencoding() returns UTF-8 locale.getpreferredencoding() return UTF-8 sys.stdin & sys.stdout error handlers to surrogateescape (avoid a UnicodeDecodeError) 53 / 66

slide-54
SLIDE 54

Gifts for the developers

> python -X dev

enable the "development mode" add additional runtime checks install debug hooks for the memory allocators enable the faulthandler module for a beautiful Python dump on a crash ;-) enable the asyncio debug mode 54 / 66

slide-55
SLIDE 55

importlib.resources

Allows to load a binary artifact that is shipped within a package. Provides functionality similar to pkg_resources but without all of the

  • verhead and performance problems of pkg_resources

a Resource can live on the File System, a zip file or anywhere. 55 / 66

slide-56
SLIDE 56

importlib.resources

Allows to load a binary artifact that is shipped within a package. Provides functionality similar to pkg_resources but without all of the

  • verhead and performance problems of pkg_resources

a Resource can live on the File System, a zip file or anywhere. My package

email/ ├── __init__.py └── tests ├── data │ ├── __init__.py │ └── message.eml └── __init__.py

56 / 66

slide-57
SLIDE 57

importlib.resources

Get the content of email/tests/data/message.eml without importlib.resources

data_dir = os.path.join(os.path.dirname(__file__), 'tests', 'data') data_path = os.path.join(data_dir, 'message.eml') with open(data_path, encoding='utf-8') as fp: eml = fp.read()

57 / 66

slide-58
SLIDE 58

importlib.resources

Get the content of email/tests/data/message.eml without importlib.resources

data_dir = os.path.join(os.path.dirname(__file__), 'tests', 'data') data_path = os.path.join(data_dir, 'message.eml') with open(data_path, encoding='utf-8') as fp: eml = fp.read()

  • r

dirname = pathlib.Path(__file__).parent data_path = dirname / 'tests' / 'data' / 'message.eml' eml = data_path.read_bytes()

58 / 66

slide-59
SLIDE 59

importlib.resources

with importlib.resources

from importlib import resources eml = resources.read_binary('email.tests.data', 'message.eml')

59 / 66

slide-60
SLIDE 60

importlib.resources

with importlib.resources

from importlib import resources eml = resources.read_binary('email.tests.data', 'message.eml')

  • r with a context manager

from importlib import resources with resources.path('email.tests.data', 'message.eml') as eml: print(eml.read_binary())

60 / 66

slide-61
SLIDE 61

importlib.resources

with importlib.resources

from importlib import resources eml = resources.read_binary('email.tests.data', 'message.eml')

  • r with a context manager

from importlib import resources with resources.path('email.tests.data', 'message.eml') as eml: print(eml.read_binary())

  • r

import email.tests.data with resources.parth(email.tests.data, 'message.eml') as eml: print(eml.read_binary())

https://docs.python.org/fr/3.7/library/importlib.html https://www.youtube.com/watch?v=ZsGFU2qh73E 61 / 66

slide-62
SLIDE 62

and many improvements, bugxes...

62 / 66

slide-63
SLIDE 63

Performances

63 / 66

slide-64
SLIDE 64

Conclusion

64 / 66

slide-65
SLIDE 65

Python 3.7 is a great vintage!

65 / 66

slide-66
SLIDE 66

stephane.wirtel@mgx.io @matrixixe 66 / 66