Spec-Zone.ru › Python 3.14

Часто задаваемые вопросы о программировании

  • Общие вопросы

    • Существует ли отладчик на уровне исходного кода с точками останова и пошаговым выполнением?
    • Существуют ли инструменты для поиска ошибок или статического анализа?
    • Как создать автономный исполняемый файл из скрипта Python?
    • Существуют ли стандарты кодирования или руководство по стилю для программ на Python?
  • Основы языка

    • Почему возникает UnboundLocalError, хотя у переменной есть значение?
    • Каковы правила работы с локальными и глобальными переменными в Python?
    • Почему лямбда-функции, определённые в цикле с разными значениями, возвращают один и тот же результат?
    • Как использовать общие глобальные переменные в нескольких модулях?
    • Каковы «рекомендации по использованию import в модуле»?
    • Почему значения по умолчанию общие для разных объектов?
    • Как передать необязательные или именованные параметры из одной функции в другую?
    • В чём разница между аргументами и параметрами?
    • Почему изменение списка ‘y’ изменило и список ‘x’?
    • Как написать функцию с выходными параметрами (передача по ссылке)?
    • Как создать функцию высшего порядка в Python?
    • Как скопировать объект в Python?
    • Как узнать методы или атрибуты объекта?
    • Как программно узнать имя объекта?
    • Что нужно знать о приоритете оператора запятой?
    • Есть ли в Python аналог тернарного оператора C «?:»?
    • Можно ли писать в Python запутанные однострочные выражения?
    • Что означает косая черта (/) в списке параметров функции?
  • Числа и строки

    • Как записать целые числа в шестнадцатеричной и восьмеричной системах?
    • Почему -22 // 10 возвращает -3?
    • Как получить атрибут целочисленного литерала вместо SyntaxError?
    • Как преобразовать строку в число?
    • Как преобразовать число в строку?
    • Как изменить строку на месте?
    • Как вызывать функции и методы с помощью строк?
    • Есть ли аналог chomp() из Perl для удаления завершающих символов новой строки?
    • Есть ли аналог scanf() или sscanf()?
    • Что означает ошибка UnicodeDecodeError или UnicodeEncodeError?
    • Можно ли завершить необработанную строку нечётным количеством обратных косых черт?
  • Производительность

    • Моя программа работает слишком медленно. Как её ускорить?
    • Как эффективнее всего объединить множество строк?
  • Последовательности (кортежи/списки)

    • Как преобразовывать кортежи в списки и наоборот?
    • Что такое отрицательный индекс?
    • Как перебирать последовательность в обратном порядке?
    • Как удалить дубликаты из списка?
    • Как удалить из списка несколько элементов?
    • Как создать массив в Python?
    • Как создать многомерный список?
    • Как применить метод или функцию к последовательности объектов?
    • Почему выражение a_tuple[i] += [‘item’] вызывает исключение, хотя операция сложения выполняется?
    • Мне нужно выполнить сложную сортировку: можно ли использовать в Python преобразование Шварца?
    • Как отсортировать один список по значениям из другого списка?
  • Объекты

    • Что такое класс?
    • Что такое метод?
    • Что такое self?
    • Как проверить, является ли объект экземпляром указанного класса или его подкласса?
    • Что такое делегирование?
    • Как вызвать из производного класса метод, определённый в базовом классе, от которого он наследуется?
    • Как организовать код, чтобы упростить замену базового класса?
    • Как создать статические данные класса и статические методы класса?
    • Как перегрузить конструкторы (или методы) в Python?
    • При попытке использовать __spam возникает ошибка, связанная с _SomeClassName__spam.
    • В моём классе определён __del__, но при удалении объекта он не вызывается.
    • Как получить список всех экземпляров указанного класса?
    • Почему результат id() кажется неуникальным?
    • Когда можно полагаться на проверку идентичности с помощью оператора is?
    • Как подкласс может контролировать, какие данные хранятся в неизменяемом экземпляре?
    • Как кэшировать вызовы методов?
  • Модули

    • Как создать файл .pyc?
    • Как узнать имя текущего модуля?
    • Как организовать взаимный импорт модулей?
    • __import__(‘x.y.z’) возвращает <module ‘x’>; как получить z?
    • Я изменяю импортированный модуль и импортирую его повторно, но изменения не видны. Почему так происходит?

Общие вопросы

Существует ли отладчик на уровне исходного кода с точками останова и пошаговым выполнением?

Да.

Ниже описано несколько отладчиков для Python, а встроенная функция breakpoint() позволяет перейти к любому из них.

Модуль pdb — простой, но вполне подходящий консольный отладчик для Python. Он входит в стандартную библиотеку Python и доступен как documented in the Library Reference Manual. Вы также можете написать собственный отладчик, используя код pdb в качестве примера.

Интерактивная среда разработки IDLE, входящая в стандартный дистрибутив Python (обычно доступна как idlelib), включает графический отладчик.

PythonWin — это IDE для Python с графическим отладчиком на основе pdb. Отладчик PythonWin выделяет точки останова цветом и обладает рядом полезных возможностей, например позволяет отлаживать программы, не относящиеся к PythonWin. PythonWin доступен в составе проекта pywin32 и дистрибутива ActivePython.

Eric — это IDE, созданная на основе PyQt и компонента редактирования Scintilla.

trepan3k — отладчик, похожий на gdb.

Visual Studio Code — IDE со средствами отладки, интегрированная с системами контроля версий.

Существует ряд коммерческих IDE для Python с графическими отладчиками. К ним относятся:

  • Wing IDE
  • PyCharm

Существуют ли инструменты для поиска ошибок или статического анализа?

Да.

Ruff, Pylint и Pyflakes выполняют базовые проверки, которые помогут вам раньше обнаруживать ошибки.

Средства статической проверки типов, такие как mypy, ty, Pyrefly и pytype, могут проверять аннотации типов в исходном коде Python.

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

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

Один из вариантов — воспользоваться инструментом freeze, который входит в дерево исходного кода Python в каталоге Tools/freeze. Он преобразует байт-код Python в массивы C; с помощью компилятора C можно встроить все модули в новую программу, которая затем связывается со стандартными модулями Python.

Инструмент рекурсивно просматривает исходный код в поисках операторов import (в обеих формах) и ищет модули как в стандартном пути Python, так и в каталоге исходного кода (для встроенных модулей). Затем он преобразует байт-код модулей, написанных на Python, в код C (инициализаторы массивов, которые модуль marshal может преобразовать в объекты кода) и создаёт специальный конфигурационный файл, содержащий только те встроенные модули, которые действительно используются программой. После этого инструмент компилирует созданный код C и связывает его с остальной частью интерпретатора Python, формируя автономный исполняемый файл, который работает точно так же, как ваш скрипт.

Следующие пакеты помогут создать консольные приложения и приложения с графическим интерфейсом:

  • Nuitka (кроссплатформенный)
  • PyInstaller (кроссплатформенный)
  • PyOxidizer (кроссплатформенный)
  • cx_Freeze (кроссплатформенный)
  • py2app (только для macOS)
  • py2exe (только для Windows)

Существуют ли стандарты кодирования или руководство по стилю для программ на Python?

Да. Стиль кодирования, обязательный для модулей стандартной библиотеки, описан в документе PEP 8.

Основы языка

Почему возникает UnboundLocalError, если у переменной есть значение?

Может оказаться неожиданностью получить UnboundLocalError в ранее работавшем коде после его изменения — например, при добавлении оператора присваивания в тело функции.

Этот код:

>>> x = 10
>>> def bar():
...     print(x)
...
>>> bar()
10

работает, но этот код:

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1

приводит к UnboundLocalError:

>>> foo()
Traceback (most recent call last):
  ...
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value

Это происходит потому, что при присваивании значения переменной в некоторой области видимости она становится локальной для этой области и затеняет любые одноимённые переменные во внешней области. Поскольку в последнем операторе foo присваивается новое значение x, компилятор распознаёт её как локальную переменную. Поэтому при выполнении более раннего print(x) предпринимается попытка вывести неинициализированную локальную переменную, что приводит к ошибке.

В приведённом выше примере можно обратиться к переменной из внешней области видимости, объявив её глобальной:

>>> x = 10
>>> def foobar():
...     global x
...     print(x)
...     x += 1
...
>>> foobar()
10

