Spec-Zone.ru › Python 3.11

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

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

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

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

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

Да.

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

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

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

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

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

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

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

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

Он работает, рекурсивно сканируя ваш исходный код на наличие операторов импорта (в обоих форматах) и ищет модули в стандартном пути 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, мы не изменяем int 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. Посмотрите на следующие три примера, слегка адаптированные из Ulf Bartelt:

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 — это ошибка, которая может подстерегать.

Как получить атрибут целочисленной литералы вместо 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’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.

Можно ли закончить строку-сырец нечётным числом обратных слэшей?

Строка-сырец, заканчивающаяся нечётным числом обратных слэшей, экранирует кавычки строки:

>>> 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\\'

Также можно использовать os.path.join() для добавления обратного слэша в Windows:

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

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

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

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

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

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

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

  • Характеристики производительности различаются в разных реализациях Python. Этот раздел FAQ посвящен 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) ) )

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

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

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

for obj in mylist:
    obj.method()

for obj in mylist:
    function(obj)

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

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

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

Если вы написали:

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

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

Внутренне это утверждение расширенного присваивания примерно выполняется так:

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

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

Когда вы пишете что-то вроде:

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

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

>>> 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']

Объекты

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

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

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

Имена переменных с двумя ведущими подчёркиваниями «заменяются» (mangle) для обеспечения простого и эффективного способа определения приватных переменных класса. Любой идентификатор вида __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. Это читается как обычный английский текст в коде и избегает путаницы с другими объектами, которые могут иметь значения булевых переменных, оцениваемых как 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'

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

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

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

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

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

Преимущество алгоритма «наименее часто используемый» заключается в том, что кэш ограничен указанным 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?

Когда модуль импортируется в первый раз (или когда исходный файл изменился с момента создания текущего скомпилированного файла), файл .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)
  • Механизм импорта пытается прочитать 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’) возвращает <модуль ‘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–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.11/faq/programming.html

Spec-Zone.ru

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