Поширені запитання про бібліотеку та розширення

Загальні бібліотечні питання

Як знайти модуль або програму для виконання завдання X?

Перевірте довідник бібліотеки, щоб побачити, чи є відповідний модуль стандартної бібліотеки. (Згодом ви дізнаєтеся, що міститься в стандартній бібліотеці, і зможете пропустити цей крок.)

Пакунки сторонніх розробників шукайте в Python Package Index або спробуйте 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. У такому випадку ви можете спробувати наступний хак (завдяки Alex Rezinsky):

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

Незначним недоліком є те, що це визначає рядок __doc__ сценарію. Однак ви можете виправити це, додавши

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

Чи є пакет curses/termcap для Python?

Для варіантів Unix: Стандартний дистрибутив вихідного коду Python постачається з модулем curses у підкаталозі Modules, хоча він не скомпільований за замовчуванням. (Зауважте, що це недоступно в дистрибутиві Windows — для Windows немає модуля curses.)

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

Чи є еквівалент onexit() C у Python?

The atexit module provides a register function that is similar to C’s onexit().

Чому мої обробники сигналів не працюють?

Найбільш поширеною проблемою є те, що обробник сигналу оголошено з неправильним списком аргументів. Називається як

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.

Як створити документацію з рядків документа?

The pydoc module can create HTML from the doc strings in your Python source code. An alternative for creating API documentation purely from docstrings is epydoc. Sphinx can also include docstring content.

Як отримати одне натискання клавіші за раз?

Для варіантів 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)  # <---------------------------!

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

Просте виправлення полягає в тому, щоб додати крихітний сон до початку функції запуску:

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. На практиці це означає, що операції зі спільними змінними вбудованих типів даних (int, списки, dicts тощо), які «виглядають атомарними», дійсно є такими.

Наприклад, усі наступні операції є атомарними (L, L1, L2 — списки, D, D1, D2 — dicts, 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

Operations that replace other objects may invoke those other objects“ __del__() method when their reference count reaches zero, and that can affect things. This is especially true for the mass updates to dictionaries and lists. When in doubt, use a mutex!

Чи не можемо ми позбутися глобального блокування інтерпретатора?

global interpreter lock (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(ім’я файлу) або os.unlink(ім’я файлу); для документації дивіться модуль 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); offset за замовчуванням до поточної позиції пошуку. Існує також os.ftruncate(fd, offset) для файлів, відкритих за допомогою os.open(), де fd є дескриптором файлу (мале ціле число).

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

Як скопіювати файл?

Модуль shutil містить функцію copyfile(). Зауважте, що на томах Windows NTFS він не копіює альтернативні потоки даних і розгалуження ресурсів на томах 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 байти) з рядка.

Для даних, які є більш регулярними (наприклад, однорідний список int або 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 файлові об’єкти є високорівневим рівнем абстракції на низькорівневих файлових дескрипторах 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.

Cameron Laird maintains a useful set of pages about Python web technologies at 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.'

Дивись також

HOWTO Отримати Інтернет-ресурси за допомогою пакета 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 зазвичай використовується для допомоги з асинхронним введенням/виведенням на сокетах.

To prevent the TCP connect from blocking, you can set the socket to non-blocking mode. Then when you do the connect(), you will either connect immediately (unlikely) or get an exception that contains the error number as .errno. errno.EINPROGRESS indicates that the connection is in progress, but hasn’t finished yet. Different OSes will return different values, so you’re going to have to check what’s returned on your system.

You can use the connect_ex() method to avoid creating an exception. It will just return the errno value. To poll, you can call connect_ex() again later – 0 or errno.EISCONN indicate that you’re connected – or you can pass this socket to select.select() to check if it’s writable.

Примітка

The asyncio module provides a general purpose single-threaded and concurrent asynchronous library, which can be used for writing non-blocking network code. The third-party Twisted library is a popular and feature-rich alternative.

Бази даних

Чи існують інтерфейси для пакетів баз даних у Python?

Так.

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

Доступна підтримка більшості реляційних баз даних. Перегляньте DatabaseProgramming вікі-сторінку для отримання додаткової інформації.

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