- PVSM.RU - https://www.pvsm.ru -

Как изменилась стандартная библиотека Python за последние годы

Python 3.8+

Когда выходит очередная версия Python, все внимание достается новым фичам языка: моржовому оператору, слиянию словарей, паттерн-матчингу. Еще много пишут об изменениях в асинхронной работе (модуль asyncio) и типизации (модуль typing) — эти модули на виду и бурно развиваются.

Остальным модулям стандартной библиотеки достается незаслуженно мало внимания. Хочу это исправить и рассказать, что интересного появилось в версиях 3.8–3.10.

Конечно, это не исчерпывающий список. Пишу только о тех изменениях, которые заинтересовали лично меня. Но поскольку я не слишком сильно отличаюсь от «среднего» бэкенд-разработчика на питоне — вполне вероятно, что вас они тоже заинтересуют. Если что-то пропустил — дополняйте в комментариях.

Модули идут в алфавитном порядке, так что если заскучаете на первых (малоизвестных) представителях, не унывайте — дальше будет интереснее.

array [1]base64 [2]bisect [3]builtins [4]dataclasses [5]datetime [6]fractions [7]functools [8]glob [9]graphlib [10]itertools [11]math [12]random [13]shlex [14]shutil [15]statistics [16]zoneinfo [17]

Все примеры рабочие. Выполнять можно в песочнице (ссылки под примерами), либо локально. Если локально у вас старый Python — запускайте через Docker:

$ docker run -it --rm python:3.10-alpine

array

Модуль array [18] предоставляет компактные однотипные числовые массивы. Используется намного реже, чем знаменитый собрат list.

Метод array.index() [19] находит значение в массиве и возвращает индекс найденного элемента. Теперь он поддерживает необязательные параметры start и stop, которые задают интервал поиска (3.10+):

from array import array
arr = array("i", [7, 11, 19, 42])

idx = arr.index(11)
# idx == 1

idx = arr.index(11, 2)
# ValueError: array.index(x): x not in array

песочница [20]

Разработчики: Anders Lorentsen [21]Zackery Spytz [22]

base64

Модуль base64 [23] кодирует бинарные данные в ASCII-строки по алгоритмам Base16, Base32 и Base64.

Он обзавелся парой новых функций b32hexencode() [24] и b32hexdecode() [25], которые используют расширенный 32-символьный алфавит согласно RFC 4648 [26] (3.10+):

import base64
bytes = b"python is awesome"

base64.b32encode(bytes)
# b'OB4XI2DPNYQGS4ZAMF3WK43PNVSQ===='

base64.b32hexencode(bytes)
# b'E1SN8Q3FDOG6ISP0C5RMASRFDLIG===='

песочница [27]

Разработчик: Filipe Laíns [28]

bisect

Модуль bisect [29] работает с отсортированными списками методом бинарного поиска. Основные функции:

  • bisect() [30] находит элемент в списке;
  • insort() [31] добавляет элемент, сохраняя порядок.

import bisect

lst = [7, 11, 19, 42]
idx = bisect.bisect(lst, 12)
# idx == 2

bisect.insort(lst, 12)
# [7, 11, 12, 19, 42]

С версии 3.10 все функции модуля поддерживают необязательный параметр key. Это функция, которая возвращает значение элемента списка. Удобно использовать, если элементы напрямую несравнимы:

import bisect
import operator

p1 = {"id": 11, "name": "Diane"}
p2 = {"id": 12, "name": "Bob"}
p3 = {"id": 13, "name": "Emma"}

key = operator.itemgetter("name")
people = sorted([p1, p2, p3], key=key)
# Bob, Diane, Emma

idx = bisect.bisect(people, "Dan")
# TypeError: '<' not supported between instances of 'str' and 'dict'

idx = bisect.bisect(people, "Dan", key=key)
# idx == 1

песочница [32]

Разработчик: Raymond Hettinger [33]

builtins

Модуль builtins [34] содержит все «встроенные» функции и классы, которые программисты используют без всяких импортов: int, list, len(), open() и тому подобное.

import builtins

list is builtins.list
# True

len is builtins.len
# True

У строки появились методы str.removeprefix() [35] и str.removesuffix() [36], которые отрезают голову и хвост соответственно (3.9+):

s = "Python is awesome"

s.removeprefix("Python is ")
# 'awesome'

s.removesuffix(" is awesome")
# 'Python'

У целого числа добавился метод int.bit_count() [37], который возвращает количество единиц в бинарном представлении числа (3.10+):

n = 42

