Spec-Zone.ru › Python 3.12

Часто задаваемые вопросы о библиотеках и расширениях

  • Общие вопросы о библиотеках

    • Как найти модуль или приложение для выполнения задачи X?
    • Где находится исходный файл math.py (socket.py, regex.py и т. д.)?
    • Как сделать скрипт Python исполняемым в Unix?
    • Существует ли для Python пакет curses/termcap?
    • Есть ли в Python аналог C-функции onexit()?
    • Почему мои обработчики сигналов не работают?
  • Типовые задачи

    • Как тестировать программу или компонент Python?
    • Как создать документацию из строк документации?
    • Как получать нажатия клавиш по одному?
  • Потоки

    • Как программировать с использованием потоков?
    • Ни один из моих потоков, похоже, не выполняется. Почему?
    • Как распределить работу между несколькими рабочими потоками?
    • Какие виды изменения глобальных значений потокобезопасны?
    • Нельзя ли избавиться от глобальной блокировки интерпретатора?
  • Ввод и вывод

    • Как удалить файл? (И другие вопросы о файлах…)
    • Как скопировать файл?
    • Как читать (или записывать) двоичные данные?
    • Почему не удаётся использовать os.read() для канала, созданного с помощью os.popen()?
    • Как получить доступ к последовательному порту (RS232)?
    • Почему закрытие sys.stdout (stdin, stderr) на самом деле не закрывает его?
  • Программирование сетевых и интернет-приложений

    • Какие инструменты для WWW существуют в Python?
    • Как имитировать отправку формы CGI (METHOD=POST)?
    • Какой модуль использовать для создания HTML?
    • Как отправить почту из скрипта Python?
    • Как избежать блокировки в методе connect() сокета?
  • Базы данных

    • Есть ли в Python интерфейсы к пакетам для работы с базами данных?
    • Как реализовать постоянные объекты в Python?
  • Математика и численные методы

    • Как генерировать случайные числа в Python?

Общие вопросы о библиотеках

Как найти модуль или приложение для выполнения задачи X?

Проверьте справочник по библиотеке, чтобы узнать, есть ли подходящий модуль в стандартной библиотеке. (Со временем вы узнаете, что входит в стандартную библиотеку, и сможете пропускать этот шаг.)

Чтобы найти сторонние пакеты, выполните поиск в индексе пакетов Python или попробуйте поискать в Google либо другой поисковой системе. Поиск по слову «Python» и одному-двум ключевым словам, связанным с интересующей вас темой, обычно помогает найти нужное.

Где находится исходный файл math.py (socket.py, regex.py и т. д.)?

Если вы не можете найти исходный файл модуля, возможно, это встроенный или динамически загружаемый модуль, реализованный на C, C++ или другом компилируемом языке. В этом случае исходного файла может не быть или он может называться, например, mathmodule.c и находиться где-нибудь в каталоге с исходным кодом на C (не в пути Python).

В Python есть (по крайней мере) три вида модулей:

  1. модули, написанные на Python (.py);
  2. модули, написанные на C и динамически загружаемые (.dll, .pyd, .so, .sl и т. д.);
  3. модули, написанные на C и связанные с интерпретатором; чтобы получить их список, введите:

    import sys
    print(sys.builtin_module_names)
    

Как сделать скрипт Python исполняемым в Unix?

Нужно сделать две вещи: файл скрипта должен иметь режим исполнения, а первая строка должна начинаться с #!, за которым следует путь к интерпретатору Python.

Первое можно сделать, выполнив chmod +x scriptfile или, возможно, chmod 755 scriptfile.

Второе можно сделать несколькими способами. Проще всего написать

#!/usr/local/bin/python

в самой первой строке файла, указав путь к месту установки интерпретатора Python на вашей платформе.

Если вы хотите, чтобы скрипт не зависел от расположения интерпретатора Python, можно использовать программу env. Практически все варианты Unix поддерживают следующий способ, если интерпретатор Python находится в каталоге, входящем в переменную PATH пользователя:

#!/usr/bin/env python

Не используйте этот способ для скриптов CGI. Переменная PATH для скриптов CGI часто имеет очень ограниченное значение, поэтому необходимо указать настоящий абсолютный путь к интерпретатору.

Иногда среда пользователя настолько перегружена, что программа /usr/bin/env завершается с ошибкой; кроме того, программы env может вообще не быть. В таком случае можно попробовать следующий обходной приём (предложенный Алексом Резинским):

#! /bin/sh
""":"
exec python $0 ${1+"$@"}
"""

Небольшой недостаток этого способа в том, что он задаёт строку __doc__ скрипта. Однако это можно исправить, добавив

__doc__ = """...Whatever..."""

Существует ли для Python пакет curses/termcap?

Для вариантов Unix: стандартный дистрибутив исходного кода Python содержит модуль curses в подкаталоге Modules, хотя по умолчанию он не компилируется. (Обратите внимание: этот модуль недоступен в дистрибутиве для Windows — для Windows модуля curses нет.)

Модуль curses поддерживает основные возможности curses, а также множество дополнительных функций из ncurses и SYSV curses, таких как цвет, поддержка альтернативных наборов символов, окон-буферов и мыши. Это означает, что модуль несовместим с операционными системами, в которых есть только BSD curses, однако, похоже, сейчас нет поддерживаемых ОС, относящихся к этой категории.

Есть ли в Python аналог C-функции onexit()?

Модуль atexit предоставляет функцию register, аналогичную функции onexit() в C.

Почему мои обработчики сигналов не работают?

Чаще всего проблема заключается в том, что обработчик сигнала объявлен с неправильным списком аргументов. Он вызывается так:

handler(signum, frame)

поэтому его нужно объявить с двумя параметрами:

def handler(signum, frame):
    ...

Типовые задачи

Как тестировать программу или компонент Python?

В Python есть две платформы для тестирования. Модуль doctest находит примеры в строках документации модуля и запускает их, сравнивая вывод с ожидаемым выводом, указанным в строке документации.

Модуль unittest — более сложная платформа тестирования, созданная по образцу платформ для тестирования Java и Smalltalk.

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

«Основная логика» программы может быть такой простой:

if __name__ == "__main__":
    main_logic()

в конце главного модуля программы.

Когда программа будет организована в виде удобного для работы набора функций и поведения классов, напишите тестовые функции, проверяющие это поведение. С каждым модулем можно связать набор тестов, автоматизирующий последовательность проверок. Это может показаться большой работой, но Python настолько лаконичен и гибок, что на деле всё оказывается удивительно просто. Разработка станет гораздо приятнее и интереснее, если писать тестовые функции параллельно с «рабочим кодом»: так будет проще раньше обнаружить ошибки и даже недостатки проектирования.

«Вспомогательные модули», которые не предназначены для использования в качестве главного модуля программы, могут включать самопроверку модуля.

if __name__ == "__main__":
    self_test()

Даже программы, взаимодействующие со сложными внешними интерфейсами, можно тестировать при отсутствии этих интерфейсов, используя «фиктивные» интерфейсы, реализованные на Python.

Как создать документацию из строк документации?

Модуль pydoc может создавать HTML из строк документации в исходном коде Python. Для создания документации API только на основе строк документации можно также использовать epydoc. Sphinx тоже умеет включать содержимое строк документации.

Как получать нажатия клавиш по одному?

Для вариантов Unix есть несколько решений. Это несложно сделать с помощью curses, но для освоения curses потребуется изучить довольно большой модуль.

Потоки

Как программировать с использованием потоков?

Обязательно используйте модуль threading, а не модуль _thread. Модуль threading предоставляет удобные абстракции поверх низкоуровневых примитивов модуля _thread.

Ни один из моих потоков, похоже, не выполняется. Почему?

Как только главный поток завершается, все потоки уничтожаются. Главный поток выполняется слишком быстро и не даёт другим потокам времени на работу.

Простое решение — добавить в конец программы паузу, достаточную для завершения всех потоков:

import threading, time

def thread_task(name, n):
    for i in range(n):
        print(name, i)

for i in range(10):
    T = threading.Thread(target=thread_task, args=(str(i), i))
    T.start()

time.sleep(10)  # <---------------------------!

