Часто задаваемые вопросы о библиотеках и расширениях
-
- Как удалить файл? (И другие вопросы о файлах…)
- Как скопировать файл?
- Как читать (или записывать) двоичные данные?
- Почему не получается использовать os.read() для канала, созданного с помощью os.popen()?
- Как получить доступ к последовательному порту (RS232)?
- Почему закрытие sys.stdout (stdin, stderr) на самом деле его не закрывает?
Общие вопросы о библиотеках
Как найти модуль или приложение для выполнения задачи X?
Проверьте справочник по библиотеке, чтобы узнать, есть ли подходящий модуль в стандартной библиотеке. (Со временем вы узнаете, что входит в стандартную библиотеку, и сможете пропускать этот шаг.)
Чтобы найти сторонние пакеты, выполните поиск в индексе пакетов Python или воспользуйтесь Google либо другой поисковой системой. Обычно достаточно поискать слово «Python» вместе с одним-двумя ключевыми словами по интересующей вас теме, чтобы найти что-нибудь полезное.
Где находится исходный файл math.py (socket.py, regex.py и т. д.)?
Если исходный файл модуля не удаётся найти, возможно, это встроенный или динамически загружаемый модуль, реализованный на C, C++ или другом компилируемом языке. В этом случае исходного файла может не быть, либо он может называться, например, mathmodule.c и находиться где-нибудь в каталоге с исходным кодом на C (не в пути Python).
В Python есть (как минимум) три вида модулей:
- модули, написанные на Python (.py);
- модули, написанные на C и загружаемые динамически (.dll, .pyd, .so, .sl и т. д.);
-
модули, написанные на 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, но этот довольно большой модуль придётся изучить.
Потоки
Как программировать с использованием потоков?
Обязательно используйте модуль 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.
После утверждения PEP 703 началась работа над удалением GIL из реализации Python CPython. Сначала это будет реализовано как необязательный флаг компилятора при сборке интерпретатора, поэтому будут доступны отдельные сборки с GIL и без него. В долгосрочной перспективе предполагается перейти к одной сборке, когда последствия удаления GIL для производительности будут полностью изучены. Вероятно, Python 3.13 станет первым выпуском с этой работой, хотя в нём она может быть реализована не полностью.
Текущая работа по удалению GIL основана на форке Python 3.9 с удалённым GIL, созданном Сэмом Гроссом. До этого, во времена Python 1.5, Грег Стайн разработал комплексный набор исправлений («исправления для свободной многопоточности»), которые удаляли GIL и заменяли его блокировками с мелкой гранулярностью. Адам Олсен провёл похожий эксперимент в проекте python-safethread. К сожалению, оба этих ранних эксперимента привели к резкому падению производительности в однопоточном режиме (как минимум на 30%) из-за необходимости использовать множество блокировок с мелкой гранулярностью для компенсации удаления GIL. Форк Python 3.9 — первая попытка удалить GIL с приемлемым влиянием на производительность.
Наличие GIL в текущих версиях Python не означает, что Python нельзя эффективно использовать на компьютерах с несколькими процессорами! Просто нужно творчески подойти к распределению работы между несколькими процессами, а не несколькими потоками. Класс ProcessPoolExecutor из нового модуля concurrent.futures упрощает эту задачу; модуль multiprocessing предоставляет низкоуровневый API, если вам нужен больший контроль над распределением задач.
Поможет и разумное использование расширений на C: если такое расширение выполняет длительную задачу, оно может освободить GIL на время выполнения кода на C и позволить другим потокам поработать. Некоторые модули стандартной библиотеки, например zlib и hashlib, уже делают это.
Ещё один способ уменьшить влияние GIL — сделать блокировку локальной для состояния интерпретатора, а не по-настоящему глобальной. Это было впервые реализовано в Python 3.12 и доступно в C API. Ожидается, что интерфейс Python для этой возможности появится в Python 3.13. Основным ограничением сейчас, вероятно, станут сторонние модули расширения: для использования они должны быть написаны с учётом нескольких интерпретаторов, поэтому многие старые модули расширения окажутся несовместимы.
Ввод и вывод
Как удалить файл? (И другие вопросы о файлах…)
Используйте 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.
Примечание
Для чтения и записи двоичных данных файл обязательно нужно открывать в двоичном режиме (в данном случае передавая "rb" функции open()). Если вместо этого использовать "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:
Для 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.
Какой модуль использовать для создания 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(), чтобы проверить, доступен ли он для записи.
Базы данных
Есть ли в 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 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/faq/library.html