bin(n)
# '0b101010'

n.bit_count()
# 3

Методы словаря dict.keys(), dict.values() и dict.items() возвращают объекты-представления (view objects), которые не дублируют данные словаря, а ссылаются на них. Раньше из этих объектов нельзя было получить обратную ссылку на словарь, а теперь можно — через атрибут .mapping (3.10+):

people = {
    "Diane": 70,
    "Bob": 78,
    "Emma": 84
}

keys = people.keys()
# dict_keys(['Diane', 'Bob', 'Emma'])

keys.mapping["Bob"]
# 78

Функция объединения коллекций zip() [38] получила параметр strict. Он проверяет, что последовательности одинаковой длины (3.10+):

keys = ["Diane", "Bob", "Emma"]
vals = [70, 78, 84, 42]

pairs = zip(keys, vals)
list(pairs)
# [('Diane', 70), ('Bob', 78), ('Emma', 84)]

pairs = zip(keys, vals, strict=True)
list(pairs)
# ValueError: zip() argument 2 is longer than argument 1

песочница [39]

Разработчики: Dennis Sweeney [40]Niklas Fiekas [41]Brandt Bucher [42]

dataclasses

Модуль dataclasses [43] генерит классы по спецификации.

Датаклассы теперь могут использовать слоты [44] (slots) для компактных объектов с фиксированным набором свойств (3.10+).

Обычный датакласс:

from dataclasses import dataclass

@dataclass
class Person:
    id: int
    name: str

diane = Person(id=11, name="Diane")
diane.__dict__
# {'id': 11, 'name': 'Diane'}
diane.salary = 70
# ok

Со слотами:

from dataclasses import dataclass

@dataclass(slots=True)
class SlotPerson:
    id: int
    name: str

bob = SlotPerson(id=12, name="Bob")
bob.__dict__
# AttributeError: 'SlotPerson' object has no attribute '__dict__'
bob.__slots__
# ('id', 'name')
bob.salary = 78
# AttributeError: 'SlotPerson' object has no attribute 'salary'

Кроме того, датакласс теперь можно заставить принимать только словарные (keyword-only) параметры при создании объекта (3.10+):

from dataclasses import dataclass

@dataclass(kw_only=True)
class KeywordPerson:
    id: int
    name: str

diane = KeywordPerson(id=11, name="Diane")
# ok
diane = KeywordPerson(11, "Diane")
# TypeError: KeywordPerson.__init__() takes 1 positional argument but 3 were given

песочница [45]

Разработчики: Yurii Karabas [46]Eric V. Smith [47]

datetime

Модуль datetime [48] работает с датой и временем.

Добавились конструкторы date.fromisocalendar() [49] и datetime.fromisocalendar() [50], которые создают дату из троицы (год, неделя, день_недели) (3.8+):

import datetime as dt

day = dt.date(2022, 9, 13)
day.isocalendar()
# datetime.IsoCalendarDate(year=2022, week=37, weekday=2)

year, week, day = day.isocalendar()
next_day = dt.date.fromisocalendar(year, week, day+1)
# datetime.date(2022, 9, 14)

Кроме того, метод .isocalendar() теперь возвращает не обычный кортеж, а именованный IsoCalendarDate (3.9+). Это видно в примере выше.

песочница [51]

Разработчики: Paul Ganssle [52]Dong-hee Na [53]

fractions

Модуль fractions [54] работает с рациональными числами.

Он получил метод Fraction.as_integer_ratio() [55] и научился возвращать дробь как пару (числитель, знаменатель), тем самым исправив вековой позор обычного float (3.8+):

(0.25).as_integer_ratio()
# (1, 4)

(0.5).as_integer_ratio()
# (1, 2)

(0.2).as_integer_ratio()
# (3602879701896397, 18014398509481984)
# oopsie

from fractions import Fraction

Fraction("0.2").as_integer_ratio()
# (1, 5)
# so much better

Справедливости ради, decimal.Decimal научился так делать еще в 3.6. Но все равно приятно.

песочница [56]

Разработчики: Lisa Roach [57]Raymond Hettinger [33]

functools

Модуль functools [58] — сборник вспомогательных функций высшего порядка. Одна из них — lru_cache() [59], которая кеширует дорогие вычисления:

import functools
import time

@functools.lru_cache(maxsize=256)
def find_user(name):
    # imitating slow search
    time.sleep(1)
    user = {"id": 11, "name": "Diane"}
    return user

find_user("Diane")
# kinda slow

find_user("Diane")
# blazingly fast