Однако теперь (на многих платформах) потоки не выполняются параллельно, а, кажется, работают последовательно, по одному за раз! Причина в том, что планировщик потоков ОС не запускает новый поток, пока предыдущий не заблокирован.

Простое решение — добавить небольшую паузу в начало функции run:

def thread_task(name, n):
    time.sleep(0.001)  # <--------------------!
    for i in range(n):
        print(name, i)

for i in range(10):
    T = threading.Thread(target=thread_task, args=(str(i), i))
    T.start()

time.sleep(10)

Вместо того чтобы пытаться угадать подходящую длительность задержки для time.sleep(), лучше использовать какой-либо механизм семафоров. Например, можно воспользоваться модулем queue, создать объект очереди, позволить каждому потоку добавлять в очередь маркер по завершении и попросить главный поток считывать из очереди столько маркеров, сколько имеется потоков.

Как распределить работу между несколькими рабочими потоками?

Проще всего использовать модуль concurrent.futures, в частности класс ThreadPoolExecutor.

Если вам нужен точный контроль над алгоритмом распределения задач, можно вручную написать собственную логику. Используйте модуль queue, чтобы создать очередь со списком заданий. Класс Queue хранит список объектов и предоставляет метод .put(obj) для добавления элементов в очередь и метод .get() для их получения. Класс позаботится о блокировках, необходимых для того, чтобы каждое задание было выдано ровно один раз.

Вот простой пример:

import threading, queue, time

# The worker thread gets jobs off the queue.  When the queue is empty, it
# assumes there will be no more work and exits.
# (Realistically workers will run until terminated.)
def worker():
    print('Running worker')
    time.sleep(0.1)
    while True:
        try:
            arg = q.get(block=False)
        except queue.Empty:
            print('Worker', threading.current_thread(), end=' ')
            print('queue empty')
            break
        else:
            print('Worker', threading.current_thread(), end=' ')
            print('running with argument', arg)
            time.sleep(0.5)

# Create queue
q = queue.Queue()

# Start a pool of 5 workers
for i in range(5):
    t = threading.Thread(target=worker, name='worker %i' % (i+1))
    t.start()

# Begin adding work to the queue
for i in range(50):
    q.put(i)

# Give threads time to run
print('Main thread sleeping')
time.sleep(5)

При запуске будет выведено следующее:

Running worker
Running worker
Running worker
Running worker
Running worker
Main thread sleeping
Worker <Thread(worker 1, started 130283832797456)> running with argument 0
Worker <Thread(worker 2, started 130283824404752)> running with argument 1
Worker <Thread(worker 3, started 130283816012048)> running with argument 2
Worker <Thread(worker 4, started 130283807619344)> running with argument 3
Worker <Thread(worker 5, started 130283799226640)> running with argument 4
Worker <Thread(worker 1, started 130283832797456)> running with argument 5
...

Дополнительные сведения см. в документации модуля; класс Queue предоставляет широкие возможности.

Какие виды изменения глобальных значений потокобезопасны?

Для внутреннего обеспечения выполнения только одного потока за раз в виртуальной машине Python используется глобальная блокировка интерпретатора (GIL). В общем случае Python переключается между потоками только между байткод-инструкциями; частоту переключения можно задать с помощью sys.setswitchinterval(). Таким образом, каждая байткод-инструкция и весь код на C, выполняемый при её обработке, атомарны с точки зрения программы Python.

Теоретически это означает, что для точного учёта необходимо точно понимать реализацию байткода PVM. На практике это означает, что операции с общими переменными встроенных типов данных (int, list, dict и т. д.), которые «выглядят атомарными», действительно являются атомарными.

Например, все следующие операции атомарны (L, L1, L2 — списки, D, D1, D2 — словари, x, y — объекты, i, j — целые числа):

L.append(x)
L1.extend(L2)
x = L[i]
x = L.pop()
L1[i:j] = L2
L.sort()
x = y
x.field = y
D[x] = y
D1.update(D2)
D.keys()

А следующие — нет:

i = i+1
L.append(L[-1])
L[i] = L[j]
D[x] = D[x] + 1

При операциях, заменяющих другие объекты, может вызываться метод __del__() этих объектов, когда счётчик ссылок достигает нуля; это может повлиять на результат. Особенно это касается массового обновления словарей и списков. Если сомневаетесь, используйте мьютекс!

