Spec-Zone.ru › Python 3.12

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

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

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

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

    • Как задать целые числа в шестнадцатеричной и восьмеричной системах?
    • Почему -22 // 10 возвращает -3?
    • Как получить атрибут литерала int вместо SyntaxError?
    • Как преобразовать строку в число?
    • Как преобразовать число в строку?
    • Как изменить строку на месте?
    • Как использовать строки для вызова функций и методов?
    • Есть ли аналог Perl-функции chomp() для удаления завершающих символов новой строки?
    • Есть ли аналог 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 (обычно доступная по адресу Tools/scripts/idle3), включает графический отладчик.

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

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

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

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

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

  • Wing IDE
  • Komodo IDE
  • PyCharm

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

Да.

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

Средства статической проверки типов, такие как Mypy, Pyre и Pytype, могут проверять подсказки типов в исходном коде Python.

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

Вам не нужно уметь компилировать Python в код C, если вам нужна лишь автономная программа, которую пользователи могут скачать и запустить без предварительной установки дистрибутива Python. Существует ряд инструментов, которые определяют набор модулей, необходимых программе, и объединяют эти модули с бинарным файлом 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: local variable 'x' referenced before assignment

Это происходит потому, что при присваивании значения переменной в области видимости она становится локальной для этой области и затеняет переменную с таким же именем во внешней области. Поскольку последний оператор в 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, PIL.Image
  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")

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

Есть ли в Python аналог тернарного оператора «?:» из 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('u', s)
>>> print(a)
array('u', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('u', 'yello, world')
>>> a.tounicode()
'yello, world'

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

Для этого есть несколько способов.

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

    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()
    

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

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

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

Что означает ошибка «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 создаёт список из трёх ссылок на один и тот же список длины два. Изменения в одной строке будут видны во всех строках, а это почти наверняка не то, что вам нужно.

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

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

Так будет создан список из трёх разных списков длины два. Можно также использовать списковое включение:

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

Причина исключения должна быть сразу понятна: к объекту a_tuple[0], на который указывает 1 (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.

Имена переменных с двумя начальными подчёркиваниями подвергаются «искажению» (mangling), что позволяет простым, но эффективным способом определять закрытые переменные класса. Любой идентификатор вида __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) Если объект является синглтоном, это означает, что может существовать только один его экземпляр. После присваиваний a = None и b = None гарантируется, что a is b, поскольку None является синглтоном.

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

>>> a = 1000
>>> b = 500
>>> c = b + 500
>>> 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__(). Последний выполняется только после создания экземпляра, когда изменять данные в неизменяемом экземпляре уже слишком поздно.

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

from datetime import date

class FirstOfMonthDate(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 в командной оболочке) файл .pyc будет создан для xyz, поскольку импортируется 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–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/faq/programming.html

Spec-Zone.ru

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