FAQ по библиотеке и расширению

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

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

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

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

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

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

В 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..."""

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

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

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

Есть ли в Python эквивалент onexit() из языка C?

Модуль atexit предоставляет функцию регистра, аналогичную 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.

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

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

Как получить одно нажатие клавиши за раз?

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

Нитки

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

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

Ни один из моих потоков не запускается: почему?

Как только главный поток выходит, все потоки погибают. Ваш главный поток работает слишком быстро, не давая потокам времени на выполнение работы.

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

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 предоставляет многофункциональный интерфейс.

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

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

В теории это означает, что точный учет требует точного понимания реализации байткода PVM. На практике это означает, что операции над общими переменными встроенных типов данных (ints, lists, dicts и т. д.), которые «выглядят атомарными», на самом деле таковыми являются.

Например, все следующие операции являются атомарными (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 основана на fork of Python 3.9 with the GIL removed Сэма Гросса. До этого, во времена Python 1.5, Грег Стайн реализовал комплексный набор патчей («free threading» patches), который удалил GIL и заменил его мелкозернистой блокировкой. Адам Олсен провел аналогичный эксперимент в своем проекте python-safethread. К сожалению, оба предыдущих эксперимента показали резкое падение однопоточной производительности (по крайней мере, на 30 % медленнее) из-за количества мелкозернистых блокировок, необходимых для компенсации удаления GIL. Форк Python 3.9 - это первая попытка удалить GIL с приемлемым влиянием на производительность.

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

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

Альтернативный подход к снижению влияния GIL заключается в том, чтобы сделать GIL не глобальной, а блокирующей состояние интерпретатора. Это было сделано first implemented in 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.ftruncate(fd, offset) для файлов, открытых с помощью os.open(), где fd - это дескриптор файла (маленькое целое число).

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

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

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

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

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

Например, следующий код считывает из файла два 2-байтовых целых числа и одно 4-байтовое целое число в формате big-endian:

import struct

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

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

Для более регулярных данных (например, однородных списков ints или float) можно также использовать модуль 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 от Митча Чепмена:

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

Python file objects - это высокоуровневый уровень абстракции над низкоуровневыми дескрипторами файлов на языке C.

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

Но stdin, stdout и stderr обрабатываются Python особым образом, поскольку в 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?

Вы можете найти коллекцию полезных ссылок на Web Programming wiki page.

Как отправить почту из сценария 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 интерфейсы для пакетов баз данных?

Да.

Интерфейсы для дисковых хэшей, таких как DBM и GDBM, также включены в стандартный Python. Есть также модуль sqlite3, который предоставляет легкую реляционную базу данных на основе диска.

Поддерживается большинство реляционных баз данных. Подробности см. в разделе DatabaseProgramming wiki page.

Как реализовать постоянные объекты в 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, который можно инстанцировать для создания независимых множественных генераторов случайных чисел.