Раньше у нее всегда нужно было указывать размер кеша. А теперь можно указать @lru_cache без аргументов, и будет использоваться умолчательный размер 128 (3.8+).

Кроме того, можно узнать параметры кеша (3.9+):

find_user.cache_parameters()
# {'maxsize': 256, 'typed': False}

Если памяти вам не жалко, вместо @lru_cache можно использовать @cache [60] — он безразмерный (3.9+).

Новый декоратор @cached_property [61] кеширует вычисляемое свойство объекта (3.8+):

import functools
import statistics

class Dataset:
    def __init__(self, seq):
        self._data = tuple(seq)

    @functools.cached_property
    def stdev(self):
        return statistics.stdev(self._data)

dataset = Dataset(range(1_000_000))

dataset.stdev
# kinda slow

dataset.stdev
# blazingly fast

А @singledispatchmethod [62] перегружает работу метода в зависимости от типа параметра (3.8+):

import functools

class Divider:
    @functools.singledispatchmethod
    def divide(self, dividend, divisor):
        raise NotImplementedError("Do not know how to divide those")

    @divide.register
    def _(self, dividend: int, divisor: int):
        return dividend // divisor

    @divide.register
    def _(self, dividend: str, divisor: int):
        # this is really stupid, I know
        newlen = len(dividend) // divisor
        return dividend[:newlen]

divider = Divider()
divider.divide(10, 2)
# 5

divider.divide("hello world", 2)
# 'hello'

Чувствуете, джавой потянуло?

песочница [63]

Разработчики: Raymond Hettinger [33]Carl Meyer [64]Ethan Smith [65]

glob

Модуль glob [66] находит файлы и каталоги, подходящие под шаблон.

Теперь благодаря параметру root_dir в glob() [67] и iglob() [68] можно указать корневую директорию поиска (3.10+):

import glob
import os

os.getcwd()
# '/'

glob.glob("*", root_dir="/usr")
# ['local', 'share', 'bin', 'lib', 'sbin', 'src']

Пустячок, а приятно.

песочница [69]

Разработчик: Serhiy Storchaka [70]

graphlib

Модуль graphlib [71] работает с графами. И знаете что? Это абсолютно новый модуль! (3.9+)

Пока у него только одна возможность — топологическая сортировка графов (такой порядок вершин, что для любых u → v, вершина u идет перед v):

from graphlib import TopologicalSorter

graph = {"Diane": {"Bob", "Cindy"}, "Cindy": {"Alice"}, "Bob": {"Alice"}}
# Alice → Bob → Diane
#     ↳ Cindy ↗

sorter = TopologicalSorter(graph)
list(sorter.static_order())
# ['Alice', 'Cindy', 'Bob', 'Diane']

песочница [72]

Разработчики: Pablo Galindo [73]Tim Peters [74]Larry Hastings [75]

itertools

Модуль itertools [76] предоставляет разнообразные итераторы для эффективной работы с коллекциями (эффективной с точки зрения использования памяти).

Одна из функций — accumulate() [77] — рассчитывает скользящий агрегат. Теперь у нее появился параметр initial, который задает начальное значение (3.8+):

import itertools

seq = [7, 11, 19, 42]

accumulator = itertools.accumulate(seq)
list(accumulator)
# [7, 18, 37, 79]

accumulator = itertools.accumulate(seq, initial=100)
list(accumulator)
# [100, 107, 118, 137, 179]

А новая замечательная функция pairwise() [78] проходит по коллекции и возвращает пары последовательных элементов (3.10+):

import itertools

seq = [7, 11, 19, 42]
pairer = itertools.pairwise(seq)

list(pairer)
# [(7, 11), (11, 19), (19, 42)]

песочница [79]

Разработчики: Lisa Roach [57]Raymond Hettinger [33]

math

Модуль math [80] включает вагон и маленькую тележку математических функций.

Тут много нового:

  • dist() [81] считает евклидово расстояние между точками (3.8+);
  • perm() [82] и comb() [83] считают перестановки и сочетания (3.8+);
  • lcm() [84] находит наименьшее общее кратное (3.9+);
  • gcd() [85] теперь считает наибольший общий делитель для произвольного количества аргументов (3.9+).

import math

math.dist((1,1), (4, 5))
# 5.0

math.perm(5, 2)
# 20

math.comb(5, 2)
# 10

math.lcm(9, 27, 60)
# 540

math.gcd(9, 27, 60)
# 3

А prod() [86] перемножает элементы последовательности (3.8+):

import math

