Spec-Zone.ru › Python 3.9

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

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

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

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

    • Как указать шестнадцатеричные и восьмеричные целые числа?
    • Почему -22 // 10 возвращает -3?
    • Как получить атрибут целочисленной литералы вместо 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?
    • При редактировании импортированного модуля и повторном импорте изменения не отображаются. Почему это происходит?
END_OF_DOCUMENT_MARKER

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

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

Да.

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

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

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

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

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

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

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

Существует ряд коммерческих сред разработки 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.

Он работает, рекурсивно просматривая ваш исходный код в поисках инструкций импорта (в обоих форматах) и ищет модули в стандартном пути 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)

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

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

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

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

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

  1. модули стандартной библиотеки — например, sys, os, getopt, re
  2. модули сторонних библиотек (любые, установленные в каталоге site-packages Python) — например, mx.DateTime, ZODB, 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")

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

Существует ли эквивалент тернарного оператора 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+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 — это ошибка, которая ждёт своего часа.

Как получить атрибут целочисленной литералы вместо 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(). Для форматирования с изысканными опциями обратитесь к разделам Форматированные строковые литералы и Синтаксис форматирования строк, например, "{: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’s 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», который полезен, если строка использует что-то отличное от пробела в качестве разделителя.

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

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

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

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

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

Это сложный вопрос в общем случае. Сначала вот список вещей, которые следует помнить перед дальнейшим погружением:

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

При этом существуют много трюков для ускорения кода 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) ) )

Если требуется изменяемость, вы можете использовать списки вместо кортежей. Здесь аналог lisp car — 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]

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

Я хочу выполнить сложное сортирование: можно ли выполнить преобразование Schwartziana в 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']

Объекты

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

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

Класс может основываться на одном или нескольких других классах, называемых базовыми классами. В таком случае он наследует атрибуты и методы своих базовых классов. Это позволяет последовательно уточнять модель объекта посредством наследования. Возможно, у вас есть обобщённый 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__() должны изменить self.__dict__ для хранения локального состояния для self, не вызывая бесконечную рекурсию.

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

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

class Derived(Base):
    def meth(self):
        super(Derived, self).meth()

Для версий до 3.0 вы можете использовать классы классического стиля: для определения класса, такого как class Derived(Base): ..., вы можете вызвать метод meth(), определённый в Base (или в одном из базовых классов Base), как Base.meth(self, arguments...). Здесь Base.meth — это несвязанный метод, поэтому вам нужно указать аргумент self.

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

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

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 создаёт новый и не связанный экземпляр, названный «count», в собственном словаре self. Переназначение имени статических данных класса всегда должно указывать класс, как внутри метода, так и вне него:

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 — это текущее имя класса с удалёнными ведущими подчёркиваниями.

Это не гарантирует конфиденциальность: внешний пользователь по-прежнему может намеренно получить доступ к атрибуту «_classname__spam», и закрытые значения видны в __dict__ объекта. Многие 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. Это читается как обычный английский язык в коде и избегает путаницы с другими объектами, которые могут иметь значения boolean, оцениваемые как false.

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'

Модули

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

Когда модуль импортируется в первый раз (или когда исходный файл изменился с момента создания текущего скомпилированного файла), файл .pyc с содержащим скомпилированный код должен быть создан в подкаталоге __pycache__ каталога, содержащего файл .py. Файл .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)
  • 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)

Предупреждение: этот метод не является на 100% надежным. В частности, модули, содержащие операторы, такие как

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–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/faq/programming.html

Spec-Zone.ru

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