Нельзя ли избавиться от глобальной блокировки интерпретатора?

Глобальная блокировка интерпретатора (GIL) часто считается препятствием для использования Python на мощных многопроцессорных серверных машинах, поскольку многопоточная программа Python фактически использует только один процессор: практически весь код Python может выполняться только при удержании GIL.

Ещё во времена Python 1.5 Грег Стайн разработал масштабный набор исправлений («исправления для свободной многопоточности»), который удалял GIL и заменял его более детализированными блокировками. Недавно Адам Олсен провёл похожий эксперимент в проекте python-safethread. К сожалению, оба эксперимента показали резкое снижение производительности однопоточных программ (как минимум на 30%) из-за количества мелкозернистых блокировок, необходимых для компенсации удаления GIL.

Это не значит, что Python нельзя эффективно использовать на машинах с несколькими процессорами! Просто нужно творчески подойти к распределению работы между несколькими процессами, а не потоками. Класс ProcessPoolExecutor из нового модуля concurrent.futures позволяет легко это сделать; модуль multiprocessing предоставляет низкоуровневый API, если вам нужен больший контроль над распределением задач.

Поможет и разумное использование расширений на C: если с помощью расширения на C выполняется длительная задача, расширение может освободить GIL, пока поток исполнения находится в коде на C, и позволить другим потокам поработать. Некоторые модули стандартной библиотеки, например zlib и hashlib, уже делают это.

Предлагалось сделать GIL блокировкой для состояния отдельного интерпретатора, а не глобальной блокировкой; тогда интерпретаторы не смогли бы совместно использовать объекты. К сожалению, это тоже вряд ли произойдёт. Для этого потребовался бы огромный объём работы, поскольку многие реализации объектов сейчас используют глобальное состояние. Например, небольшие целые числа и короткие строки кэшируются; эти кэши пришлось бы перенести в состояние интерпретатора. У других типов объектов есть собственные списки свободных объектов; их тоже пришлось бы перенести в состояние интерпретатора. И так далее.

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

И наконец, если у вас есть несколько интерпретаторов, не использующих общее состояние, что вы выигрываете по сравнению с запуском каждого интерпретатора в отдельном процессе?

Ввод и вывод

Как удалить файл? (И другие вопросы о файлах…)

Используйте os.remove(filename) или os.unlink(filename); документацию см. в модуле os. Эти две функции идентичны; unlink() — это просто название системного вызова Unix для этой функции.

Чтобы удалить каталог, используйте os.rmdir(); для его создания используйте os.mkdir(). os.makedirs(path) создаст все отсутствующие промежуточные каталоги в path. os.removedirs(path) удалит промежуточные каталоги, если они пусты; чтобы удалить всё дерево каталогов вместе с содержимым, используйте shutil.rmtree().

Чтобы переименовать файл, используйте os.rename(old_path, new_path).

Чтобы усечь файл, откройте его с помощью f = open(filename, "rb+") и используйте f.truncate(offset); по умолчанию смещение соответствует текущей позиции. Для файлов, открытых с помощью os.open(), также есть os.ftruncate(fd, offset), где fd — файловый дескриптор (небольшое целое число).

Модуль shutil также содержит ряд функций для работы с файлами, включая copyfile(), copytree() и rmtree().

Как скопировать файл?

Модуль shutil содержит функцию copyfile(). Обратите внимание: на томах NTFS в Windows она не копирует альтернативные потоки данных, а на томах HFS+ в macOS — ресурсные ветви, хотя и те и другие сейчас используются редко. Также она не копирует разрешения и метаданные файлов, однако функция shutil.copy2() сохраняет большую часть (но не все) этих данных.

Как читать (или записывать) двоичные данные?

Для чтения и записи сложных двоичных форматов данных лучше всего использовать модуль struct. Он позволяет преобразовывать строку с двоичными данными (обычно числами) в объекты Python и наоборот.

Например, следующий код считывает из файла два двухбайтовых целых числа и одно четырёхбайтовое целое число в порядке байтов от старшего к младшему:

import struct

with open(filename, "rb") as f:
    s = f.read(8)
    x, y, z = struct.unpack(">hhl", s)

