Оновлення Python 3.12 — чи справді мова тепер вдвічі швидша. Ключові зміни
Привіт! Мене звуть Олексій. Я студент першого курсу магістратури у Київському політехнічному інституті ім. Сікорського, і я програмую на Python уже близько пʼяти років. Це вже третя стаття із серії #найголовніші_фічі, які зʼявляються з новими версіями Python.
Поговоримо про зміни, бо Python міцно тримає лідерську позицію у рейтингу TIOBE Index for January 2024 & PYPL Popularity of Programming Language, залишаючи позаду Java, JS, та C/C++. У чому ж секрет?
Я розділив статтю на дві частини: практичну та теоретичну.
Перша (яку ви зараз читаєте) — основна. Тут ви дізнаєтеся про ключові зміни, які приніс нам реліз Python 3.12. Ви також познайомитеся з прикладами використання цих змін, довідаєтеся про їхні переваги та чи варто якнайшвидше переходити на нову версію.
У другій частині зосередимо більше уваги на порівнянні швидкодії (performance comparison) останніх версій: від інсталяції версій Python за останні п’ять років (версії
Зміни у Python 3.12
Release Date: October 2023
Новий синтаксис параметра типів — type
Нагадаю, що в Python 3.5 (PEP 484) було внесено концепцію змінних типів. Розвиток цієї ідеї відбувся у PEP 612 (яке впровадило специфікації параметрів), і PEP 646 (яке розширило це поняття до змінних варіаційних типів).
Хоча generic-типи та параметри типів стали популярними, синтаксис визначення параметрів типів все ще виглядає дещо «прикріпленим» до Python. Це призводить до плутанини серед розробників Python.
Розгляньмо приклад з функцією, що відповідатиме за сортування (алгоритмом QuickSort) заданого списку, що складається з одного з елементів можливих типів int
, float
чи str
:
def sort(iterable: list) -> list: """ Sorts a list by using quicksort in ascending order. Parameters: - iterable (list[]): The input list to be sorted. Returns: - list[T]: The sorted list. """ less = [] equal = [] greater = [] if len(iterable) > 1: pivot = iterable[0] for x in iterable: if x < pivot: less.append(x) elif x == pivot: equal.append(x) elif x > pivot: greater.append(x) return sort(less) + equal + sort(greater) else: return iterable
Що є аналогічним для
from typing import Any def sort(iterable: list[Any]) -> list[Any]: # Sorting realization
Тобто тут ми оголошуємо функцію, що приймає на вхід список, який може містити будь-які елементи. Результатом може також бути список, що складається з будь-яких елементів.
Наведу приклад використання:
int_list = [1, 0, 3, 2] sort(int_list) >>> [0, 1, 2, 3] float_list = [1.0, 0.0, 3.0, 2.0] sort(float_list) >>> [0.0, 1.0, 2.0, 3.0] str_list = ["1", "a", "2", "b", "3", "c"] sort(str_list) >>> ['1', '2', '3', 'a', 'b', 'c']
Досі в реалізації ми не показали можливі типи int
, float
та str
. Ви можете подумати, що для цього достатньо перебрати їх у множині Union
, як тут:
def sort(iterable: list[Union[int, float, str]]) -> list[Union[int, float, str]]: # Sorting realization
Чи, як я писав у статті про нові можливості Python 3.10, за допомогою використання нового Union оператора |
:
def sort(iterable: list[int | float | str]) -> list[int | float | str]: # Sorting realization
Але ні, у цьому випадку ми дозволимо передавати на вхід списки з різними типами:
different_list = [1, 1.0, "1"]
Це призведе до помилки типізатора:
error: Unsupported operand types for < ("int" and "str") [operator] error: Unsupported operand types for < ("float" and "str") [operator] error: Unsupported operand types for < ("str" and "int") [operator] error: Unsupported operand types for < ("str" and "float") [operator] note: Both left and right operands are unions error: Unsupported operand types for > ("int" and "str") [operator] error: Unsupported operand types for > ("float" and "str") [operator] error: Unsupported operand types for > ("str" and "int") [operator] error: Unsupported operand types for > ("str" and "float") [operator] note: Both left and right operands are unions error: Argument 1 to "sort" has incompatible type "list[int]"; expected "list[int | float | str]" [arg-type] note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance note: Consider using "Sequence" instead, which is covariant error: Argument 1 to "sort" has incompatible type "list[float]"; expected "list[int | float | str]" [arg-type] note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance note: Consider using "Sequence" instead, which is covariant error: Argument 1 to "sort" has incompatible type "list[str]"; expected "list[int | float | str]" [arg-type] note: "List" is invariant -- see https://mypy.readthedocs.io/en/stable/common_issues.html#variance note: Consider using "Sequence" instead, which is covariant Found 11 errors in 1 file (checked 1 source file)
Ми прагнемо показати, що список елементів з типом на виході функції відповідає типу, з елементів яких складається параметр списку на вході функції. Для порівняння, всередині алгоритму ми також будемо використовувати елементи однакового типу. Для цього нам потрібно використати параметризований параметр, TypeVar
.
from typing import TypeVar T = TypeVar("T") def sort(iterable: list[T]) -> list[T]: # Sorting realization
На перший погляд може здатися, що все чудово. Ну, або точно краще, ніж було. Та є нюанс. У групі розробників, які використовують статичну типізацію Python, існує консенсус, що настав час введення формального синтаксису, який би більше відповідав сучасним мовам програмування з підтримкою узагальнених типів.
Аналіз 25 популярних типізованих бібліотек Python показав, що типові змінні (зокрема, символ typing.TypeVar
) використовуються у 14% модулів.
У Python 3.12 ми отримали нову можливість представити аналогічну функцію — тільки вже без оголошення TypeVar
:
def sort[T](iterable: list[T]) -> list[T]: # Sorting realization
*Застереження. Якщо отримали схожу помилку:
File "/home/oleksii/python/example.py", line 1 def sort[T](iterable: list[T]) -> list[T]: ^ SyntaxError: expected '('
Спробуйте перевірити, чи точно ви здійснюєте виконання свого коду з Python 3.12 😉.
На момент тестування, mypy
ще не підтримує цей синтаксис. Тому поки що отримуємо помилку:
error: PEP 695 generics are not yet supported [valid-type] error: Name "T" is not defined [name-defined]
Проте після додавання підтримки даного синтаксису до всіх статичних типізаторів ви зможете отримати всі переваги цього лаконічного запису.
Для повного завершення поставленої задачі ми мусимо ще дописати до T bounds
типів int
, float
та str
.
T = TypeVar("T", int, float, str)
Повторити це для нового синтаксису ви зможете за допомогою:
def sort[T: (int, float, str)](iterable: list[T]) -> list[T]: # Sorting realization
Тепер ви більше не потребуватимете імпортування та ручної ініціалізації TypeVar. Крім того, ви зможете також використовувати цей спосіб застосування з декількома типами і класами. Розгляньмо приклад використання з останніми:
class SortedList[T: (int, float, str)]: def __init__(self, iterable: list[T]) -> None: self.sorted_list = sort(iterable) def get_sorted_list(self) -> list[T]: return self.sorted_list int_list = [1, 0, 3, 2] int_sorted_list = SortedList[int](int_list) int_sorted_list.get_sorted_list()
Проте я і досі не розповів про новий синтаксис параметра типів — type
. Відтепер ми можемо створювати псевдоніми типів. Для оголошення використовується інструкція type:
type Item = dict[int, int | float | str] | dict[str, str]
У випадку, коли нам потрібно описати вкладену структуру з багатьма можливими варіантами застосування множиного оператора |
або Union
, це дуже зіграє нам на руку. Адже варто уникати багаторазового використання таких обʼємних записів у код базі, коли є можливість використовувати псевдоніми.
Синтаксична формалізація f-рядків
У Python 3.12 з’явилися чудові покращення для f-рядків, що забезпечують більшу гнучкість та виразність форматування рядків. Покращення формалізовано у PEP 701, який вносить синтаксичні зміни до f-рядків. Це знімає попередні обмеження і робить f-string ще потужнішими.
У Python 3.11 повторне використання тих самих лапок, що й укладений в них f-рядок, викликало синтаксичну помилку. Це обмежувало можливості вкладеності.
Приклад:
import random random_choice = random.choice([True, False]) motto = f'I like to read Tolkien\'s {'Hobbit' if random_choice else 'The Lord of the Rings'}!' motto >>> File "/home/oleksii/python/example.py", line 4 >>> motto = f'I like to read Tolkien\'s {'Hobbit' if random_choice else 'The Lord of the Rings'}!' >>> ^^^^^^ >>> SyntaxError: f-string: expecting '}'
Для подолання цієї проблеми потрібно було використовувати інші лапки. Наприклад:
motto = f"I like to read Tolkien\'s {'Hobbit' if random_choice else 'The Lord of the Rings'}!" motto >>> I like to read Tolkien's The Lord of the Rings!
Спосіб маркувати кожен вкладений символ лапок символом \
(як це показано з Tolkien\’s
) не працював:
motto = f'I like to read Tolkien\'s {\'Hobbit\' if random_choice else \'The Lord of the Rings\'}!' >>> File "/home/oleksii/python/example.py", line 4 >>> motto = f'I like to read Tolkien\'s {\'Hobbit\' if random_choice else \'The Lord of the Rings\'}!' >>> ^ >>> SyntaxError: f-string expression part cannot include a backslash
Спосіб не спрацював, бо f-рядки зовсім не могли містити зворотні слеші, як символ \n
для переведення на новий рядок, чи символи Юнікоду.
У Python 3.12 використовувати зворотні слеші для позначення внутрішніх лапок і далі не можна, проте у цьому тепер немає потреби. Обмеження на символи Юнікоду та використання тих самих лапок, що позначають f-рядки, було знято. Це дозволяє більш виразно використовувати f-рядки:
import random random_choice = random.choice([True, False]) motto = f'I like to read Tolkien\'s {'\nHobbit' if random_choice else '\nThe Lord of the Rings'}!' motto >>> I like to read Tolkien's >>> The Lord of the Rings!
Окрім того, тепер f-рядки можна вкладати довільно, що забезпечує більшу гнучкість:
f'{f'{f'The Lord of' + f'the Rings!'}'}' # Missed space here! >>> The Lord ofthe Rings!
Також зʼявилась можливість багаторядкових виразів та коментарів.
Python 3.12 дозволяє визначати f-рядки у декількох рядках і включає вбудовані коментарі для покращення читабельності:
f'{ # First comment f'{ # Second comment 'The Lord of' + 'the Rings!' # Missed space here! }' }' >>> The Lord ofthe Rings!
Ці зміни дозволяють створювати більш виразні та універсальні f-рядкові вирази.
Разом із цим покращенням ми отримали точніше повідомлення про помилку.
Python 3.12 містить точніші повідомлення про помилки для f-рядків завдяки синтаксичному аналізатору PEG. На відміну від Python 3.11:
text = f'{1 2 3}' >>> File "/home/oleksii/python/example.py", line 1 >>> (1 2 3) >>> ^^^ >>> SyntaxError: f-string: invalid syntax. Perhaps you forgot a comma?
Повідомлення тепер показують точне місце помилки в рядку:
text = f'{1 2 3}' >>> Traceback (most recent call last): >>> File "<frozen runpy>", line 189, in _run_module_as_main >>> File "<frozen runpy>", line 112, in _get_module_details >>> File "/home/oleksii/python/example.py", line 1 >>> text = f'{1 2 3}' >>> ^^^ >>> SyntaxError: invalid syntax. Perhaps you forgot a comma?
Крім того, завдяки змінам в PEP 701 генерація токенів за допомогою модуля tokenize
прискорюється на 64%.
GIL для кожного інтерпретатора. Шлях до паралелізму
Глобальний блокувальник інтерпретатора (GIL) у Python традиційно обмежував одночасне виконання потоків, ускладнюючи роботу з паралельним кодом. PEP 684 вносить кардинальні зміни, дозволяючи кожному субінтерпретатору мати власний GIL. Це забезпечує краще використання потужностей багатоядерних процесорів.
Для створення субінтерпретатора з власним GIL розробники можуть скористатися функцією Py_NewInterpreterFromConfig()
, як показано у прикладі. Можливість створювати підінтерпретатори з окремими GIL наразі доступна через
З окремим GIL для кожного інтерпретатора розробники Python отримують гнучкість у дослідженні нових моделей паралелізму. Під капотом відбувся рефакторинг внутрішніх компонентів CPython. Ініціатива передбачала перенесення більшої частини глобального сховища станів до сховища для кожного інтерпретатора.
Попри відсутність суттєвих змін, версія Python 3.12 водночас і не змінює поточних підходів, і є підготовкою до великих покращень, бо закладає основу для майбутнього розвитку паралелізму.
Покращення повідомлень помилок NameError, ImportError і SyntaxError
Ми вже розглядали, як з кожною версією Python розробники ядра покращували вивід помилок та процес знаходження помилок. Завдяки цьому знаходження помилок у ваших програмах стає очевиднішим і простішим.
У Python 3.10 повідомлення про помилки, особливо пов’язані з синтаксисом, стали більш інформативними та точними.
У Python 3.11 були розширені можливості трасування, що полегшило пошук проблемного коду.
Останній випуск, Python 3.12, продовжує цю тенденцію до покращення досвіду розробників, надаючи більш інформативні повідомлення про помилки. Зокрема деякі поширені повідомлення про помилки тепер супроводжуються корисними підказками. Розгляньмо детальніше ці нові й поліпшені повідомлення.
Значна частина цих покращень стосується процесу імпорту модулів. Уявімо сценарій, у якому ми будемо працювати зі змінними оточення, дістаючи їх з модуля os
, імпортуючи його перед цим.
3.11:
os.getenv("PROJECT_NAME") >>> Traceback (most recent call last): >>> File "/home/oleksii/python/example.py", line 1, in <module> >>> os.getenv("PROJECT_NAME") >>> ^^ >>> NameError: name 'os' is not defined 3.12: os.getenv("PROJECT_NAME") >>> Traceback (most recent call last): >>> File "<stdin>", line 1, in <module> >>> NameError: name 'os' is not defined. Did you forget to import 'os'?
Під час спроби використати os
без його попереднього імпорту виникає традиційна помилка виклику NameError
. Однак у Python 3.12 з’явилося корисне нагадування, яке радить вам імпортувати модуль os
перед спробою отримати до нього доступ.
Це нагадування про імпорт стосується лише стандартних бібліотечних модулів і не відстежує сторонні бібліотеки, які ви могли встановити.
Ще одне покращення тексту в інформації помилки також стосується імпорту. Тепер, якщо ви випадково поміняєте порядок ключових слів в інструкції from-import:
Python 3.11:
import getenv from os >>> File "/home/oleksii/python/example.py", line 1, in <module> >>> import getenv from os >>> ^^^^ >>> SyntaxError: invalid syntax
Python 3.12 дружелюбно підштовхне вас до правильного синтаксису:
import getenv from os >>> File "<stdin>", line 1 >>> import getenv from os >>> ^^^^^^^^^^^^^^^^^^^^^ >>> SyntaxError: Did you mean to use 'from ... import ...' instead?
У цьому прикладі спроба імпортувати getenv
з os
модуля викликає синтаксичну помилку. Не впевнений, що вона поширена серед досвідчених розробників, проте Python 3.12 виявляє її і пропонує використовувати правильний синтаксис з from ... import ....
.
Ще одне покращення повідомлення про помилку стосується імпорту конкретних імен з модуля.
Python 3.11:
from os import get_env >>> File "/home/oleksii/python/example.py", line 1 >>> >>> Traceback (most recent call last): >>> ^^ >>> SyntaxError: invalid syntax
Python 3.12:
from os import get_env >>> Traceback (most recent call last): >>> File "<stdin>", line 1, in <module> >>> ImportError: cannot import name 'get_env' from 'os' (/home/oleksii/.pyenv/versions/3.12.1/lib/python3.12/os.py). Did you mean: 'getenv'?
У цьому випадку спроба імпортувати get_env
з os
призводить до помилки імпорту. Python 3.12 припускає, що ви, ймовірно, хотіли імпортувати getenv
замість get_env
. Ця функція, запроваджена в Python 3.10, тепер поширюється і на операторів імпорту в Python 3.12.
Окрім покращень, пов’язаних з імпортом, у Python 3.12 впроваджене також покращення методів, визначених усередині класів.
Розгляньмо для прикладу реалізацію класу Person
:
class Person: def __init__(self, name: str): self.name = name def print_name(self): print(name)
Python 3.11:
Person("Oleksii").print_name() >>> Traceback (most recent call last): >>> File "/home/oleksii/python/example.py", line 141, in <module> >>> Person("Oleksii").print_name() >>> File "/home/oleksii/python/example.py", line 138, in print_name >>> print(name) >>> ^^^^ >>> NameError: name 'name' is not defined
Python 3.12:
Person("Oleksii").print_name() >>> Traceback (most recent call last): >>> File "<stdin>", line 1, in <module> >>> File "<stdin>", line 5, in print_name >>> NameError: name 'name' is not defined. Did you mean: 'self.name'?
Замість звичайного NameError
, Python 3.12 розпізнає, що name є атрибутом, доступним на self
, і пропонує використовувати атрибут екземпляра self.name
замість локального імені name
.
Buffer-протокол
У Python 3.12 впроваджене PEP 688, що робить буферний протокол доступним безпосередньо з коду Python.
Це покращення дозволяє класам, що реалізують метод __buffer__()
, використовувати буферні типи. Крім того, доданий абстрактний клас collections.abc.Buffer
(ABC), що стандартизує представлення буферних об’єктів, полегшуючи їх використання в анотаціях типів.
Мотивують PEP 688 тим, що це має допомогти подолати розрив між буферним протоколом Python та C.
Буферний протокол, який спочатку був доступний лише для коду на C, тепер отримав API на рівні Python. Ця розробка дозволяє засобам статичної типізації оцінювати, чи об’єкти реалізують протокол, та усуває обмеження поточних опцій.
Наразі в коді Python відсутній механізм перевірки того, чи підтримує об’єкт буферний протокол. У статичних типізаторах також відсутня спеціальна анотація типу для представлення протоколу.
Це стає проблемою під час написання анотацій типів для коду, який приймає загальні буфери. Є два шляхи використання псевдоніму типу для відомих типів буферів та використання typing.ByteString
, але їх вважають неадекватними з різних причин.
Буферний протокол C підтримує різні можливості, пов’язані з кроками, неперервністю та підтримкою запису у буфер. Хоча typeshed
надає псевдоніми типів для буферів, доступних для запису та читання, багато з цих опцій не можуть бути безпосередньо запитані в об’єкті типу в буферному протоколі C.
Практичний приклад з використанням collections.abc.Buffer
, що наводить документація:
def need_buffer(b: Buffer) -> memoryview: return memoryview(b) need_buffer(b"xy") # ok need_buffer("xy") # rejected by static type checkers
Також це може бути використане з isinstance
та issubclass
перевірками:
from collections.abc import Buffer isinstance(b"xy", Buffer) >>> True issubclass(bytes, Buffer) >>> True issubclass(memoryview, Buffer) >>> True isinstance("xy", Buffer) >>> False issubclass(str, Buffer) >>> False
Щоб продемонструвати використання буферного протоколу, розгляньмо наступний клас Python:
import contextlib import inspect class MyBuffer: def __init__(self, data: bytes): self.data = bytearray(data) self.view = None def __buffer__(self, flags: int) -> memoryview: if flags != inspect.BufferFlags.FULL_RO: raise TypeError("Only BufferFlags.FULL_RO supported") if self.view is not None: raise RuntimeError("Buffer already held") self.view = memoryview(self.data) return self.view def __release_buffer__(self, view: memoryview) -> None: assert self.view is view # guaranteed to be true self.view.release() self.view = None def extend(self, b: bytes) -> None: if self.view is not None: raise RuntimeError("Cannot extend held buffer") self.data.extend(b) buffer = MyBuffer(b"capybara") with memoryview(buffer) as view: view[0] = ord("C") with contextlib.suppress(RuntimeError): buffer.extend(b"!") # raises RuntimeError buffer.extend(b"!") # ok, buffer is no longer held with memoryview(buffer) as view: assert view.tobytes() == b"Capybara!"
Підвищення продуктивності isinstance та asyncio
З незначних покращень можна виокремити приріст швидкодії, про яку ми детальніше говоримо в другій статті.
У Python 3.12 функція isinstance()
для перевірок протоколів, що перевіряються під час виконання, має помітні покращення під час перевірки протоколів. Більшість перевірок isinstance()
протоколів мають бути принаймні удвічі швидшими, ніж у версії 3.11.
Деякі можуть бути навіть у 20 разів швидшими, або і більше. Разом з тим, перевірка isinstance()
протоколів з чотирнадцятьма або більше членами може, навпаки, бути повільнішою, ніж у Python 3.11.
Пакет asyncio в Python 3.12 також зазнав значних покращень продуктивності. Це призвело до збільшення швидкості на 75%, у чому ми з вами спробуємо переконатись у другій частині цієї статті, де ми позапускаємо різні бенчмарки та порівняємо зміни швидкодії.
Використання TypedDict для точнішої анотації **kwargs
Розгляньмо PEP 692 — першу зміну, яка стосується анотації. Тепер ми маємо змогу більш детально описувати **kwargs
. **kwargs
— спеціальний синтаксис, який дозволяє передавати до функції словник аргументів змінної довжини з ключовими словами. Це скорочення від keyword-arguments (аргументи ключового слова).
Раніше, коли ми передавали **kwargs
як словник з ключами типу str
та будь-якими значеннями, ми могли як максимум тільки скористатись конструкцією **kwargs: Any
. Або, якщо щастило, **kwargs: dict[str, Any]
. Проте зараз ми можемо оголосити TypedDict
, та використовуючи typing.Unpack
, явно вказати, що очікується на вході **kwargs
.
Приклад:
from typing import TypedDict, Unpack class Book(TypedDict): title: str author: str publication_year: int def process_book_info(**kwargs: Unpack[Book]) -> str: title = kwargs['title'] author = kwargs['author'] publication_year = kwargs['publication_year'] book_info = f"Title: {title}\nAuthor: {author}\nYear of Publication: {publication_year}" # Можлива якась додаткова логіка обробки return book_info lotr_info: Book = {"title": "The Lord of the Rings", "author": "J.R.R. Tolkien", "publication_year": 1954} result = process_book_info(**lotr_info) result >>> Title: The Lord of the Rings >>> Author: J.R.R. Tolkien >>> Year of Publication: 1954
Ця можливість уже підтримується типізаторами, а ось під час запуску на версії Python 3.11 ми отримаємо таку помилку:
error: "Unpack" support is experimental, use --enable-incomplete-feature=Unpack to enable [misc]
@override для явного перевизначення методів
Другою новинкою для анотації типів у Python 3.12 став декоратор @override
— цікавий спосіб типізації, призначений для явного позначення методів у підкласах, які перевизначають їхні аналоги у батьківському класі. Це вдосконалення є подібним до механізмів в таких мовах, як-от Java та C++.
Попри сприяння безпечного кодингу та запобігання помилкам під час рефакторингу коду, анотація типів не ставить за обовʼязок використовувати цей декоратор у кожному методі підкласу. Чи приживеться цей спосіб у пересічного розробника — я ще не впевнений.
Знаю точно, що цей декоратор (як і @abstractmethod
для батьківського) посилює зв’язку між класами та допомагає розробникам створювати більш надійний та стійкий до помилок код за допомогою аналізу інструментами статичної типізації.
Приклад використання:
from typing import override class BaseCase: def __init__(self, text: str): self.text = text def print_color(self): print(self._get_text()) def _get_text(self) -> str: return self.text class UpperCase(BaseCase): @override def _get_text(self) -> str: return self.text.upper() class LowerCase(BaseCase): @override # Помилка Missing super method for override. У назві пропущено "_" префікс def get_text(self) -> str: return self.text.lower()
Підсумки
У цьому оновлені, у порівнянні з минулими, ми отримали:
- Новий синтаксис параметра типів —
type
. - Синтаксичну формалізацію f-рядків.
- GIL для кожного інтерпретатора.
- Покращення повідомлень помилок
NameError
,ImportError
, іSyntaxError
. - Buffer-протокол.
- Підвищення продуктивності
isinstance
таasyncio
. - Використання TypedDict для точнішої анотації
**kwargs
. @override
для явного перевизначення методів.
Деякі модулі в новій версії вже застаріли. А ті, що були застарілими в попередніх версіях, могли бути підчищенні чи навіть повністю видалені.
Якщо збираєтесь оновлюватись і використовуєте один з них, зверніть увагу на список модулів що зазнав змін: asynchat, asyncore, configparser, distutils, ensurepip, enum, ftplib, gzip, hashlib, importlib, imp, io, locale, smtpd, sqlite3, ssl, unittest, webbrowser, xml.etree.ElementTree, zipimport та інші.
Впевнено сказати, що ви мусите оновлювати ваші проєкти до 3.12, я не можу. Зміни сильно вплинули на деякі частини розробки, торкнулись покращення знаходження помилок, додали можливість кожному інтерпретатору окремо працювати з GIL.
Це важливо, бо закладає фундамент для паралелізму в нових версіях. Гвідо ван Россум (автор мови Python) обіцяв, що Python 3.11 буде вдвічі швидшим. Чи вдалося йому дотриматися обіцянки?
На це питання шукайте відповідь у другій частині статті. Там я встановлю паралельно останні версії Python та покажу як це зробити, використовуючи pyenv
. Ми також запустимо pyperformance
та займемось порівнянням швидкодії (performance comparison
) останніх версій Python.
Якшо ви ще не читали попередні випуски, або бажаєте освіжити знання, нижче ви знайдете посилання на велику статтю, де я розібрав зміни Python з версії 3.8 до версії 3.10 та посилання на статтю зі змінами Python 3.11:
- Що нового в Python 3.10. Функціонал та найголовніші зміни.
- Що нового в Python 3.11 — функціонал та найголовніші зміни.
Дякую за прочитання. Слава Україні 🇺🇦
Джерела:
37 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів