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

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

### Як знайти модуль або програму для виконання завдання 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.

With the approval of PEP 703 work is now underway to remove the GIL from the CPython implementation of Python. Initially it will be implemented as an optional compiler flag when building the interpreter, and so separate builds will be available with and without the GIL. Long-term, the hope is to settle on a single build, once the performance implications of removing the GIL are fully understood. Python 3.13 is likely to be the first release containing this work, although it may not be completely functional in this release.

The current work to remove the GIL is based on a fork of Python 3.9 with the GIL removed by Sam Gross. Prior to that, in the days of Python 1.5, Greg Stein actually implemented a comprehensive patch set (the «free threading» patches) that removed the GIL and replaced it with fine-grained locking. Adam Olsen did a similar experiment in his python-safethread project. Unfortunately, both of these earlier experiments exhibited a sharp drop in single-thread performance (at least 30% slower), due to the amount of fine-grained locking necessary to compensate for the removal of the GIL. The Python 3.9 fork is the first attempt at removing the GIL with an acceptable performance impact.

The presence of the GIL in current Python releases doesn’t mean that you can’t make good use of Python on multi-CPU machines! You just have to be creative with dividing the work up between multiple processes rather than multiple threads. The `ProcessPoolExecutor` class in the new `concurrent.futures` module provides an easy way of doing so; the `multiprocessing` module provides a lower-level API in case you want more control over dispatching of tasks.

Розумне використання розширень C також допоможе; якщо ви використовуєте розширення C для виконання трудомісткого завдання, розширення може звільнити GIL, поки потік виконання знаходиться в коді C, і дозволить іншим потокам виконати певну роботу. Деякі стандартні бібліотечні модулі, такі як `zlib` і `hashlib`, вже це роблять.

An alternative approach to reducing the impact of the GIL is to make the GIL a per-interpreter-state lock rather than truly global. This was first implemented in Python 3.12 and is available in the C API. A Python interface to it is expected in Python 3.13. The main limitation to it at the moment is likely to be 3rd party extension modules, since these must be written with multiple interpreters in mind in order to be usable, so many older extension modules will not be usable.

## Вхід і вихід

### Як видалити файл? (І інші запитання щодо файлів…)

Використовуйте `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:

> 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` стає сміттям.

Але 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?

Ви можете знайти колекцію корисних посилань на Вікі-сторінці веб-програмування.

### Як надіслати пошту за допомогою сценарію 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()

This returns a random floating-point number in the range [0, 1).

У цьому модулі також є багато інших спеціалізованих генераторів, таких як:

- `randrange(a, b)` вибирає ціле число в діапазоні [a, b).

- `uniform(a, b)` chooses a floating-point number in the range [a, b).

- `normalvariate(mean, sdev)` вибірка нормального (гауссового) розподілу.

Деякі функції вищого рівня працюють безпосередньо з послідовностями, наприклад:

- `choice(S)` вибирає випадковий елемент із заданої послідовності.

- `shuffle(L)` перемішує список на місці, тобто переставляє його випадковим чином.

Існує також клас `Random`, який можна створити для створення незалежних кількох генераторів випадкових чисел.