seq = range(3, 9)
math.prod(seq)
# 20160

песочница [87]

Разработчики: Raymond Hettinger [33]Yash Aggarwal [88]Keller Fuchs [89]Serhiy Storchaka [70]Mark Dickinson [90]Ananthakrishnan [91]Pablo Galindo [73]

random

Модуль random [92] работает со случайными числами.

Новый метод randbytes() [93] генерит случайную байтовую строку (3.9+):

import random

random.randbytes(4)
# b'x8bxd4x8fxc9'

песочница [94]

Разработчик: Victor Stinner [95]

shlex

Модуль shlex [96] бьет строку на токены по правилам командной строки Unix.

А теперь не только бьет, но и обратно объединяет — благодаря функции join() [97] (3.8+):

import shlex

tokens = ["echo", "-n", "Python is awesome"]
shlex.join(tokens)
# "echo -n 'Python is awesome'"

песочница [98]

Разработчик: Bo Bayles [99]

shutil

Модуль shutil [100] работает с файлами и каталогами: копирует, переносит, удаляет.

И копировать каталоги теперь стало немного удобнее — благодаря параметру dirs_exist_ok в функции copytree() [101] (3.8+). С ним функция не сломается, даже если целевой каталог уже существует:

from pathlib import Path
import shutil

tmp = Path("/tmp")

src = tmp.joinpath("src")
src.mkdir()
src.joinpath("src.txt").touch()
# /tmp/src
# /tmp/src/src.txt

dst = tmp.joinpath("dst")
dst.mkdir()
# /tmp/dst

shutil.copytree(src, dst)
# FileExistsError: [Errno 17] File exists: '/tmp/dst'
shutil.copytree(src, dst, dirs_exist_ok=True)
# PosixPath('/tmp/dst')

песочница [102]

Разработчик: Josh Bronson [103]

statistics

Модуль statistics [104] работает с математической статистикой. Как и math, он заметно развился в последних версиях. Это еще не scipy, но уже и не тот детский сад, что был в 3.4.

Судите сами:

  • fmean() [105] считает среднее арифметическое как mean(), только быстрее (3.8+);
  • geometric_mean() [106] считает геометрическое среднее (3.8+);
  • multimode() [107] возвращает моды (самые частые значения в датасете), даже если их несколько (в отличие от mode()) (3.8+);
  • quantiles() [108] разбивает датасет на квантили (3.8+).

import statistics

seq = list(range(1, 10))

statistics.fmean(seq)
# 5.0

statistics.geometric_mean(seq)
# 4.147166274396913

statistics.multimode(seq)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
statistics.multimode("python is awesome")
# ['o', ' ', 's', 'e']

statistics.quantiles(seq)
# [2.5, 5.0, 7.5]

NormalDist [109] описывает нормальное распределение случайной величины (3.8+):

from statistics import NormalDist

birth_weights = NormalDist.from_samples([2.5, 3.1, 2.1, 2.4, 2.7, 3.5])
drug_effects = NormalDist(0.4, 0.15)
combined = birth_weights + drug_effects

round(combined.mean, 1)
# 3.1

round(combined.stdev, 1)
# 0.5

Появились корреляция Пирсона correlation() [110] и ковариация covariance() [111] (3.10+):

import statistics

x = [1, 2, 3, 4, 5, 6, 7, 8, 9]
y = [9, 8, 7, 6, 5, 4, 3, 2, 1]

statistics.correlation(x, x)
# 1.0

statistics.correlation(x, y)
# -1.0

statistics.covariance(x, x)
# 7.5

statistics.covariance(x, y)
# -7.5

И даже линейная регрессия linear_regression() [112] (3.10+):

import statistics

movies_by_year = {
    2000: 371,
    2003: 507,
    2006: 608,
    2009: 520,
    2012: 669,
    2015: 708,
    2018: 873,
    2021: 403,
}

x = movies_by_year.keys()
y = movies_by_year.values()
slope, intercept = statistics.linear_regression(x, y)

year_2022 = round(slope * 2022 + intercept)
# 697

Кстати, модуль statistics славится еще и шикарной документацией. Рекомендую.

песочница [113]

Разработчики: Raymond Hettinger [33]Steven D’Aprano [114]Timothy Wolodzko [115]

zoneinfo

Модуль zoneinfo [116] предоставляет информацию о часовых поясах по всему миру. Еще один новый модуль! (3.9+)

До появления zoneinfo питон щеголял единственным часовым поясом timezone.utc, удивляя разработчиков из других языков. Теперь это исправили:

import datetime as dt
from zoneinfo import ZoneInfo

utc = dt.datetime(2022, 9, 13, hour=21, tzinfo=dt.timezone.utc)
# 2022-09-13 21:00:00+00:00

paris = utc.astimezone(ZoneInfo("Europe/Paris"))
# 2022-09-13 23:00:00+02:00

tokyo = utc.astimezone(ZoneInfo("Asia/Tokyo"))
# 2022-09-14 06:00:00+09:00

sydney = utc.astimezone(ZoneInfo("Australia/Sydney"))
# 2022-09-14 07:00:00+10:00

песочница [117]

Разработчик: Paul Ganssle [52]

Итого

Мы рассмотрели аж 17 модулей от 27 разработчиков — и это без учета asyncio, typing и великого множества прочих, более низкоуровневых. Как видите, стандартная библиотека активно развивается. И фичи, на мой взгляд, добавляют весьма разумно. Буду рад, если что-то из новшеств пригодится вам в работе!

А если хотите узнать больше о стандартной библиотеке Python — подписывайтесь на мой канал @ohmypy [118]

Автор: Антон Жиянов

Источник [119]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/375016

Ссылки в тексте:

[1] array: #array

[2] base64: #base64

[3] bisect: #bisect

[4] builtins: #builtins

[5] dataclasses: #dataclasses

[6] datetime: #datetime

[7] fractions: #fractions

[8] functools: #functools

[9] glob: #glob

[10] graphlib: #graphlib

[11] itertools: #itertools

[12] math: #math

[13] random: #random

[14] shlex: #shlex

[15] shutil: #shutil

[16] statistics: #statistics

[17] zoneinfo: #zoneinfo

[18] array: https://docs.python.org/3/library/array

[19] array.index(): https://docs.python.org/3/library/array#array.array.index

[20] песочница: https://stepik.org/lesson/716879/step/2

[21] Anders Lorentsen: https://github.com/Phaqui

[22] Zackery Spytz: https://github.com/ZackerySpytz

[23] base64: https://docs.python.org/3/library/base64

[24] b32hexencode(): https://docs.python.org/3/library/base64#base64.b32hexencode

[25] b32hexdecode(): https://docs.python.org/3/library/base64#base64.b32hexdecode

[26] RFC 4648: https://datatracker.ietf.org/doc/html/rfc4648.html#section-7

[27] песочница: https://stepik.org/lesson/716879/step/3

[28] Filipe Laíns: https://github.com/FFY00

[29] bisect: https://docs.python.org/3/library/bisect

[30] bisect(): https://docs.python.org/3/library/bisect#bisect.bisect

[31] insort(): https://docs.python.org/3/library/bisect#bisect.insort

[32] песочница: https://stepik.org/lesson/716879/step/4

[33] Raymond Hettinger: https://github.com/rhettinger

[34] builtins: https://docs.python.org/3/library/builtins

[35] str.removeprefix(): https://docs.python.org/3/library/stdtypes#str.removeprefix

[36] str.removesuffix(): https://docs.python.org/3/library/stdtypes#str.removesuffix

[37] int.bit_count(): https://docs.python.org/3/library/stdtypes#int.bit_count

[38] zip(): https://docs.python.org/3/library/functions#zip

[39] песочница: https://stepik.org/lesson/716879/step/5

[40] Dennis Sweeney: https://github.com/sweeneyde

[41] Niklas Fiekas: https://github.com/niklasf

[42] Brandt Bucher: https://github.com/brandtbucher

[43] dataclasses: https://docs.python.org/3/library/dataclasses

[44] слоты: https://docs.python.org/3/reference/datamodel.html#slots

[45] песочница: https://stepik.org/lesson/716879/step/6

[46] Yurii Karabas: https://github.com/uriyyo

[47] Eric V. Smith: https://github.com/ericvsmith

[48] datetime: https://docs.python.org/3/library/datetime

[49] date.fromisocalendar(): https://docs.python.org/3/library/datetime#datetime.date.fromisocalendar

[50] datetime.fromisocalendar(): https://docs.python.org/3/library/datetime#datetime.datetime.fromisocalendar

[51] песочница: https://stepik.org/lesson/716879/step/7

[52] Paul Ganssle: https://github.com/pganssle

[53] Dong-hee Na: https://github.com/corona10

[54] fractions: https://docs.python.org/3/library/fractions

[55] Fraction.as_integer_ratio(): https://docs.python.org/3/library/fractions#fractions.Fraction.as_integer_ratio