Символ «>» в строке формата задаёт порядок байтов от старшего к младшему; буква «h» считывает из строки одно «короткое целое число» (2 байта), а «l» — одно «длинное целое число» (4 байта).

Для более регулярных данных (например, однородного списка целых чисел или чисел с плавающей точкой) можно также использовать модуль array.

Примечание

Для чтения и записи двоичных данных файл необходимо открыть в двоичном режиме (здесь в open() передаётся "rb"). Если вместо этого использовать "r" (значение по умолчанию), файл будет открыт в текстовом режиме, и f.read() будет возвращать объекты str, а не объекты bytes.

Почему не удаётся использовать os.read() для канала, созданного с помощью os.popen()?

os.read() — это низкоуровневая функция, которая принимает файловый дескриптор — небольшое целое число, представляющее открытый файл. os.popen() создаёт высокоуровневый файловый объект того же типа, что и возвращаемый встроенной функцией open(). Поэтому для чтения n байтов из канала p, созданного с помощью os.popen(), необходимо использовать p.read(n).

Как получить доступ к последовательному порту (RS232)?

Для Win32, OSX, Linux, BSD, Jython и IronPython:

pyserial

Для Unix см. сообщение Митча Чапмена в Usenet:

https://groups.google.com/groups?selm=34A04430.CF9@ohioee.com

Почему закрытие sys.stdout (stdin, stderr) на самом деле не закрывает его?

Файловые объекты Python — это высокоуровневая абстракция над низкоуровневыми файловыми дескрипторами C.

Для большинства файловых объектов, создаваемых в Python с помощью встроенной функции open(), f.close() помечает файловый объект Python как закрытый с точки зрения Python и также закрывает соответствующий файловый дескриптор C. То же самое происходит автоматически в деструкторе f, когда f становится недостижимым объектом.

Однако Python обрабатывает stdin, stdout и stderr особым образом из-за особого статуса, присвоенного им также в C. Выполнение sys.stdout.close() помечает файловый объект на уровне Python как закрытый, но не закрывает связанный файловый дескриптор C.

Чтобы закрыть лежащий в основе файловый дескриптор C для одного из этих трёх потоков, сначала убедитесь, что действительно хотите это сделать (например, это может помешать модулям расширения выполнять ввод-вывод). Если это так, используйте os.close():

os.close(stdin.fileno())
os.close(stdout.fileno())
os.close(stderr.fileno())

Или можно использовать соответствующие числовые константы 0, 1 и 2.

Сетевое программирование и программирование для Интернета

Какие инструменты для WWW есть в Python?

См. главы Протоколы Интернета и поддержка и Обработка данных Интернета в справочнике по библиотеке. В Python есть множество модулей, которые помогут создать веб-системы на стороне сервера и клиента.

Сводку доступных фреймворков ведёт Пол Бодди на странице https://wiki.python.org/moin/WebProgramming.

Кэмерон Лэрд ведёт полезную подборку страниц о веб-технологиях Python по адресу https://web.archive.org/web/20210224183619/http://phaseit.net/claird/comp.lang.python/web_python.

Как имитировать отправку формы CGI (METHOD=POST)?

Я хочу получить веб-страницы, которые являются результатом отправки формы методом POST. Есть ли готовый код, позволяющий легко это сделать?

Да. Вот простой пример с использованием urllib.request:

#!/usr/local/bin/python

import urllib.request

# build the query string
qs = "First=Josephine&MI=Q&Last=Public"

# connect and send the server a path
req = urllib.request.urlopen('http://www.some-server.out-there'
                             '/cgi-bin/some-cgi-script', data=qs)
with req:
    msg, hdrs = req.read(), req.info()

Обратите внимание, что при POST-запросах с кодированием в процентах строки запроса обычно нужно заключать в кавычки с помощью urllib.parse.urlencode(). Например, чтобы отправить name=Guy Steele, Jr.:

>>> import urllib.parse
>>> urllib.parse.urlencode({'name': 'Guy Steele, Jr.'})
'name=Guy+Steele%2C+Jr.'

См. также

В Практическое руководство по получению интернет-ресурсов с помощью пакета urllib приведено множество примеров.