Это явное объявление необходимо, чтобы напомнить вам, что (в отличие от внешне похожей ситуации с переменными класса и экземпляра) вы действительно изменяете значение переменной во внешней области видимости:

>>> print(x)
11

Вложенная область видимости работает аналогичным образом, если использовать ключевое слово nonlocal:

>>> def foo():
...    x = 10
...    def bar():
...        nonlocal x
...        print(x)
...        x += 1
...    bar()
...    print(x)
...
>>> foo()
10
11

Каковы правила использования локальных и глобальных переменных в Python?

В Python переменные, на которые ссылаются только внутри функции, неявно считаются глобальными. Если значение переменной присваивается где-либо в теле функции, она считается локальной, если только явно не объявлена глобальной.

На первый взгляд это может показаться неожиданным, но стоит немного подумать, и всё становится понятно. С одной стороны, требование использовать global для переменных, которым присваиваются значения, помогает предотвратить непреднамеренные побочные эффекты. С другой стороны, если бы global требовалось для всех обращений к глобальным переменным, вам пришлось бы постоянно использовать global. Вам нужно было бы объявлять глобальными все обращения к встроенным функциям и компонентам импортированных модулей. Такой нагромождённый код свёл бы на нет пользу от объявления global, которое помогает выявлять побочные эффекты.

Почему лямбда-функции, определённые в цикле с разными значениями, возвращают одинаковый результат?

Предположим, вы используете цикл for, чтобы определить несколько разных лямбда-функций (или даже обычных функций), например:

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda: x**2)

В результате вы получите список из 5 лямбда-функций, вычисляющих x**2. Можно ожидать, что при вызове они вернут соответственно 0, 1, 4, 9 и 16. Однако, если попробовать это сделать, вы увидите, что все они возвращают 16:

>>> squares[2]()
16
>>> squares[4]()
16

Это происходит потому, что x не является локальной переменной лямбда-функций, а определена во внешней области видимости. К ней обращаются при вызове лямбда-функции, а не при её определении. В конце цикла значение x равно 4, поэтому все функции теперь возвращают 4**2, то есть 16. Это также можно проверить, изменив значение x и посмотрев, как изменятся результаты лямбда-функций:

>>> x = 8
>>> squares[2]()
64

Чтобы этого избежать, нужно сохранить значения в переменных, локальных для лямбда-функций, чтобы они не зависели от значения глобальной переменной x:

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda n=x: n**2)

Здесь n=x создаёт новую переменную n, локальную для лямбда-функции, и вычисляет её при определении лямбда-функции, чтобы она получила то же значение, которое x имела в этот момент цикла. Это означает, что значение n будет равно 0 в первой лямбда-функции, 1 — во второй, 2 — в третьей и так далее. Таким образом, теперь каждая лямбда-функция будет возвращать правильный результат:

>>> squares[2]()
4
>>> squares[4]()
16

Обратите внимание, что такое поведение свойственно не только лямбда-функциям, но и обычным функциям.

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

Принятый способ обмениваться данными между модулями в одной программе — создать специальный модуль (часто его называют config или cfg). Импортируйте модуль config во все модули приложения; тогда он станет доступен в качестве глобального имени. Поскольку экземпляр каждого модуля существует в единственном числе, любые изменения объекта модуля будут видны повсюду. Например:

config.py:

x = 0   # Default value of the 'x' configuration setting

mod.py:

import config
config.x = 1

main.py:

import config
import mod
print(config.x)

Обратите внимание: по той же причине использование модуля также лежит в основе реализации шаблона проектирования «Одиночка».

Каковы «лучшие практики» использования import в модуле?

В целом не используйте from modulename import *. Это загромождает пространство имён импортирующего модуля и значительно затрудняет линтерам обнаружение неопределённых имён.

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

Рекомендуется импортировать модули в следующем порядке:

  1. модули стандартной библиотеки, например sys, os, argparse, re
  2. модули сторонних библиотек (всё, что установлено в каталоге site-packages Python), например dateutil, requests, tzdata
  3. модули, разработанные локально

Иногда необходимо перенести импорты в функцию или класс, чтобы избежать проблем с циклическими импортами. Гордон Макмиллан пишет:

Циклические импорты допустимы, если оба модуля используют форму импорта «import <module>». Они завершаются ошибкой, когда второй модуль пытается получить имя из первого («from module import name»), а импорт выполняется на верхнем уровне. Это происходит потому, что имена в первом модуле ещё недоступны: он занят импортом второго.

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

Также может понадобиться перенести импорты с верхнего уровня кода, если некоторые модули зависят от платформы. В таком случае, возможно, не удастся импортировать все модули в начале файла. Хороший вариант — импортировать соответствующие модули в коде для нужной платформы.

Переносите импорты в локальную область видимости, например внутрь определения функции, только если это необходимо для решения проблемы, такой как циклический импорт, или если вы пытаетесь сократить время инициализации модуля. Этот приём особенно полезен, если многие импорты не нужны в зависимости от того, как выполняется программа. Импорты также можно перенести внутрь функции, если модули используются только в ней. Обратите внимание: первая загрузка модуля может быть затратной из-за его однократной инициализации, но повторная загрузка модуля практически ничего не стоит — требуется лишь пара поисков в словаре. Даже если имя модуля вышло из области видимости, модуль, вероятно, доступен в sys.modules.

Почему значения по умолчанию являются общими для объектов?

Подобная ошибка часто подстерегает начинающих программистов. Рассмотрим эту функцию:

def foo(mydict={}):  # Danger: shared reference to one dict for all calls
    ... compute something ...
    mydict[key] = value
    return mydict

При первом вызове этой функции mydict содержит один элемент. При втором вызове mydict содержит два элемента, потому что к моменту начала выполнения foo() в mydict уже есть один элемент.

Часто ожидают, что при вызове функции для значений по умолчанию будут созданы новые объекты. Но это не так. Значения по умолчанию создаются ровно один раз — при определении функции. Если этот объект изменится, как словарь в данном примере, при последующих вызовах функции будет использоваться уже изменённый объект.

Неизменяемые объекты, такие как числа, строки, кортежи и None, по определению не подвержены изменениям. Изменение изменяемых объектов, таких как словари, списки и экземпляры классов, может привести к неожиданным последствиям.

Учитывая эту особенность, хорошей практикой программирования считается не использовать изменяемые объекты в качестве значений по умолчанию. Вместо этого используйте None в качестве значения по умолчанию, а внутри функции проверяйте, равно ли ему значение параметра None, и, если да, создавайте новый список, словарь или другой объект. Например, не пишите:

def foo(mydict={}):
    ...

а пишите:

def foo(mydict=None):
    if mydict is None:
        mydict = {}  # create a new dict for local namespace

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

# Callers can only provide two parameters and optionally pass _cache by keyword
def expensive(arg1, arg2, *, _cache={}):
    if (arg1, arg2) in _cache:
        return _cache[(arg1, arg2)]

    # Calculate the value
    result = ... expensive computation ...
    _cache[(arg1, arg2)] = result           # Store result in the cache
    return result

Вместо значения по умолчанию можно использовать глобальную переменную со словарём — это дело вкуса.

Как передать необязательные или именованные параметры из одной функции в другую?

Соберите аргументы с помощью спецификаторов * и ** в списке параметров функции; так вы получите позиционные аргументы в виде кортежа, а именованные аргументы — в виде словаря. Затем эти аргументы можно передать при вызове другой функции, используя * и **:

def f(x, *args, **kwargs):
    ...
    kwargs['width'] = '14.3c'
    ...
    g(x, *args, **kwargs)

В чём разница между аргументами и параметрами?

Параметры задаются именами, указанными в определении функции, а аргументы — это значения, фактически передаваемые функции при её вызове. Параметры определяют, какие типы аргументов может принимать функция. Например, для следующего определения функции:

def func(foo, bar=None, **kwargs):
    pass

foo, bar и kwargs — параметры func. Однако при вызове func, например:

func(42, bar=314, extra=somevar)

значения 42, 314 и somevar являются аргументами.

Почему при изменении списка «y» изменился и список «x»?

Если вы написали такой код:

>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]

возможно, вас интересует, почему добавление элемента в y также изменило x.

