Spec-Zone.ru › Python 3.10

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

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

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

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

    • Как указать шестнадцатеричные и восьмеричные целые числа?
    • Почему -22 // 10 возвращает -3?
    • Как получить атрибут целочисленной литералы вместо SyntaxError?
    • Как преобразовать строку в число?
    • Как преобразовать число в строку?
    • Как изменить строку на месте?
    • Как использовать строки для вызова функций/методов?
    • Есть ли аналог Perl’s 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 — это среда разработки, построенная на 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)

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

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

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

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

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

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

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

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

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

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

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

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

Этот тип ошибки часто ловит неопытных программистов. Рассмотрим эту функцию:

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

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

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

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

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

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

а:

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

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

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

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

Можно использовать глобальную переменную, содержащую словарь, вместо значения по умолчанию; это дело вкуса.

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

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

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

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

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

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

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

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

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

Почему изменение списка ‘y’ также изменило список ‘x’?

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

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

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

Два фактора приводят к этому результату:

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

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

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

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

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

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

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

Другими словами:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Или используя вызываемый объект:

class linear:

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

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

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

taxes = linear(0.3, 2)

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

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

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

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

class counter:

    value = 0

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

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

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

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

Здесь inc(), dec() и reset() ведут себя как функции, которые делят одну и ту же переменную подсчета.

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

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

Некоторые объекты могут быть скопированы проще. Словари имеют метод copy():

newdict = olddict.copy()

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

new_l = l[:]

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

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

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

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

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

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

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

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

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

…и не удивляйтесь, если вы найдете, что её знают под многими именами или вообще не знают!

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

Запятая не является оператором в Python. Рассмотрим эту сессию:

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

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

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

а не:

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

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

Существует ли эквивалент тройного оператора C «?:»?

Да, есть. Синтаксис следующий:

[on_true] if [expression] else [on_false]

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

До того, как этот синтаксис был введен в Python 2.5, распространённым приёмом было использование логических операторов:

[expression] and [on_true] or [on_false]

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

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

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

from functools import reduce

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

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

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

Не пробуйте это дома, дети!

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

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

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

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

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

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

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

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

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

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

Шестнадцатеричные числа задаются также легко. Просто добавьте ноль перед шестнадцатеричным числом, а затем строчную или прописную букву «х». Шестнадцатеричные цифры могут быть указаны в строчном или прописном регистре. Например, в интерпретаторе 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(). Для красивого форматирования см. разделы Литералы форматированных строк и Синтаксис строк форматирования, например, "{:04d}".format(144) возвращает '0144' и "{:.3f}".format(1.0/3.0) возвращает '0.333'.

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

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

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

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

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

Существует несколько методов.

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

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

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

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

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

    class Foo:
        def do_foo(self):
            ...
    
        def do_bar(self):
            ...
    
    f = getattr(foo_instance, 'do_' + opname)
    f()
    
  • Используйте locals() для разрешения имени функции:

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

Есть ли эквивалент Perl chomp() для удаления конечных символов новой строки из строк?

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

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

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

Есть ли эквивалент scanf() или sscanf()?

Нет, такого нет.

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

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

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

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

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

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

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

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

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

Другой — конкатенировать обычную строку, содержащую экранированный обратный слеш, со строкой-сырцом:

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

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

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

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

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

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

END_OF_DOCUMENT_MARKER

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

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

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

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

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

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

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

См. также

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

Каким образом эффективно конкатенировать большое количество строк?

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

Для накопления многих str объектов рекомендуемый способ — поместить их в список и вызвать str.join() в конце:

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

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

Для накопления многих bytes объектов рекомендуемый способ — расширить bytearray объект с помощью конкатенации на месте (оператор +=):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

См. Python Cookbook для подробного обсуждения многих способов сделать это:

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

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

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

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

mylist = list(set(mylist))

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

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

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

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

Список из генерации может быть самым быстрым.

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

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

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

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

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

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

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

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

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

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

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

Это выглядит правильно, если вы его выведете:

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

Но при присваивании значения оно появляется во множестве мест:

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

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

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

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

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

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

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

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

Используйте генератор списков:

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

Почему 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):
    ...

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

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

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

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-программисты вообще не утруждают себя использованием имен закрытых переменных.

END_OF_DOCUMENT_MARKER

Мой класс определяет __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 работает с методами, имеющими хешируемые аргументы. Он создаёт ссылку на экземпляр, если не предпринимаются специальные усилия для передачи слабых ссылок.

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

Модули

Как создать файл .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’) возвращает <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–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/faq/programming.html

Spec-Zone.ru

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