Какой модуль использовать для генерации HTML?

Подборку полезных ссылок можно найти на вики-странице о веб-программировании.

Как отправить почту из скрипта Python?

Используйте модуль стандартной библиотеки smtplib.

Вот очень простой интерактивный отправитель почты, который его использует. Этот способ работает на любом узле, поддерживающем прослушивание SMTP-порта.

import sys, smtplib

fromaddr = input("From: ")
toaddrs  = input("To: ").split(',')
print("Enter message, end with ^D:")
msg = ''
while True:
    line = sys.stdin.readline()
    if not line:
        break
    msg += line

# The actual mail send
server = smtplib.SMTP('localhost')
server.sendmail(fromaddr, toaddrs, msg)
server.quit()

В качестве альтернативы, доступной только в Unix, можно использовать sendmail. Расположение программы sendmail зависит от системы: иногда это /usr/lib/sendmail, иногда — /usr/sbin/sendmail. Дополнительную информацию можно найти на странице руководства sendmail. Вот пример кода:

import os

SENDMAIL = "/usr/sbin/sendmail"  # sendmail location
p = os.popen("%s -t -i" % SENDMAIL, "w")
p.write("To: receiver@example.com\n")
p.write("Subject: test\n")
p.write("\n")  # blank line separating headers from body
p.write("Some text\n")
p.write("some more text\n")
sts = p.close()
if sts != 0:
    print("Sendmail exit status", sts)

Как избежать блокировки в методе connect() сокета?

Для асинхронного ввода-вывода с сокетами обычно используют модуль select.

Чтобы TCP-подключение не блокировало выполнение, сокет можно перевести в неблокирующий режим. Тогда при вызове connect() подключение либо произойдёт сразу (что маловероятно), либо возникнет исключение, содержащее номер ошибки в виде .errno. errno.EINPROGRESS означает, что подключение выполняется, но ещё не завершено. Разные ОС возвращают разные значения, поэтому нужно проверить, какое значение возвращается в вашей системе.

Чтобы не создавать исключение, можно использовать метод connect_ex(). Он просто возвращает значение errno. Для проверки можно позднее снова вызвать connect_ex(): значения 0 или errno.EISCONN означают, что подключение установлено. Или можно передать этот сокет в select.select(), чтобы проверить, доступен ли он для записи.

Примечание

Модуль asyncio — это универсальная однопоточная библиотека для конкурентного асинхронного программирования, которую можно использовать для написания неблокирующего сетевого кода. Популярной и многофункциональной альтернативой является сторонняя библиотека Twisted.

Базы данных

Есть ли в Python интерфейсы к пакетам баз данных?

Да.

В стандартную поставку Python также входят интерфейсы к дисковым хеш-таблицам, таким как DBM и GDBM. Кроме того, модуль sqlite3 предоставляет лёгкую реляционную базу данных, хранящую данные на диске.

Доступна поддержка большинства реляционных баз данных. Подробности см. на вики-странице о программировании баз данных.

Как реализовать постоянное хранение объектов в Python?

Модуль стандартной библиотеки pickle решает эту задачу универсальным способом (хотя хранить такие объекты, как открытые файлы, сокеты или окна, всё равно нельзя), а модуль стандартной библиотеки shelve использует pickle и (g)dbm для создания постоянных отображений, содержащих произвольные объекты Python.

Математика и численные методы

Как генерировать случайные числа в Python?

Стандартный модуль random реализует генератор случайных чисел. Использовать его просто:

import random
random.random()

Эта функция возвращает случайное число с плавающей точкой в диапазоне [0, 1).

В этом модуле есть и другие специализированные генераторы, например:

  • randrange(a, b) выбирает целое число в диапазоне [a, b).
  • uniform(a, b) выбирает число с плавающей точкой в диапазоне [a, b).
  • normalvariate(mean, sdev) генерирует выборку из нормального (гауссова) распределения.

Некоторые функции более высокого уровня непосредственно работают с последовательностями, например:

  • choice(S) выбирает случайный элемент из заданной последовательности.
  • shuffle(L) перемешивает список на месте, то есть случайным образом изменяет порядок его элементов.

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

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/faq/library.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API