К этому приводят два фактора:

  1. Переменные — это просто имена, ссылающиеся на объекты. Операция y = x не создаёт копию списка — она создаёт новую переменную y, ссылающуюся на тот же объект, что и x. Это означает, что существует только один объект (список), на который ссылаются обе переменные: x и y.
  2. Списки являются изменяемыми, то есть их содержимое можно менять.

После вызова append() содержимое изменяемого объекта изменилось с [] на [10]. Поскольку обе переменные ссылаются на один и тот же объект, при использовании любого из имён вы получите изменённое значение [10].

Если вместо этого присвоить x неизменяемый объект:

>>> x = 5  # ints are immutable
>>> y = x
>>> x = x + 1  # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5

мы увидим, что в этом случае x и y больше не равны. Это происходит потому, что целые числа являются неизменяемыми, и при выполнении x = x + 1 мы не изменяем целое число 5, увеличивая его значение; вместо этого мы создаём новый объект (целое число 6) и присваиваем его переменной x (то есть меняем объект, на который ссылается x). После этого присваивания у нас есть два объекта (целые числа 6 и 5) и две переменные, ссылающиеся на них (теперь x ссылается на 6, а y по-прежнему ссылается на 5).

Некоторые операции (например, y.append(10) и y.sort()) изменяют объект, а внешне похожие операции (например, y = y + [10] и sorted(y)) создают новый объект. В Python в целом (и во всех случаях в стандартной библиотеке) метод, изменяющий объект, возвращает None, чтобы не путать эти два типа операций. Поэтому, если вы по ошибке напишете y.sort(), думая, что получите отсортированную копию y, вместо этого вы получите None, что, скорее всего, приведёт к легко диагностируемой ошибке в программе.

Однако есть один класс операций, поведение которых иногда различается в зависимости от типа объекта: составные операторы присваивания. Например, += изменяет списки, но не кортежи и целые числа (a_list += [1, 2, 3] эквивалентно a_list.extend([1, 2, 3]) и изменяет a_list, тогда как some_tuple += (1, 2, 3) и some_int += 1 создают новые объекты).

Иными словами:

  • Если у нас есть изменяемый объект (например, list, dict, set), мы можем использовать определённые операции для его изменения, и все переменные, ссылающиеся на него, увидят это изменение.
  • Если у нас есть неизменяемый объект (например, str, int, tuple), все переменные, ссылающиеся на него, всегда будут видеть одно и то же значение, но операции, преобразующие это значение в новое, всегда возвращают новый объект.

Чтобы узнать, ссылаются ли две переменные на один и тот же объект, можно использовать оператор is или встроенную функцию id().

Как написать функцию с выходными параметрами (передача по ссылке)?

Помните, что в Python аргументы передаются посредством присваивания. Поскольку присваивание лишь создаёт ссылки на объекты, между именем аргумента в вызывающей функции и именем в вызываемой функции нет псевдонима, а значит, и передачи по ссылке нет. Добиться нужного результата можно несколькими способами.

  1. Возвращая кортеж результатов:

    >>> def func1(a, b):
    ...     a = 'new-value'        # a and b are local names
    ...     b = b + 1              # assigned to new objects
    ...     return a, b            # return new values
    ...
    >>> x, y = 'old-value', 99
    >>> func1(x, y)
    ('new-value', 100)
    

    Почти всегда это самое понятное решение.

  2. Используя глобальные переменные. Это небезопасно в многопоточной среде и не рекомендуется.
  3. Передавая изменяемый объект (который можно менять на месте):

    >>> def func2(a):
    ...     a[0] = 'new-value'     # 'a' references a mutable list
    ...     a[1] = a[1] + 1        # changes a shared object
    ...
    >>> args = ['old-value', 99]
    >>> func2(args)
    >>> args
    ['new-value', 100]
    
  4. Передавая словарь, который будет изменён:

    >>> def func3(args):
    ...     args['a'] = 'new-value'     # args is a mutable dictionary
    ...     args['b'] = args['b'] + 1   # change it in-place
    ...
    >>> args = {'a': 'old-value', 'b': 99}
    >>> func3(args)
    >>> args
    {'a': 'new-value', 'b': 100}
    
  5. Или объединяя значения в экземпляре класса:

    >>> class Namespace:
    ...     def __init__(self, /, **args):
    ...         for key, value in args.items():
    ...             setattr(self, key, value)
    ...
    >>> def func4(args):
    ...     args.a = 'new-value'        # args is a mutable Namespace
    ...     args.b = args.b + 1         # change object in-place
    ...
    >>> args = Namespace(a='old-value', b=99)
    >>> func4(args)
    >>> vars(args)
    {'a': 'new-value', 'b': 100}
    

    Почти никогда нет веских причин настолько усложнять решение.

Лучше всего вернуть кортеж, содержащий несколько результатов.

Как создать функцию высшего порядка в Python?

Есть два варианта: использовать вложенные области видимости или вызываемые объекты. Например, предположим, что вы хотите определить linear(a,b), которая возвращает функцию f(x), вычисляющую значение a*x+b. С помощью вложенных областей видимости:

def linear(a, b):
    def result(x):
        return a * x + b
    return result

Или с помощью вызываемого объекта:

class linear:

    def __init__(self, a, b):
        self.a, self.b = a, b

    def __call__(self, x):
        return self.a * x + self.b

В обоих случаях

taxes = linear(0.3, 2)

возвращает вызываемый объект, для которого taxes(10e6) == 0.3 * 10e6 + 2.

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

class exponential(linear):
    # __init__ inherited
    def __call__(self, x):
        return self.a * (x ** self.b)

Объект может хранить состояние для нескольких методов:

class counter:

    value = 0

    def set(self, x):
        self.value = x

    def up(self):
        self.value = self.value + 1

    def down(self):
        self.value = self.value - 1

count = counter()
inc, dec, reset = count.up, count.down, count.set

Здесь inc(), dec() и reset() действуют как функции, использующие одну и ту же переменную-счётчик.

Как скопировать объект в Python?

В общем случае попробуйте использовать copy.copy() или copy.deepcopy(). Копировать можно не все объекты, но большинство — можно.

Некоторые объекты копировать проще. У словарей есть метод copy():

newdict = olddict.copy()

Последовательности можно копировать с помощью срезов:

new_l = l[:]

Как узнать методы или атрибуты объекта?

Для экземпляра x пользовательского класса функция dir(x) возвращает отсортированный по алфавиту список имён, содержащий атрибуты и методы экземпляра, а также атрибуты, определённые его классом.

Как коду узнать имя объекта?

Как правило, никак, потому что у объектов на самом деле нет имён. По сути, присваивание всегда связывает имя со значением; то же верно для операторов def и class, но в этом случае значением является вызываемый объект. Рассмотрим следующий код:

>>> class A:
...     pass
...
>>> B = A
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x16D07CC>
>>> print(a)
<__main__.A object at 0x16D07CC>

Можно утверждать, что у класса есть имя: хотя он связан с двумя именами и вызывается через имя B, созданный экземпляр всё равно считается экземпляром класса A. Однако невозможно сказать, называется ли экземпляр a или b, поскольку оба имени связаны с одним и тем же значением.

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

Однажды на comp.lang.python Фредрик Лунд дал отличный ответ на этот вопрос, использовав аналогию:

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

…и не удивляйтесь, если выяснится, что у неё много имён или вовсе нет имени!

Что происходит с приоритетом оператора запятой?

В Python запятая не является оператором. Рассмотрим этот сеанс:

>>> "a" in "b", "a"
(False, 'a')

Поскольку запятая — не оператор, а разделитель выражений, приведённое выше вычисляется так, как если бы вы ввели:

("a" in "b"), "a"

а не:

"a" in ("b", "a")

То же верно для различных операторов присваивания (=, += и так далее). На самом деле это не операторы, а синтаксические разделители в операторах присваивания.

Есть ли аналог тернарного оператора «?:» из C?

Да, есть. Синтаксис выглядит так:

[on_true] if [expression] else [on_false]

x, y = 50, 25
small = x if x < y else y

До появления этого синтаксиса в Python 2.5 часто использовали логические операторы:

[expression] and [on_true] or [on_false]

Однако этот приём небезопасен, так как может дать неверный результат, если on_true имеет ложное логическое значение. Поэтому всегда лучше использовать форму ... if ... else ....

Можно ли писать в Python запутанные однострочные выражения?

Да. Обычно это делают, вкладывая lambda в lambda. Ниже приведены три примера, слегка адаптированные из работы Ульфа Бартелта:

from functools import reduce

# Primes < 1000
print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))

# First 10 Fibonacci numbers
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
f(x,f), range(10))))

# Mandelbrot set
print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+'\n'+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
>=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24))
#    \___ ___/  \___ ___/  |   |   |__ lines on screen
#        V          V      |   |______ columns on screen
#        |          |      |__________ maximum of "iterations"
#        |          |_________________ range on y axis
#        |____________________________ range on x axis

Дети, не пытайтесь повторить это дома!

Что означает косая черта (/) в списке параметров функции?

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

>>> help(divmod)
Help on built-in function divmod in module builtins:

divmod(x, y, /)
    Return the tuple (x//y, x%y).  Invariant: div*y + mod == x.

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

>>> divmod(x=3, y=4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: divmod() takes no keyword arguments

Числа и строки

Как задать шестнадцатеричные и восьмеричные целые числа?

Чтобы указать восьмеричное число, поставьте перед ним ноль, а затем строчную или прописную букву «o». Например, чтобы присвоить переменной «a» восьмеричное значение «10» (8 в десятичной системе), введите:

>>> a = 0o10
>>> a
8

С шестнадцатеричными числами всё так же просто. Поставьте перед шестнадцатеричным числом ноль, а затем строчную или прописную букву «x». Шестнадцатеричные цифры можно задавать в нижнем или верхнем регистре. Например, в интерпретаторе Python:

>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178

Почему -22 // 10 возвращает -3?

В первую очередь это обусловлено желанием, чтобы i % j имело тот же знак, что и j. Если вы хотите этого, а также хотите, чтобы выполнялось следующее:

i == (i // j) * j + (i % j)

тогда целочисленное деление должно возвращать округление вниз. C также требует соблюдения этого тождества, и поэтому компиляторы, которые отсекают i // j, должны сделать так, чтобы i % j имело тот же знак, что и i.

Случаев, когда i % j имеет отрицательное значение j, немного. Когда j положительно, таких случаев много, и почти во всех из них полезнее, чтобы i % j было >= 0. Если сейчас часы показывают 10, что они показывали 200 часов назад? -190 % 12 == 2 полезно; -190 % 12 == -10 — ошибка, которая рано или поздно даст о себе знать.

Как получить атрибут литерала int вместо SyntaxError?

Попытка обычным способом получить атрибут литерала int приводит к ошибке SyntaxError, потому что точка воспринимается как десятичный разделитель:

>>> 1.__class__
  File "<stdin>", line 1
  1.__class__
   ^
SyntaxError: invalid decimal literal

Решение — отделить литерал от точки пробелом или скобками.

>>> 1 .__class__
<class 'int'>
>>> (1).__class__
<class 'int'>

Как преобразовать строку в число?

Для целых чисел используйте встроенный конструктор типа int(), например, int('144') == 144. Аналогично, float() преобразует значение в число с плавающей точкой, например, float('144') == 144.0.

По умолчанию эти функции интерпретируют число как десятичное: int('0144') == 144 выполняется, а int('0x144') вызывает исключение ValueError. int(string, base) принимает систему счисления, из которой нужно выполнить преобразование, в качестве второго необязательного аргумента, поэтому int( '0x144', 16) == 324. Если система счисления указана как 0, число интерпретируется по правилам Python: начальное «0o» обозначает восьмеричное число, а «0x» — шестнадцатеричное.

Не используйте встроенную функцию eval(), если вам нужно только преобразовать строки в числа. eval() работает значительно медленнее и представляет угрозу безопасности: вам могут передать выражение Python, которое приведёт к нежелательным побочным эффектам. Например, вам могут передать __import__('os').system("rm -rf $HOME"), что удалит вашу домашнюю директорию.

eval() также интерпретирует числа как выражения Python, поэтому, например, eval('09') вызывает синтаксическую ошибку, поскольку Python не допускает начальный «0» в десятичном числе (кроме самого «0»).

Как преобразовать число в строку?

Например, чтобы преобразовать число 144 в строку '144', используйте встроенный конструктор типа str(). Если вам нужно шестнадцатеричное или восьмеричное представление, используйте встроенные функции hex() или oct(). Для более сложного форматирования см. разделы f-строки и Синтаксис форматирования строк. Например, "{:04d}".format(144) возвращает '0144', а "{:.3f}".format(1.0/3.0) возвращает '0.333'.

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

Никак, поскольку строки неизменяемы. В большинстве случаев следует просто создать новую строку из нужных частей. Однако если вам нужен объект, позволяющий изменять данные Unicode на месте, попробуйте использовать объект io.StringIO или модуль array:

>>> import io
>>> s = "Hello, world"
>>> sio = io.StringIO(s)
>>> sio.getvalue()
'Hello, world'
>>> sio.seek(7)
7
>>> sio.write("there!")
6
>>> sio.getvalue()
'Hello, there!'

>>> import array
>>> a = array.array('w', s)
>>> print(a)
array('w', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('w', 'yello, world')
>>> a.tounicode()
'yello, world'

Как вызывать функции/методы с помощью строк?

Есть несколько способов.

  • Лучше всего использовать словарь, сопоставляющий строки функциям. Главное преимущество этого подхода в том, что строки не обязательно должны совпадать с именами функций. Это также основной способ эмулировать оператор выбора:

    def a():
        pass
    
    def b():
        pass
    
    dispatch = {'go': a, 'stop': b}  # Note lack of parens for funcs
    
    dispatch[get_input()]()  # Note trailing parens to call function
    
  • Используйте встроенную функцию getattr():

    import foo
    getattr(foo, 'bar')()
    

    Обратите внимание, что getattr() работает с любыми объектами, включая классы, экземпляры классов, модули и так далее.

    Этот способ используется в нескольких местах стандартной библиотеки, например:

    class Foo:
        def do_foo(self):
            ...
    
        def do_bar(self):
            ...
    
    f = getattr(foo_instance, 'do_' + opname)
    f()
    
  • Используйте locals(), чтобы получить функцию по её имени:

    def myFunc():
        print("hello")
    
    fname = "myFunc"
    
    f = locals()[fname]
    f()
    

Есть ли аналог chomp() из Perl для удаления завершающих символов новой строки?

Можно использовать S.rstrip("\r\n"), чтобы удалить все символы конца строки в конце строки S, не удаляя другие завершающие пробельные символы. Если строка S содержит несколько строк и в конце несколько пустых строк, символы конца строки будут удалены у всех пустых строк:

>>> lines = ("line 1 \r\n"
...          "\r\n"
...          "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '

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

Есть ли аналог scanf() или sscanf()?

Прямого аналога нет.

Для простого разбора входных данных обычно проще всего разбить строку на слова, разделённые пробельными символами, с помощью метода split() строковых объектов, а затем преобразовать десятичные строки в числовые значения с помощью int() или float(). split() поддерживает необязательный параметр «sep», который полезен, если в строке в качестве разделителя используется что-то, кроме пробельных символов.

Для более сложного разбора входных данных регулярные выражения мощнее C-функции sscanf и лучше подходят для этой задачи.

Что означает ошибка UnicodeDecodeError или UnicodeEncodeError?

См. руководство по Unicode.

Можно ли завершить необработанную строку нечётным числом обратных косых черт?

Если необработанная строка заканчивается нечётным числом обратных косых черт, одна из них экранирует кавычку строки:

>>> r'C:\this\will\not\work\'
  File "<stdin>", line 1
    r'C:\this\will\not\work\'
    ^
SyntaxError: unterminated string literal (detected at line 1)

Есть несколько способов обойти эту проблему. Один из них — использовать обычные строки и удвоить обратные косые черты:

>>> 'C:\\this\\will\\work\\'
'C:\\this\\will\\work\\'

Другой способ — добавить к необработанной строке обычную строку с экранированной обратной косой чертой:

>>> r'C:\this\will\work' '\\'
'C:\\this\\will\\work\\'

В Windows также можно использовать os.path.join(), чтобы добавить обратную косую черту:

>>> os.path.join(r'C:\this\will\work', '')
'C:\\this\\will\\work\\'

Обратите внимание: хотя обратная косая черта «экранирует» кавычку, определяя, где заканчивается необработанная строка, при интерпретации значения необработанной строки экранирования не происходит. То есть обратная косая черта остаётся в значении необработанной строки:

>>> r'backslash\'preserved'
"backslash\\'preserved"

Также см. спецификацию в справочнике по языку.

Производительность

Моя программа работает слишком медленно. Как её ускорить?

В общем случае на этот вопрос непросто ответить. Для начала перечислим несколько вещей, о которых стоит помнить:

  • Характеристики производительности различаются в разных реализациях Python. В этом разделе часто задаваемых вопросов рассматривается CPython.
  • Поведение может различаться в разных операционных системах, особенно при работе с вводом-выводом или многопоточностью.
  • Прежде чем пытаться оптимизировать код, всегда находите узкие места своей программы (см. модуль profile).
  • Создание тестовых сценариев производительности позволит быстро проверять разные варианты в поисках улучшений (см. модуль timeit).
  • Настоятельно рекомендуется обеспечить хороший охват кода тестами (с помощью модульного тестирования или другими способами), прежде чем вносить сложные оптимизации, которые могут привести к скрытым регрессиям.

Тем не менее существует множество приёмов для ускорения кода Python. Вот несколько общих принципов, которые помогут добиться приемлемой производительности:

  • Ускорение алгоритмов (или замена их на более быстрые) может дать гораздо больший эффект, чем попытки повсеместно применять микрооптимизации.
  • Используйте подходящие структуры данных. Изучите документацию по встроенным типам и модулю collections.
  • Если в стандартной библиотеке есть средство для выполнения какой-либо задачи, вероятно (хотя и не гарантированно), оно будет работать быстрее любого альтернативного решения, которое вы сможете придумать. Это особенно верно для средств, написанных на C, таких как встроенные функции и некоторые типы расширений. Например, для сортировки обязательно используйте либо встроенный метод list.sort(), либо связанную с ним функцию sorted() (примеры достаточно сложного использования см. в разделе Методы сортировки).
  • Абстракции часто создают дополнительные уровни косвенности и заставляют интерпретатор выполнять больше работы. Если затраты на косвенность превышают объём полезной работы, программа будет работать медленнее. Избегайте избыточных абстракций, особенно в виде маленьких функций или методов (которые также часто ухудшают читаемость).

Если вы достигли предела возможностей чистого Python, существуют инструменты, которые помогут продвинуться дальше. Например, Cython может скомпилировать слегка изменённый код Python в расширение C и работать на многих разных платформах. Cython использует компиляцию (и необязательные аннотации типов), чтобы значительно ускорить код по сравнению с интерпретируемым вариантом. Если вы уверенно владеете C, можно также самостоятельно написать модуль расширения на C.

См. также

Страница вики, посвящённая советам по повышению производительности.

Как эффективнее всего объединить множество строк?

Объекты str и bytes неизменяемы, поэтому объединять множество строк неэффективно: при каждом объединении создаётся новый объект. В общем случае общие затраты времени квадратично зависят от длины итоговой строки.

Чтобы накопить множество объектов str, рекомендуется помещать их в список и в конце вызывать str.join():

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

(Ещё один достаточно эффективный способ — использовать io.StringIO.)

Чтобы накопить множество объектов bytes, рекомендуется расширять объект bytearray с помощью конкатенации на месте (оператор +=):

result = bytearray()
for b in my_bytes_objects:
    result += b

Последовательности (кортежи/списки)

Как преобразовать кортеж в список и наоборот?

Конструктор типа tuple(seq) преобразует любую последовательность (а точнее, любой итерируемый объект) в кортеж с теми же элементами в том же порядке.

Например, tuple([1, 2, 3]) возвращает (1, 2, 3), а tuple('abc') возвращает ('a', 'b', 'c'). Если аргументом является кортеж, копия не создаётся и возвращается тот же объект, поэтому вызов tuple() не требует больших затрат, если вы не уверены, что объект уже является кортежем.

Конструктор типа list(seq) преобразует любую последовательность или итерируемый объект в список с теми же элементами в том же порядке. Например, list((1, 2, 3)) возвращает [1, 2, 3], а list('abc') возвращает ['a', 'b', 'c']. Если аргументом является список, создаётся его копия, как и при вызове seq[:].

Что такое отрицательный индекс?

Для индексации последовательностей Python используются положительные и отрицательные числа. При положительной индексации 0 — это первый индекс, 1 — второй и так далее. При отрицательной индексации -1 — последний индекс, -2 — предпоследний и так далее. Считайте, что seq[-n] — это то же самое, что seq[len(seq)-n].

Отрицательные индексы могут быть очень удобны. Например, S[:-1] — это вся строка, кроме последнего символа, что полезно для удаления завершающего символа новой строки.

Как перебрать последовательность в обратном порядке?

Используйте встроенную функцию reversed():

for x in reversed(sequence):
    ...  # do something with x ...

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

Как удалить дубликаты из списка?

Подробное обсуждение множества способов решения этой задачи см. в Python Cookbook:

https://code.activestate.com/recipes/52560/

Если порядок элементов в списке не важен, отсортируйте список, а затем просмотрите его с конца, удаляя дубликаты по мере их обнаружения:

if mylist:
    mylist.sort()
    last = mylist[-1]
    for i in range(len(mylist)-2, -1, -1):
        if last == mylist[i]:
            del mylist[i]
        else:
            last = mylist[i]

Если все элементы списка можно использовать в качестве ключей множества (то есть все они являются хешируемыми), этот способ часто работает быстрее:

mylist = list(set(mylist))

Этот код преобразует список во множество, удаляя дубликаты, а затем снова преобразует его в список.

Как удалить из списка несколько элементов?

Как и при удалении дубликатов, можно явно перебирать список в обратном порядке и удалять элементы по условию. Однако проще и быстрее использовать замену среза с неявным или явным перебором вперёд. Вот три варианта:

mylist[:] = filter(keep_function, mylist)
mylist[:] = (x for x in mylist if keep_condition)
mylist[:] = [x for x in mylist if keep_condition]

Генератор списков может работать быстрее всего.

Как создать массив в Python?

Используйте список:

["this", 1, "is", "an", "array"]

С точки зрения временной сложности списки эквивалентны массивам C или Pascal; главное отличие состоит в том, что список Python может содержать объекты разных типов.

Модуль array также предоставляет методы для создания массивов фиксированных типов с компактным представлением, однако индексирование в них медленнее, чем в списках. Также обратите внимание, что NumPy и другие сторонние пакеты предоставляют структуры, подобные массивам, с различными характеристиками.

Чтобы получить связные списки в стиле Lisp, можно эмулировать cons-ячейки с помощью кортежей:

lisp_list = ("like",  ("this",  ("example", None) ) )

Если нужна изменяемость, вместо кортежей можно использовать списки. Здесь аналог car в Lisp — это lisp_list[0], а аналог cdr — lisp_list[1]. Делайте так, только если уверены, что это действительно необходимо, поскольку обычно такой способ намного медленнее использования списков Python.

Как создать многомерный список?

Возможно, вы пытались создать многомерный массив так:

>>> A = [[None] * 2] * 3

При выводе на экран он выглядит правильно:

>>> A
[[None, None], [None, None], [None, None]]

Но при присваивании значения оно появляется сразу в нескольких местах:

>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]

Причина в том, что при дублировании списка с помощью * копии не создаются — вместо этого создаются ссылки на существующие объекты. *3 создаёт список, содержащий 3 ссылки на один и тот же список длины два. Изменения в одной строке будут видны во всех строках, чего вы, скорее всего, не хотите.

Рекомендуется сначала создать список нужной длины, а затем заполнить каждый его элемент новым списком:

A = [None] * 3
for i in range(3):
    A[i] = [None] * 2

В результате будет создан список из 3 разных списков длины два. Также можно использовать генератор списков:

w, h = 2, 3
A = [[None] * w for i in range(h)]

Ещё можно использовать расширение, предоставляющее тип данных «матрица»; наиболее известное из них — NumPy.

Как применить метод или функцию к последовательности объектов?

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

result = [obj.method() for obj in mylist]

result = [function(obj) for obj in mylist]

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

for obj in mylist:
    obj.method()

for obj in mylist:
    function(obj)

Почему a_tuple[i] += [‘item’] вызывает исключение, хотя сложение выполняется?

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

Это объяснение в целом применимо к операторам расширенного присваивания, используемым для элементов кортежа, ссылающихся на изменяемые объекты, но в качестве примера мы рассмотрим list и +=.

Если написать:

>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
   ...
TypeError: 'tuple' object does not support item assignment

Причина исключения должна быть очевидна: 1 добавляется к объекту, на который указывает a_tuple[0] (1), и получается объект 2. Но при попытке присвоить результат вычисления, 2, элементу 0 кортежа возникает ошибка, поскольку изменить объект, на который указывает элемент кортежа, нельзя.

В действительности этот оператор расширенного присваивания примерно эквивалентен следующему:

>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

Ошибка возникает при выполнении части присваивания, поскольку кортеж неизменяем.

Если написать, например:

>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

Исключение выглядит несколько неожиданным, а ещё удивительнее то, что, несмотря на ошибку, добавление элемента выполнилось:

>>> a_tuple[0]
['foo', 'item']

Чтобы понять, почему так происходит, нужно знать, что (а) если объект реализует магический метод __iadd__(), он вызывается при выполнении расширенного присваивания +=, а возвращаемое им значение используется в операторе присваивания; и (б) для списков __iadd__() эквивалентен вызову extend() для списка с последующим возвратом этого списка. Именно поэтому для списков += называют сокращённой записью для list.extend():

>>> a_list = []
>>> a_list += [1]
>>> a_list
[1]

Это эквивалентно следующему:

>>> result = a_list.__iadd__([1])
>>> a_list = result

Объект, на который указывает a_list, был изменён, и указатель на изменённый объект присваивается обратно переменной a_list. В итоге присваивание ничего не меняет, поскольку это указатель на тот же объект, на который ранее указывала a_list, но само присваивание всё равно выполняется.

Таким образом, в примере с кортежем происходит то же самое, что и здесь:

>>> result = a_tuple[0].__iadd__(['item'])
>>> a_tuple[0] = result
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

Операция __iadd__() выполняется успешно, и список расширяется. Но, хотя result указывает на тот же объект, что и a_tuple[0], последнее присваивание всё равно приводит к ошибке, поскольку кортежи неизменяемы.

Мне нужна сложная сортировка: можно ли выполнить преобразование Шварца в Python?

Этот метод, названный в честь Рэндала Шварца из сообщества Perl, сортирует элементы списка по показателю, который сопоставляет каждому элементу его «значение сортировки». В Python для этого используйте аргумент key метода list.sort():

Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))

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

Объедините их в итератор кортежей, отсортируйте полученный список, а затем выберите нужный элемент.

>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']

Объекты

Что такое класс?

Класс — это конкретный тип объекта, создаваемый при выполнении оператора class. Объекты классов используются в качестве шаблонов для создания объектов экземпляров, которые воплощают как данные (атрибуты), так и код (методы), характерные для определённого типа данных.

Класс может быть основан на одном или нескольких других классах, называемых его базовыми классами. В таком случае он наследует атрибуты и методы своих базовых классов. Это позволяет последовательно уточнять объектную модель с помощью наследования. Например, можно создать обобщённый класс Mailbox, предоставляющий базовые методы доступа к почтовому ящику, и подклассы, такие как MboxMailbox, MaildirMailbox, OutlookMailbox, обрабатывающие различные специфические форматы почтовых ящиков.

Что такое метод?

Метод — это функция некоторого объекта x, которую обычно вызывают как x.name(arguments...). Методы определяются как функции внутри определения класса:

class C:
    def meth(self, arg):
        return arg * 2 + self.attribute

Что такое self?

Self — это всего лишь общепринятое имя первого аргумента метода. Метод, определённый как meth(self, a, b, c), следует вызывать как x.meth(a, b, c) для некоторого экземпляра x класса, в котором находится определение; вызванный метод будет считать, что его вызвали как meth(x, a, b, c).

См. также Почему в определениях и вызовах методов необходимо явно использовать «self»?.

Как проверить, является ли объект экземпляром данного класса или его подкласса?

Используйте встроенную функцию isinstance(obj, cls). Можно проверить, является ли объект экземпляром одного из нескольких классов, передав кортеж вместо одного класса, например, isinstance(obj, (class1, class2, ...)). Также можно проверить, является ли объект одним из встроенных типов Python, например, isinstance(obj, str) или isinstance(obj, (int, float, complex)).

Обратите внимание, что isinstance() также проверяет виртуальное наследование от абстрактного базового класса. Поэтому проверка вернёт True для зарегистрированного класса, даже если он не наследовался от него прямо или косвенно. Чтобы проверить «настоящее наследование», просмотрите порядок разрешения методов (MRO) класса:

from collections.abc import Mapping

class P:
     pass

class C(P):
    pass

Mapping.register(P)
>>> c = C()
>>> isinstance(c, C)        # direct
True
>>> isinstance(c, P)        # indirect
True
>>> isinstance(c, Mapping)  # virtual
True

# Actual inheritance chain
>>> type(c).__mro__
(<class 'C'>, <class 'P'>, <class 'object'>)

# Test for "true inheritance"
>>> Mapping in type(c).__mro__
False

Обратите внимание, что в большинстве программ isinstance() нечасто используется для классов, определённых пользователем. Если вы сами разрабатываете классы, более правильный объектно-ориентированный подход — определять в классах методы, инкапсулирующие определённое поведение, вместо проверки класса объекта и выполнения разных действий в зависимости от него. Например, если у вас есть функция, которая выполняет какое-либо действие:

def search(obj):
    if isinstance(obj, Mailbox):
        ...  # code to search a mailbox
    elif isinstance(obj, Document):
        ...  # code to search a document
    elif ...

Лучше определить метод search() во всех классах и просто вызывать его:

class Mailbox:
    def search(self):
        ...  # code to search a mailbox

class Document:
    def search(self):
        ...  # code to search a document

obj.search()

Что такое делегирование?

Делегирование — это объектно-ориентированный приём (также называемый шаблоном проектирования). Допустим, у вас есть объект x, и вы хотите изменить поведение только одного из его методов. Можно создать новый класс, который реализует новый вариант интересующего вас метода, а все остальные методы делегирует соответствующим методам объекта x.

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

class UpperOut:

    def __init__(self, outfile):
        self._outfile = outfile

    def write(self, s):
        self._outfile.write(s.upper())

    def __getattr__(self, name):
        return getattr(self._outfile, name)

Здесь класс UpperOut переопределяет метод write(), преобразуя строку-аргумент в верхний регистр перед вызовом нижележащего метода self._outfile.write(). Все остальные методы делегируются нижележащему объекту self._outfile. Делегирование выполняется с помощью метода __getattr__(); дополнительные сведения об управлении доступом к атрибутам см. в справочнике по языку.

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

class X:
    ...
    def __setattr__(self, name, value):
        self.__dict__[name] = value
    ...

Многие реализации __setattr__() вызывают object.__setattr__(), чтобы установить атрибут у self и избежать бесконечной рекурсии:

class X:
    def __setattr__(self, name, value):
        # Custom logic here...
        object.__setattr__(self, name, value)

В качестве альтернативы атрибуты можно устанавливать, непосредственно добавляя записи в self.__dict__.

Как из производного класса вызвать метод базового класса, от которого он наследуется?

Используйте встроенную функцию super():

class Derived(Base):
    def meth(self):
        super().meth()  # calls Base.meth

В этом примере super() автоматически определит экземпляр, из которого она была вызвана (значение self), найдёт порядок разрешения методов (MRO) с помощью type(self).__mro__ и вернёт следующий класс после Derived в MRO: Base.

Как организовать код, чтобы упростить смену базового класса?

Можно присвоить базовому классу псевдоним и наследоваться от него. Тогда для смены базового класса достаточно изменить значение, присвоенное псевдониму. Этот приём также полезен, если вы хотите динамически выбирать базовый класс (например, в зависимости от доступности ресурсов). Пример:

class Base:
    ...

BaseAlias = Base

class Derived(BaseAlias):
    ...

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

Python поддерживает статические данные и статические методы (в смысле C++ или Java).

Чтобы создать статические данные, просто определите атрибут класса. Чтобы присвоить атрибуту новое значение, необходимо явно указать имя класса в операторе присваивания:

class C:
    count = 0   # number of times C.__init__ called

    def __init__(self):
        C.count = C.count + 1

    def getcount(self):
        return C.count  # or return self.count

c.count также ссылается на C.count для любого c, такого что выполняется isinstance(c, C), если только его не переопределяет сам c или какой-либо класс в цепочке поиска базовых классов от c.__class__ до C.

Внимание: внутри метода класса C присваивание вида self.count = 42 создаёт в собственном словаре self новый, не связанный с классом экземпляр с именем «count». Для перепривязки имени статических данных класса всегда необходимо указывать класс — как внутри метода, так и вне его:

C.count = 314

Статические методы тоже возможны:

class C:
    @staticmethod
    def static(arg1, arg2, arg3):
        # No 'self' parameter!
        ...

Однако гораздо проще добиться эффекта статического метода с помощью обычной функции на уровне модуля:

def getcount():
    return C.count

Если код организован так, что в каждом модуле определяется один класс (или тесно связанная иерархия классов), это обеспечивает нужную инкапсуляцию.

Как перегружать конструкторы (или методы) в Python?

На самом деле этот ответ относится ко всем методам, но обычно этот вопрос впервые возникает в контексте конструкторов.

В C++ вы бы написали:

class C {
    C() { cout << "No arguments\n"; }
    C(int i) { cout << "Argument is " << i << "\n"; }
}

В Python нужно написать один конструктор, который обрабатывает все случаи с помощью аргументов по умолчанию. Например:

class C:
    def __init__(self, i=None):
        if i is None:
            print("No arguments")
        else:
            print("Argument is", i)

Это не совсем эквивалентно, но на практике достаточно близко.

Можно также попробовать список аргументов переменной длины, например:

def __init__(self, *args):
    ...

Тот же подход работает для определений любых методов.

Я пытаюсь использовать __spam и получаю ошибку о _SomeClassName__spam.

Имена переменных, начинающиеся с двух подчёркиваний, «искажаются», чтобы обеспечить простой, но эффективный способ определения закрытых переменных класса. Любой идентификатор вида __spam (не менее двух подчёркиваний в начале и не более одного в конце) текстуально заменяется на _classname__spam, где classname — это имя текущего класса без начальных подчёркиваний.

Внутри класса идентификатор можно использовать без изменений, но для доступа к нему извне необходимо использовать искажённое имя:

class A:
    def __one(self):
        return 1
    def two(self):
        return 2 * self.__one()

class B(A):
    def three(self):
        return 3 * self._A__one()

four = 4 * A()._A__one()

В частности, это не гарантирует приватность, поскольку пользователь извне всё ещё может намеренно обратиться к закрытому атрибуту; многие программисты на Python вообще не используют закрытые имена переменных.

См. также

Подробности и особые случаи описаны в спецификации искажения закрытых имён.

В моём классе определён __del__, но он не вызывается при удалении объекта.

Этому есть несколько возможных объяснений.

Оператор del не обязательно вызывает __del__() — он лишь уменьшает счётчик ссылок на объект, и если счётчик достигает нуля, вызывается __del__().

Если ваши структуры данных содержат циклические ссылки (например, дерево, в котором у каждого дочернего узла есть ссылка на родителя, а у каждого родителя — список дочерних узлов), счётчики ссылок никогда не достигнут нуля. Время от времени Python запускает алгоритм обнаружения таких циклов, но сборщик мусора может запуститься через некоторое время после исчезновения последней ссылки на структуру данных. Поэтому ваш метод __del__() может быть вызван в неудобный и непредсказуемый момент. Это неудобно, если вы пытаетесь воспроизвести проблему. Более того, порядок выполнения методов __del__() объектов произволен. Чтобы принудительно запустить сборку мусора, можно вызвать gc.collect(), но существуют патологические случаи, когда объекты никогда не будут собраны.

Несмотря на наличие сборщика циклических ссылок, рекомендуется определять для объектов явный метод close(), который будет вызываться, когда объект больше не нужен. Затем метод close() может удалять атрибуты, ссылающиеся на подчинённые объекты. Не вызывайте __del__() напрямую — __del__() должен вызывать close(), а close() должен обеспечивать возможность повторного вызова для одного и того же объекта.

Ещё один способ избежать циклических ссылок — использовать модуль weakref, позволяющий ссылаться на объекты, не увеличивая их счётчик ссылок. Например, в структурах данных типа «дерево» для ссылок на родительские и соседние узлы (если они нужны) следует использовать слабые ссылки.

Наконец, если ваш метод __del__() вызывает исключение, в sys.stderr выводится предупреждение.

Как получить список всех экземпляров данного класса?

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

Почему результат id() кажется неуникальным?

Встроенная функция id() возвращает целое число, уникальность которого гарантирована на протяжении жизни объекта. Поскольку в CPython это адрес памяти объекта, часто бывает так, что после удаления объекта из памяти следующий созданный объект размещается по тому же адресу. Это демонстрирует следующий пример:

>>> id(1000)
13901272
>>> id(2000)
13901272

Эти два идентификатора принадлежат разным целочисленным объектам, которые создаются до вызова id() и удаляются сразу после его выполнения. Чтобы гарантировать, что объекты, идентификаторы которых вы хотите проверить, всё ещё существуют, создайте на объект ещё одну ссылку:

>>> a = 1000; b = 2000
>>> id(a)
13901272
>>> id(b)
13891296

Когда можно полагаться на проверки идентичности с помощью оператора is?

Оператор is проверяет идентичность объектов. Проверка a is b эквивалентна id(a) == id(b).

Важнейшее свойство проверки идентичности заключается в том, что объект всегда идентичен самому себе: a is a всегда возвращает True. Проверки идентичности обычно выполняются быстрее проверок на равенство. И, в отличие от проверок на равенство, проверки идентичности гарантированно возвращают логическое значение True или False.

Однако проверки идентичности можно использовать вместо проверок на равенство только тогда, когда идентичность объектов гарантирована. Как правило, есть три случая, в которых идентичность гарантирована:

  1. Присваивания создают новые имена, но не меняют идентичность объекта. После присваивания new = old гарантируется, что new is old.
  2. Помещение объекта в контейнер, хранящий ссылки на объекты, не меняет его идентичность. После присваивания списка s[0] = x гарантируется, что s[0] is x.
  3. Если объект является одиночным экземпляром (singleton), это означает, что может существовать только один экземпляр этого объекта. После присваиваний a = None и b = None гарантируется, что a is b, поскольку None является одиночным экземпляром.

В большинстве остальных случаев проверки идентичности не рекомендуются; предпочтительнее проверки на равенство. В частности, не следует использовать проверки идентичности для констант, таких как int и str, которые не гарантированно являются одиночными экземплярами:

>>> a = 10_000_000
>>> b = 5_000_000
>>> c = b + 5_000_000
>>> a is c
False

>>> a = 'Python'
>>> b = 'Py'
>>> c = b + 'thon'
>>> a is c
False

Аналогично, новые экземпляры изменяемых контейнеров никогда не идентичны:

>>> a = []
>>> b = []
>>> a is b
False

В коде стандартной библиотеки встречаются несколько распространённых способов правильного использования проверок идентичности:

  1. Как рекомендовано в PEP 8, проверка идентичности — предпочтительный способ проверить значение None. В коде это читается как обычный английский текст и позволяет избежать путаницы с другими объектами, логическое значение которых может оцениваться как ложное.
  2. Обнаружить необязательные аргументы бывает непросто, если None является допустимым входным значением. В таких ситуациях можно создать специальный одиночный объект-маркер, гарантированно отличающийся от других объектов. Например, вот как реализовать метод, который ведёт себя подобно dict.pop():

    _sentinel = object()
    
    def pop(self, key, default=_sentinel):
        if key in self:
            value = self[key]
            del self[key]
            return value
        if default is _sentinel:
            raise KeyError(key)
        return default
    
  3. Реализациям контейнеров иногда нужно дополнять проверки на равенство проверками идентичности. Это не позволяет коду запутаться при работе с такими объектами, как float('NaN'), которые не равны сами себе.

Например, вот реализация collections.abc.Sequence.__contains__():

def __contains__(self, value):
    for v in self:
        if v is value or v == value:
            return True
    return False

Как подклассу управлять данными, хранящимися в неизменяемом экземпляре?

При наследовании от неизменяемого типа переопределяйте метод __new__(), а не метод __init__(). Последний выполняется только после создания экземпляра, то есть слишком поздно, чтобы изменить данные в неизменяемом экземпляре.

Все эти неизменяемые классы имеют сигнатуру, отличающуюся от сигнатуры родительского класса:

import datetime as dt

class FirstOfMonthDate(dt.date):
    "Always choose the first day of the month"
    def __new__(cls, year, month, day):
        return super().__new__(cls, year, month, 1)

class NamedInt(int):
    "Allow text names for some numbers"
    xlat = {'zero': 0, 'one': 1, 'ten': 10}
    def __new__(cls, value):
        value = cls.xlat.get(value, value)
        return super().__new__(cls, value)

class TitleStr(str):
    "Convert str to name suitable for a URL path"
    def __new__(cls, s):
        s = s.lower().replace(' ', '-')
        s = ''.join([c for c in s if c.isalnum() or c == '-'])
        return super().__new__(cls, s)

Классы можно использовать следующим образом:

>>> FirstOfMonthDate(2012, 2, 14)
FirstOfMonthDate(2012, 2, 1)
>>> NamedInt('ten')
10
>>> NamedInt(20)
20
>>> TitleStr('Blog: Why Python Rocks')
'blog-why-python-rocks'

Как кэшировать вызовы методов?

Два основных инструмента для кэширования методов — @functools.cached_property и @functools.lru_cache. Первый хранит результаты на уровне экземпляра, а второй — на уровне класса.

Подход cached_property работает только с методами, не принимающими аргументов. Он не создаёт ссылку на экземпляр. Результат кэшированного метода хранится только до тех пор, пока существует экземпляр.

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

Подход lru_cache работает с методами, принимающими хешируемые аргументы. Он создаёт ссылку на экземпляр, если не приложить специальных усилий для передачи слабых ссылок.

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

В этом примере показаны различные методы:

class Weather:
    "Lookup weather information on a government website"

    def __init__(self, station_id):
        self._station_id = station_id
        # The _station_id is private and immutable

    def current_temperature(self):
        "Latest hourly observation"
        # Do not cache this because old results
        # can be out of date.

    @cached_property
    def location(self):
        "Return the longitude/latitude coordinates of the station"
        # Result only depends on the station_id

    @lru_cache(maxsize=20)
    def historic_rainfall(self, date, units='mm'):
        "Rainfall on a given date"
        # Depends on the station_id, date, and units.

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

Чтобы подход lru_cache работал при изменяемом значении station_id, класс должен определять методы __eq__() и __hash__(), чтобы кэш мог обнаруживать изменения соответствующих атрибутов:

class Weather:
    "Example with a mutable station identifier"

    def __init__(self, station_id):
        self.station_id = station_id

    def change_station(self, station_id):
        self.station_id = station_id

    def __eq__(self, other):
        return self.station_id == other.station_id

    def __hash__(self):
        return hash(self.station_id)

    @lru_cache(maxsize=20)
    def historic_rainfall(self, date, units='cm'):
        'Rainfall on a given date'
        # Depends on the station_id, date, and units.

Модули

Как создать файл .pyc?

При первом импорте модуля (или если исходный файл изменился с момента создания текущего скомпилированного файла) в подкаталоге __pycache__ каталога, содержащего файл .py, должен создаваться файл .pyc со скомпилированным кодом. Имя файла .pyc начинается с того же имени, что и у файла .py, и заканчивается на .pyc; его средняя часть зависит от конкретного двоичного файла python, который его создал. (Подробности см. в PEP 3147.)

Одна из причин, по которой файл .pyc может не создаваться, — проблема с разрешениями для каталога, содержащего исходный файл, из-за которой невозможно создать подкаталог __pycache__. Такое может произойти, например, если вы разрабатываете программу от имени одного пользователя, а запускаете от имени другого, например при тестировании с веб-сервером.

Если не установлена переменная среды PYTHONDONTWRITEBYTECODE, файл .pyc создаётся автоматически при импорте модуля, если у Python есть возможность (разрешения, свободное место и т. п.) создать подкаталог __pycache__ и записать в него скомпилированный модуль.

Запуск Python для выполнения скрипта верхнего уровня не считается импортом, поэтому файл .pyc не создаётся. Например, если у вас есть модуль верхнего уровня foo.py, который импортирует другой модуль xyz.py, то при запуске foo (вводом python foo.py в командной оболочке) для xyz будет создан файл .pyc, поскольку xyz импортируется. Однако файл .pyc для foo создан не будет, поскольку foo.py не импортируется.

Если вам нужно создать файл .pyc для foo — то есть файл .pyc для модуля, который не импортируется, — это можно сделать с помощью модулей py_compile и compileall.

Модуль py_compile позволяет вручную скомпилировать любой модуль. Например, можно интерактивно использовать функцию compile() из этого модуля:

>>> import py_compile
>>> py_compile.compile('foo.py')

Файл .pyc будет записан в подкаталог __pycache__ в том же месте, что и foo.py (или можно указать другое место с помощью необязательного параметра cfile).

Также можно автоматически скомпилировать все файлы в одном или нескольких каталогах с помощью модуля compileall. Для этого запустите compileall.py из командной строки и укажите путь к каталогу с файлами Python, которые нужно скомпилировать:

python -m compileall .

Как узнать имя текущего модуля?

Модуль может узнать собственное имя, обратившись к предопределённой глобальной переменной __name__. Если её значение равно '__main__', программа выполняется как скрипт. Многие модули, которые обычно импортируются, также предоставляют интерфейс командной строки или самопроверку и выполняют соответствующий код, только предварительно проверив __name__:

def main():
    print('Running test...')
    ...

if __name__ == '__main__':
    main()

Как сделать так, чтобы модули импортировали друг друга?

Предположим, у вас есть следующие модули:

foo.py:

from bar import bar_var
foo_var = 1

bar.py:

from foo import foo_var
bar_var = 2

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

  • main импортирует foo
  • Создаются пустые глобальные переменные для foo
  • Выполняется компиляция foo и начинается его выполнение
  • foo импортирует bar
  • Создаются пустые глобальные переменные для bar
  • Выполняется компиляция bar и начинается его выполнение
  • bar импортирует foo (ничего не происходит, поскольку модуль с именем foo уже существует)
  • Механизм импорта пытается прочитать foo_var из глобальных переменных foo, чтобы присвоить bar.foo_var = foo.foo_var

Последний шаг завершается неудачно, потому что Python ещё не закончил интерпретировать foo, а глобальный словарь символов для foo всё ещё пуст.

То же самое происходит при использовании import foo, если затем попытаться обратиться к foo.foo_var в глобальном коде.

Эту проблему можно решить как минимум тремя способами.

Гвидо ван Россум рекомендует избегать любого использования from <module> import ... и помещать весь код внутрь функций. При инициализации глобальных переменных и переменных классов следует использовать только константы или встроенные функции. Это означает, что на всё из импортированного модуля нужно ссылаться как на <module>.<name>.

Джим Роскинд предлагает выполнять действия в каждом модуле в следующем порядке:

  • экспортируемые объекты (глобальные переменные, функции и классы, которым не нужны импортированные базовые классы)
  • операторы import
  • активный код (включая глобальные переменные, инициализируемые импортированными значениями).

Ван Россуму этот подход не очень нравится, поскольку операторы импорта оказываются в необычном месте, но он работает.

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

Эти решения не исключают друг друга.

__import__(‘x.y.z’) возвращает <module ‘x’>; как получить z?

Вместо этого попробуйте использовать вспомогательную функцию import_module() из модуля importlib:

z = importlib.import_module('x.y.z')

Я изменил импортированный модуль и импортировал его снова, но изменения не появились. Почему?

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

import importlib
import modname
importlib.reload(modname)

Предупреждение: этот приём не даёт стопроцентной гарантии. В частности, модули, содержащие операторы вида:

from modname import some_objects

продолжат работать со старой версией импортированных объектов. Если модуль содержит определения классов, существующие экземпляры классов не будут обновлены для использования нового определения класса. Это может привести к следующему парадоксальному поведению:

>>> import importlib
>>> import cls
>>> c = cls.C()                # Create an instance of C
>>> importlib.reload(cls)
<module 'cls' from 'cls.py'>
>>> isinstance(c, cls.C)       # isinstance is false?!?
False

Суть проблемы становится очевидной, если вывести «идентичность» объектов-классов:

>>> hex(id(c.__class__))
'0x7352a0'
>>> hex(id(cls.C))
'0x4198d0'

© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/faq/programming.html

Spec-Zone.ru

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