[56] песочница: https://stepik.org/lesson/716879/step/8

[57] Lisa Roach: https://github.com/lisroach

[58] functools: https://docs.python.org/3/library/functools

[59] lru_cache(): https://docs.python.org/3/library/functools#functools.lru_cache

[60] @cache: https://docs.python.org/3/library/functools#functools.cache

[61] @cached_property: https://docs.python.org/3/library/functools#functools.cached_property

[62] @singledispatchmethod: https://docs.python.org/3/library/functools#functools.singledispatchmethod

[63] песочница: https://stepik.org/lesson/716879/step/9

[64] Carl Meyer: https://github.com/carljm

[65] Ethan Smith: https://github.com/ethanhs

[66] glob: https://docs.python.org/3/library/glob

[67] glob(): https://docs.python.org/3/library/glob#glob.glob

[68] iglob(): https://docs.python.org/3/library/glob#glob.iglob

[69] песочница: https://stepik.org/lesson/716879/step/10

[70] Serhiy Storchaka: https://github.com/serhiy-storchaka

[71] graphlib: https://docs.python.org/3/library/graphlib

[72] песочница: https://stepik.org/lesson/716898/step/2

[73] Pablo Galindo: https://github.com/pablogsal

[74] Tim Peters: https://github.com/tim-one

[75] Larry Hastings: https://github.com/larryhastings

[76] itertools: https://docs.python.org/3/library/itertools

[77] accumulate(): https://docs.python.org/3/library/itertools#itertools.accumulate

[78] pairwise(): https://docs.python.org/3/library/itertools#itertools.pairwise

[79] песочница: https://stepik.org/lesson/716898/step/3

[80] math: https://docs.python.org/3/library/math

[81] dist(): https://docs.python.org/3/library/math#math.dist

[82] perm(): https://docs.python.org/3/library/math#math.perm

[83] comb(): https://docs.python.org/3/library/math#math.comb

[84] lcm(): https://docs.python.org/3/library/math#math.lcm

[85] gcd(): https://docs.python.org/3/library/math#math.gcd

[86] prod(): https://docs.python.org/3/library/math#math.prod

[87] песочница: https://stepik.org/lesson/716898/step/4

[88] Yash Aggarwal: https://github.com/FR4NKESTI3N

[89] Keller Fuchs: https://github.com/KellerFuchs

[90] Mark Dickinson: https://github.com/mdickinson

[91] Ananthakrishnan: https://github.com/ananthan-123

[92] random: https://docs.python.org/3/library/random

[93] randbytes(): https://docs.python.org/3/library/random#random.randbytes

[94] песочница: https://stepik.org/lesson/716898/step/5

[95] Victor Stinner: https://github.com/vstinner

[96] shlex: https://docs.python.org/3/library/shlex

[97] join(): https://docs.python.org/3/library/shlex#shlex.join

[98] песочница: https://stepik.org/lesson/716898/step/6

[99] Bo Bayles: https://github.com/bbayles

[100] shutil: https://docs.python.org/3/library/shutil

[101] copytree(): https://docs.python.org/3/library/shutil#shutil.copytree

[102] песочница: https://stepik.org/lesson/716898/step/7

[103] Josh Bronson: https://github.com/jab

[104] statistics: https://docs.python.org/3/library/statistics

[105] fmean(): https://docs.python.org/3/library/statistics#statistics.fmean

[106] geometric_mean(): https://docs.python.org/3/library/statistics#statistics.geometric_mean

[107] multimode(): https://docs.python.org/3/library/statistics#statistics.multimode

[108] quantiles(): https://docs.python.org/3/library/statistics#statistics.quantiles

[109] NormalDist: https://docs.python.org/3/library/statistics#statistics.NormalDist

[110] correlation(): https://docs.python.org/3/library/statistics#statistics.correlation

[111] covariance(): https://docs.python.org/3/library/statistics#statistics.covariance

[112] linear_regression(): https://docs.python.org/3/library/statistics#statistics.linear_regression

[113] песочница: https://stepik.org/lesson/716898/step/8

[114] Steven D’Aprano: https://github.com/stevendaprano

[115] Timothy Wolodzko: https://github.com/twolodzko

[116] zoneinfo: https://docs.python.org/3/library/zoneinfo

[117] песочница: https://stepik.org/lesson/716898/step/9

[118] @ohmypy: https://t.me/ohmypy

[119] Источник: https://habr.com/ru/post/665020/?utm_source=habrahabr&utm_medium=rss&utm_campaign=665020