Часто задаваемые вопросы по программированию
-
- Существует ли отладчик на уровне исходного кода с точками останова, пошаговым выполнением и т. д.?
- Существуют ли инструменты для поиска ошибок или статического анализа?
- Как создать автономный двоичный файл из скрипта Python?
- Существуют ли стандарты кодирования или руководство по стилю для программ на Python?
-
- Почему возникает UnboundLocalError, если переменная имеет значение?
- Каковы правила использования локальных и глобальных переменных в Python?
- Почему лямбда-функции, определённые в цикле с разными значениями, возвращают один и тот же результат?
- Как использовать глобальные переменные в нескольких модулях?
- Каковы «лучшие практики» использования import в модуле?
- Почему значения по умолчанию используются совместно разными объектами?
- Как передать необязательные или именованные параметры из одной функции в другую?
- В чём разница между аргументами и параметрами?
- Почему изменение списка ‘y’ также изменило список ‘x’?
- Как написать функцию с выходными параметрами (передача по ссылке)?
- Как создать функцию высшего порядка в Python?
- Как скопировать объект в Python?
- Как узнать методы или атрибуты объекта?
- Как узнать имя объекта в коде?
- Как работает приоритет оператора запятой?
- Есть ли аналог тернарного оператора C «?:»?
- Можно ли писать в Python запутанные однострочные выражения?
- Что означает косая черта (/) в списке параметров функции?
-
- Как задать шестнадцатеричные и восьмеричные целые числа?
- Почему -22 // 10 возвращает -3?
- Как получить атрибут литерала int вместо SyntaxError?
- Как преобразовать строку в число?
- Как преобразовать число в строку?
- Как изменить строку на месте?
- Как вызывать функции/методы с помощью строк?
- Есть ли аналог
chomp()из Perl для удаления завершающих символов новой строки? - Есть ли аналог
scanf()илиsscanf()? - Что означает ошибка
UnicodeDecodeErrorилиUnicodeEncodeError? - Можно ли завершить необработанную строку нечётным числом обратных косых черт?
-
Последовательности (кортежи/списки)
- Как преобразовать кортеж в список и наоборот?
- Что такое отрицательный индекс?
- Как перебрать элементы последовательности в обратном порядке?
- Как удалить дубликаты из списка?
- Как удалить несколько элементов из списка
- Как создать массив в Python?
- Как создать многомерный список?
- Как применить метод или функцию к последовательности объектов?
- Почему a_tuple[i] += [‘item’] вызывает исключение, хотя сложение выполняется?
- Мне нужна сложная сортировка: можно ли использовать в Python преобразование Шварца?
- Как отсортировать один список по значениям из другого списка?
-
- Что такое класс?
- Что такое метод?
- Что такое self?
- Как проверить, является ли объект экземпляром заданного класса или его подкласса?
- Что такое делегирование?
- Как вызвать метод базового класса из производного класса, который его расширяет?
- Как организовать код, чтобы упростить изменение базового класса?
- Как создать статические данные класса и статические методы класса?
- Как перегрузить конструкторы (или методы) в Python?
- Я пытаюсь использовать __spam и получаю ошибку, в которой упоминается _SomeClassName__spam.
- В моём классе определён __del__, но он не вызывается при удалении объекта.
- Как получить список всех экземпляров заданного класса?
- Почему результат
id()кажется неуникальным? - Когда можно полагаться на проверку идентичности с помощью оператора is?
- Как подкласс может управлять тем, какие данные хранятся в неизменяемом экземпляре?
- Как кэшировать вызовы методов?
Общие вопросы
Существует ли отладчик на уровне исходного кода с точками останова, пошаговым выполнением и т. д.?
Да.
Ниже описаны несколько отладчиков для Python, а встроенная функция breakpoint() позволяет перейти в любой из них.
Модуль pdb — это простой, но достаточно функциональный консольный отладчик для Python. Он входит в стандартную библиотеку Python и documented in the Library
Reference Manual. Вы также можете написать собственный отладчик, взяв код pdb за образец.
Интерактивная среда разработки IDLE, входящая в стандартный дистрибутив Python (обычно доступна как Tools/scripts/idle3), содержит графический отладчик.
PythonWin — это IDE для Python со встроенным графическим отладчиком на основе pdb. Отладчик PythonWin выделяет точки останова цветом и обладает рядом полезных возможностей, например позволяет отлаживать программы, не относящиеся к PythonWin. PythonWin доступен в составе проекта pywin32 и дистрибутива ActivePython.
Eric — это IDE на основе PyQt и компонента редактирования Scintilla.
trepan3k — отладчик, похожий на gdb.
Visual Studio Code — это IDE с инструментами отладки и интеграцией с системами контроля версий.
Существует несколько коммерческих IDE для Python с графическими отладчиками. Среди них:
Существуют ли инструменты для поиска ошибок или статического анализа?
Да.
Pylint и Pyflakes выполняют базовые проверки, которые помогут раньше обнаружить ошибки.
Средства статической проверки типов, такие как Mypy, Pyre и Pytype, могут проверять аннотации типов в исходном коде Python.
Как создать автономный двоичный файл из скрипта Python?
Если вам нужна только автономная программа, которую пользователи смогут скачать и запустить без предварительной установки дистрибутива Python, компилировать Python в код C необязательно. Существует ряд инструментов, которые определяют набор модулей, необходимых программе, и объединяют эти модули с двоичным файлом Python, создавая один исполняемый файл.
Один из вариантов — использовать инструмент freeze, включённый в дерево исходного кода Python как Tools/freeze. Он преобразует байт-код Python в массивы C; с помощью компилятора C можно встроить все модули в новую программу, которая затем связывается со стандартными модулями Python.
Инструмент рекурсивно просматривает исходный код в поисках операторов import (в обеих формах) и ищет модули в стандартном пути Python, а также в каталоге исходного кода (для встроенных модулей). Затем он преобразует байт-код модулей, написанных на Python, в код C (инициализаторы массивов, которые можно преобразовать в объекты кода с помощью модуля marshal) и создаёт специальный файл конфигурации, содержащий только те встроенные модули, которые действительно используются программой. После этого он компилирует сгенерированный код C и связывает его с остальной частью интерпретатора Python, создавая автономный двоичный файл, который работает точно так же, как ваш скрипт.
Следующие пакеты могут помочь создать консольные приложения и исполняемые файлы с графическим интерфейсом:
- Nuitka (кроссплатформенный)
- PyInstaller (кроссплатформенный)
- PyOxidizer (кроссплатформенный)
- cx_Freeze (кроссплатформенный)
- py2app (только macOS)
- py2exe (только Windows)
Существуют ли стандарты кодирования или руководство по стилю для программ на Python?
Да. Стиль кодирования, обязательный для модулей стандартной библиотеки, описан в документе PEP 8.
Основной язык
Почему возникает UnboundLocalError, если переменная имеет значение?
Может оказаться неожиданностью, если в ранее работавшем коде возникает UnboundLocalError после добавления оператора присваивания в тело функции.
Этот код:
>>> x = 10 >>> def bar(): ... print(x) ... >>> bar() 10
работает, а этот код:
>>> x = 10 >>> def foo(): ... print(x) ... x += 1
приводит к UnboundLocalError:
>>> foo() Traceback (most recent call last): ... UnboundLocalError: 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
Обратите внимание: такое поведение свойственно не только лямбда-функциям, но и обычным функциям.
Каковы «лучшие практики» использования import в модуле?
В общем случае не используйте from modulename import *. Это загромождает пространство имён импортирующего модуля и значительно затрудняет линтерам поиск неопределённых имён.
Импортируйте модули в начале файла. Так будет понятно, от каких других модулей зависит ваш код, и не возникнет вопросов о том, находится ли имя модуля в области видимости. Если указывать каждый импорт на отдельной строке, модули будет проще добавлять и удалять, а несколько импортов в одной строке экономят место на экране.
Рекомендуется импортировать модули в следующем порядке:
- модули стандартной библиотеки — например,
sys,os,argparse,re - модули сторонних библиотек (всё, что установлено в каталоге site-packages Python), например
dateutil,requests,PIL.Image - модули, разработанные локально
Иногда импорты приходится перемещать в функцию или класс, чтобы избежать проблем с циклическими импортами. Гордон Макмиллан пишет:
Циклические импорты допустимы, если оба модуля используют форму импорта «import <module>». Они приводят к сбою, когда второй модуль пытается получить имя из первого («from module import name»), а импорт находится на верхнем уровне. Это происходит потому, что имена первого модуля ещё недоступны: он занят импортом второго.
В таком случае, если второй модуль используется только в одной функции, импорт можно без труда переместить в эту функцию. К моменту вызова импорта первый модуль уже завершит инициализацию, и второй модуль сможет его импортировать.
Импорты также может потребоваться переместить с верхнего уровня кода, если некоторые модули предназначены для конкретных платформ. В этом случае может быть невозможно импортировать все модули в начале файла. Хорошим решением будет импортировать нужные модули в соответствующем коде для конкретной платформы.
Перемещайте импорты в локальную область видимости, например внутрь определения функции, только если это необходимо для решения проблемы — например, чтобы избежать циклического импорта или сократить время инициализации модуля. Этот приём особенно полезен, если необходимость во многих импортируемых модулях зависит от выполнения программы. Импорты также можно переместить внутрь функции, если модули используются только в ней. Учтите, что первая загрузка модуля может быть затратной из-за его однократной инициализации, но повторная загрузка практически ничего не стоит и требует лишь пары поисков в словарях. Даже если имя модуля вышло из области видимости, сам модуль, вероятно, доступен в sys.modules.
Как передать необязательные параметры или параметры-ключевые слова из одной функции в другую?
Соберите аргументы с помощью спецификаторов * и ** в списке параметров функции; так вы получите позиционные аргументы в виде кортежа, а аргументы-ключевые слова — в виде словаря. Затем эти аргументы можно передать при вызове другой функции, используя * и **:
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.
К такому результату приводят два фактора:
- Переменные — это просто имена, ссылающиеся на объекты. Операция
y = xне создаёт копию списка: она создаёт новую переменнуюy, которая ссылается на тот же объект, что иx. Это означает, что объект (список) только один и на него ссылаются обе переменные —xиy. - Списки являются изменяемыми, то есть их содержимое можно менять.
После вызова 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 аргументы передаются посредством присваивания. Поскольку присваивание лишь создаёт ссылки на объекты, между именем аргумента в вызывающем коде и именем параметра в вызываемой функции нет псевдонима, а значит, нет и передачи по ссылке в собственном смысле. Желаемого эффекта можно добиться несколькими способами.
-
Возвращая кортеж результатов:
>>> 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)Почти всегда это самое понятное решение.
- Используя глобальные переменные. Это небезопасно при многопоточном выполнении и не рекомендуется.
-
Передавая изменяемый объект (который можно менять на месте):
>>> 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]
-
Передавая словарь, содержимое которого изменяется:
>>> 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} -
Или объединяя значения в экземпляре класса:
>>> class Namespace: ... def __init__(self, /, **args): ... for key, value in args.items(): ... setattr(self, key, value) ... >>> def func4(args): ... args.a = 'new-value' # args is a mutable Namespace ... args.b = args.b + 1 # change object in-place ... >>> args = Namespace(a='old-value', b=99) >>> func4(args) >>> vars(args) {'a': 'new-value', 'b': 100}Почти никогда нет веских причин так усложнять код.
Лучше всего вернуть кортеж с несколькими результатами.
Как создать функцию высшего порядка в Python?
Есть два варианта: использовать вложенные области видимости или вызываемые объекты. Например, предположим, что вы хотите определить linear(a,b), возвращающую функцию f(x), которая вычисляет значение a*x+b. С использованием вложенных областей видимости:
def linear(a, b):
def result(x):
return a * x + b
return result
Или с использованием вызываемого объекта:
class linear:
def __init__(self, a, b):
self.a, self.b = a, b
def __call__(self, x):
return self.a * x + self.b
В обоих случаях
taxes = linear(0.3, 2)
возвращает вызываемый объект, для которого taxes(10e6) == 0.3 * 10e6 + 2.
Недостаток подхода с вызываемым объектом заключается в том, что он немного медленнее и приводит к несколько более длинному коду. Однако учтите, что коллекция вызываемых объектов может наследовать общую сигнатуру:
class exponential(linear):
# __init__ inherited
def __call__(self, x):
return self.a * (x ** self.b)
Объект может инкапсулировать состояние для нескольких методов:
class counter:
value = 0
def set(self, x):
self.value = x
def up(self):
self.value = self.value + 1
def down(self):
self.value = self.value - 1
count = counter()
inc, dec, reset = count.up, count.down, count.set
Здесь inc(), dec() и reset() действуют как функции, использующие одну и ту же переменную-счётчик.
Как скопировать объект в Python?
В общем случае попробуйте использовать copy.copy() или copy.deepcopy(). Не все объекты можно скопировать, но большинство — можно.
Некоторые объекты копируются проще. У словарей есть метод copy():
newdict = olddict.copy()
Последовательности можно скопировать с помощью среза:
new_l = l[:]
Как узнать методы или атрибуты объекта?
Для экземпляра x класса, определённого пользователем, функция dir(x) возвращает отсортированный по алфавиту список имён атрибутов и методов экземпляра, а также атрибутов, определённых его классом.
Как коду определить имя объекта?
Как правило, никак, поскольку у объектов на самом деле нет имён. По сути, присваивание всегда связывает имя со значением; то же относится к операторам def и class, но в этих случаях значением является вызываемый объект. Рассмотрим следующий код:
>>> class A: ... pass ... >>> B = A >>> a = B() >>> b = a >>> print(b) <__main__.A object at 0x16D07CC> >>> print(a) <__main__.A object at 0x16D07CC>
Можно утверждать, что у класса есть имя: хотя он связан с двумя именами и вызывается через имя B, созданный экземпляр по-прежнему считается экземпляром класса A. Однако невозможно сказать, называется ли экземпляр a или b, поскольку оба имени связаны с одним и тем же значением.
Как правило, коду не нужно «знать имена» конкретных значений. Если только вы намеренно не пишете программы для самоанализа, обычно это означает, что стоит выбрать другой подход.
В comp.lang.python Фредрик Лунд однажды дал отличный ответ на этот вопрос, используя аналогию:
Так же, как вы узнаёте имя кота, которого нашли у себя на крыльце: сам кот (объект) не может назвать вам своё имя, да ему это и не важно. Поэтому узнать, как его зовут, можно, только спросив всех соседей (пространства имён), не их ли это кот (объект)…
…и не удивляйтесь, если окажется, что его знают под многими именами или вообще без имени!
В чём дело с приоритетом оператора запятой?
В Python запятая не является оператором. Рассмотрим этот сеанс:
>>> "a" in "b", "a" (False, 'a')
Поскольку запятая — это не оператор, а разделитель выражений, приведённое выше вычисляется так, как если бы вы ввели:
("a" in "b"), "a"
а не:
"a" in ("b", "a")
То же относится к различным операторам присваивания (=, += и т. д.). На самом деле это не операторы, а синтаксические разделители в операторах присваивания.
Есть ли в Python аналог тернарного оператора C «?:»?
Да, есть. Синтаксис выглядит так:
[on_true] if [expression] else [on_false] x, y = 50, 25 small = x if x < y else y
До появления этого синтаксиса в Python 2.5 часто использовали логические операторы:
[expression] and [on_true] or [on_false]
Однако этот приём небезопасен: он может давать неправильные результаты, если on_true имеет ложное логическое значение. Поэтому всегда лучше использовать форму ... if ... else ....
Можно ли писать в Python запутанные однострочные выражения?
Да. Обычно для этого вкладывают lambda в lambda. Вот три примера, немного адаптированных из работы Ульфа Бартельта:
from functools import reduce # Primes < 1000 print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0, map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000))))) # First 10 Fibonacci numbers print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1: f(x,f), range(10)))) # Mandelbrot set print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+'\n'+y,map(lambda y, Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM, Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro, i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y >=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr( 64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy ))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24)) # \___ ___/ \___ ___/ | | |__ lines on screen # V V | |______ columns on screen # | | |__________ maximum of "iterations" # | |_________________ range on y axis # |____________________________ range on x axis
Дети, не повторяйте это дома!
Что означает косая черта (/) в списке параметров функции?
Косая черта в списке аргументов функции означает, что параметры перед ней являются только позиционными. У таких параметров нет имени, доступного извне. При вызове функции, принимающей только позиционные параметры, аргументы сопоставляются с параметрами исключительно по их позиции. Например, divmod() принимает только позиционные параметры. Её документация выглядит так:
>>> help(divmod)
Help on built-in function divmod in module builtins:
divmod(x, y, /)
Return the tuple (x//y, x%y). Invariant: div*y + mod == x.
Косая черта в конце списка параметров означает, что оба параметра являются только позиционными. Поэтому вызов divmod() с аргументами-ключевыми словами приведёт к ошибке:
>>> divmod(x=3, y=4) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: divmod() takes no keyword arguments
Числа и строки
Как задать шестнадцатеричные и восьмеричные целые числа?
Чтобы задать восьмеричное число, поставьте перед ним ноль, а затем строчную или заглавную букву «o». Например, чтобы присвоить переменной «a» восьмеричное значение «10» (8 в десятичной системе счисления), введите:
>>> a = 0o10 >>> a 8
Шестнадцатеричные числа задаются так же просто. Поставьте перед шестнадцатеричным числом ноль, а затем строчную или заглавную букву «x». Шестнадцатеричные цифры можно записывать строчными или заглавными буквами. Например, в интерпретаторе Python:
>>> a = 0xa5 >>> a 165 >>> b = 0XB2 >>> b 178
Почему -22 // 10 возвращает -3?
В первую очередь это обусловлено желанием обеспечить, чтобы i % j имело тот же знак, что и j. Если это необходимо, а также требуется, чтобы выполнялось условие:
i == (i // j) * j + (i % j)
тогда целочисленное деление должно возвращать округление вниз. В C также требуется, чтобы это тождество выполнялось, а компиляторы, которые усекают i // j, должны обеспечивать, чтобы i % j имело тот же знак, что и i.
Есть мало реальных сценариев использования i % j, когда j отрицательно. Когда j положительно, таких сценариев много, и почти во всех полезнее, чтобы i % j было равно >= 0. Если сейчас часы показывают 10, сколько они показывали 200 часов назад? -190 % 12 == 2 — полезный результат; -190 % 12 == -10 — ошибка, которая может дорого обойтись.
Как получить атрибут литерала int вместо SyntaxError?
Попытка обычным способом обратиться к атрибуту литерала int приводит к ошибке SyntaxError, поскольку точка воспринимается как десятичная точка:
>>> 1.__class__ File "<stdin>", line 1 1.__class__ ^ SyntaxError: invalid decimal literal
Решение — отделить литерал от точки пробелом или скобками.
>>> 1 .__class__ <class 'int'> >>> (1).__class__ <class 'int'>
Как преобразовать строку в число?
Для целых чисел используйте встроенный конструктор типа int(), например int('144')
== 144. Аналогично, float() преобразует значение в число с плавающей точкой, например float('144') == 144.0.
По умолчанию эти функции интерпретируют число как десятичное, поэтому int('0144') ==
144 выполняется, а int('0x144') вызывает исключение ValueError. В качестве второго необязательного аргумента int(string,
base) принимает основание системы счисления, из которой выполняется преобразование, поэтому int(
'0x144', 16) == 324. Если основание указано как 0, число интерпретируется по правилам Python: начальный «0o» обозначает восьмеричное число, а «0x» — шестнадцатеричное.
Не используйте встроенную функцию eval(), если вам нужно только преобразовать строки в числа. eval() работает значительно медленнее и представляет угрозу безопасности: кто-нибудь может передать вам выражение Python с нежелательными побочными эффектами. Например, вам могут передать __import__('os').system("rm -rf $HOME"), которое удалит вашу домашнюю директорию.
eval() также интерпретирует числа как выражения Python, поэтому, например, eval('09') вызывает синтаксическую ошибку: Python не допускает начальный «0» в десятичном числе (за исключением самого числа «0»).
Как преобразовать число в строку?
Чтобы, например, преобразовать число 144 в строку '144', используйте встроенный конструктор типа str(). Для шестнадцатеричного или восьмеричного представления используйте встроенные функции hex() или oct(). Для более сложного форматирования см. разделы f-строки и синтаксис форматирования строк; например, "{:04d}".format(144) дает '0144', а "{:.3f}".format(1.0/3.0) дает '0.333'.
Как изменить строку на месте?
Это невозможно, поскольку строки неизменяемы. В большинстве случаев следует просто создать новую строку из нужных частей. Однако если вам нужен объект, позволяющий изменять данные Unicode на месте, попробуйте использовать объект io.StringIO или модуль array:
>>> import io
>>> s = "Hello, world"
>>> sio = io.StringIO(s)
>>> sio.getvalue()
'Hello, world'
>>> sio.seek(7)
7
>>> sio.write("there!")
6
>>> sio.getvalue()
'Hello, there!'
>>> import array
>>> a = array.array('w', s)
>>> print(a)
array('w', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('w', '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()
Есть ли аналог chomp() из Perl для удаления завершающих символов новой строки?
Можно использовать S.rstrip("\r\n"), чтобы удалить все символы завершения строки в конце строки S, не затрагивая другие завершающие пробельные символы. Если строка S содержит несколько строк и в конце есть несколько пустых строк, символы завершения строки будут удалены у всех пустых строк:
>>> lines = ("line 1 \r\n"
... "\r\n"
... "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '
Обычно это нужно только при чтении текста построчно, поэтому такой способ использования S.rstrip() хорошо подходит.
Есть ли аналог scanf() или sscanf()?
Прямого аналога нет.
Для простого разбора входных данных обычно проще всего разделить строку на слова, отделённые пробельными символами, с помощью метода split() строковых объектов, а затем преобразовать десятичные строки в числовые значения с помощью int() или float(). split() поддерживает необязательный параметр «sep», который полезен, если в строке в качестве разделителя используются не пробельные символы.
Для более сложного разбора входных данных регулярные выражения мощнее, чем sscanf из C, и лучше подходят для этой задачи.
Что означает ошибка UnicodeDecodeError или UnicodeEncodeError?
Можно ли завершить необработанную строку нечётным количеством обратных косых черт?
Необработанная строка, заканчивающаяся нечётным количеством обратных косых черт, экранирует закрывающую кавычку строки:
>>> r'C:\this\will\not\work\'
File "<stdin>", line 1
r'C:\this\will\not\work\'
^
SyntaxError: unterminated string literal (detected at line 1)
Есть несколько способов обойти это ограничение. Один из них — использовать обычные строки и удвоить обратные косые черты:
>>> 'C:\\this\\will\\work\\' 'C:\\this\\will\\work\\'
Другой способ — присоединить к необработанной строке обычную строку, содержащую экранированную обратную косую черту:
>>> r'C:\this\will\work' '\\' 'C:\\this\\will\\work\\'
В Windows также можно использовать os.path.join(), чтобы добавить обратную косую черту:
>>> os.path.join(r'C:\this\will\work', '') 'C:\\this\\will\\work\\'
Обратите внимание: хотя обратная косая черта «экранирует» кавычку при определении конца необработанной строки, при интерпретации значения необработанной строки экранирования не происходит. Иными словами, обратная косая черта остаётся в значении необработанной строки:
>>> r'backslash\'preserved' "backslash\\'preserved"
См. также описание в справочнике по языку.
Производительность
Моя программа работает слишком медленно. Как её ускорить?
В общем случае это непростой вопрос. Прежде чем углубляться в тему, учтите следующее:
- Характеристики производительности различаются в разных реализациях Python. В этом разделе часто задаваемых вопросов рассматривается CPython.
- Поведение может различаться в разных операционных системах, особенно при работе с вводом-выводом или многопоточностью.
- Прежде чем пытаться оптимизировать код, всегда находите узкие места в программе (см. модуль
profile). - Написание сценариев для тестирования производительности позволит быстро перебирать варианты в поисках улучшений (см. модуль
timeit). - Настоятельно рекомендуется обеспечить хорошее покрытие кода тестами (с помощью модульного тестирования или других средств), прежде чем вносить потенциально регрессионные сложные оптимизации.
Тем не менее существует много способов ускорить код Python. Ниже приведены общие принципы, которые помогут добиться приемлемой производительности:
- Ускорение алгоритмов (или переход на более быстрые алгоритмы) может дать гораздо больший эффект, чем попытки повсюду применять микрооптимизации.
- Используйте подходящие структуры данных. Изучите документацию по встроенным типам и модулю
collections. - Если стандартная библиотека предоставляет средство для решения задачи, скорее всего (хотя это и не гарантируется), оно будет быстрее любого альтернативного решения, которое вы сможете придумать. Особенно это относится к средствам, написанным на C, например к встроенным функциям и некоторым типам расширений. Например, для сортировки используйте встроенный метод
list.sort()или связанную с ним функциюsorted()(примеры достаточно сложного использования см. в разделе методы сортировки). - Абстракции обычно создают дополнительные уровни косвенности и увеличивают нагрузку на интерпретатор. Если затраты на косвенность превышают объём полезной работы, программа будет работать медленнее. Избегайте чрезмерных абстракций, особенно в виде небольших функций или методов (которые часто также ухудшают читаемость).
Если вы достигли предела возможностей чистого Python, существуют инструменты, которые помогут продвинуться дальше. Например, Cython может скомпилировать слегка изменённую версию кода Python в расширение C и работает на многих платформах. Cython использует преимущества компиляции (и необязательных аннотаций типов), позволяя значительно ускорить код по сравнению с интерпретируемым вариантом. Если вы уверенно программируете на C, можно также самостоятельно написать модуль расширения C.
См. также
Страница вики, посвящённая советам по повышению производительности.
Как эффективнее всего объединить множество строк?
Объекты str и bytes неизменяемы, поэтому объединять множество строк неэффективно: при каждом объединении создаётся новый объект. В общем случае общая стоимость выполнения растёт квадратично относительно общей длины строк.
Чтобы накапливать множество объектов str, рекомендуется помещать их в список и в конце вызывать str.join():
chunks = []
for s in my_strings:
chunks.append(s)
result = ''.join(chunks)
(ещё один достаточно эффективный способ — использовать io.StringIO)
Чтобы накапливать множество объектов bytes, рекомендуется расширять объект bytearray с помощью конкатенации на месте (оператор +=):
result = bytearray()
for b in my_bytes_objects:
result += b
Последовательности (кортежи/списки)
Как преобразовывать кортежи в списки и наоборот?
Конструктор типа tuple(seq) преобразует любую последовательность (на самом деле любой итерируемый объект) в кортеж с теми же элементами в том же порядке.
Например, tuple([1, 2, 3]) возвращает (1, 2, 3), а tuple('abc') возвращает ('a', 'b', 'c'). Если аргумент — кортеж, копия не создаётся: возвращается тот же объект, поэтому вызов tuple() не требует больших затрат, если вы не уверены, что объект уже является кортежем.
Конструктор типа list(seq) преобразует любую последовательность или итерируемый объект в список с теми же элементами в том же порядке. Например, list((1, 2, 3)) возвращает [1, 2, 3], а list('abc') возвращает ['a', 'b', 'c']. Если аргумент — список, создаётся его копия, как и при вызове seq[:].
Что такое отрицательный индекс?
В последовательностях Python используются положительные и отрицательные индексы. При положительной индексации 0 — это первый индекс, 1 — второй и так далее. При отрицательной индексации -1 — последний индекс, -2 — предпоследний и так далее. Считайте, что seq[-n] эквивалентно seq[len(seq)-n].
Отрицательные индексы могут быть очень удобны. Например, S[:-1] — это вся строка, кроме последнего символа; это удобно для удаления завершающего символа новой строки.
Как перебрать последовательность в обратном порядке?
Используйте встроенную функцию reversed():
for x in reversed(sequence):
... # do something with x ...
Исходная последовательность не изменится: будет создана новая копия в обратном порядке, по которой и будет выполняться перебор.
Как удалить дубликаты из списка?
Подробное обсуждение множества способов решения этой задачи см. в Python Cookbook:
https://code.activestate.com/recipes/52560/
Если порядок элементов списка не важен, отсортируйте его, а затем просматривайте с конца, удаляя дубликаты по мере обнаружения:
if mylist:
mylist.sort()
last = mylist[-1]
for i in range(len(mylist)-2, -1, -1):
if last == mylist[i]:
del mylist[i]
else:
last = mylist[i]
Если все элементы списка можно использовать в качестве ключей множества (то есть все они хешируемые), часто быстрее будет сделать так:
mylist = list(set(mylist))
Это преобразует список во множество, удаляя дубликаты, а затем обратно в список.
Как удалить из списка несколько элементов?
Как и при удалении дубликатов, можно явно перебирать список в обратном порядке и удалять элементы по условию. Однако проще и быстрее использовать замену среза с неявным или явным перебором в прямом порядке. Вот три варианта:
mylist[:] = filter(keep_function, mylist) mylist[:] = (x for x in mylist if keep_condition) mylist[:] = [x for x in mylist if keep_condition]
Список-генератор может оказаться самым быстрым вариантом.
Как создать массив в Python?
Используйте список:
["this", 1, "is", "an", "array"]
По временной сложности списки эквивалентны массивам C или Pascal; главное отличие состоит в том, что список Python может содержать объекты разных типов.
Модуль array также предоставляет методы для создания массивов фиксированных типов с компактным представлением, однако индексирование в них медленнее, чем в списках. Учтите также, что NumPy и другие сторонние пакеты определяют структуры, подобные массивам, с различными характеристиками.
Для получения связанных списков в стиле Lisp можно эмулировать cons-ячейки с помощью кортежей:
lisp_list = ("like", ("this", ("example", None) ) )
Если нужна изменяемость, вместо кортежей можно использовать списки. Аналог car в Lisp здесь — lisp_list[0], а аналог cdr — lisp_list[1]. Делайте так, только если уверены, что это действительно необходимо: обычно этот способ намного медленнее использования списков Python.
Как создать многомерный список?
Возможно, вы пытались создать многомерный массив следующим образом:
>>> A = [[None] * 2] * 3
При выводе он выглядит корректно:
>>> A [[None, None], [None, None], [None, None]]
Но при присваивании значения оно появляется сразу в нескольких местах:
>>> A[0][0] = 5 >>> A [[5, None], [5, None], [5, None]]
Причина в том, что повторение списка с помощью * не создаёт копии, а лишь создаёт ссылки на существующие объекты. *3 создаёт список, содержащий 3 ссылки на один и тот же список длины два. Изменения в одной строке будут видны во всех строках, чего почти наверняка не требуется.
Рекомендуется сначала создать список нужной длины, а затем заполнить каждый его элемент новым списком:
A = [None] * 3
for i in range(3):
A[i] = [None] * 2
Так создаётся список, содержащий 3 разных списка длины два. Также можно использовать генератор списков:
w, h = 2, 3 A = [[None] * w for i in range(h)]
Можно также использовать расширение, предоставляющее тип данных «матрица»; наиболее известное из них — NumPy.
Как применить метод или функцию к последовательности объектов?
Чтобы вызвать метод или функцию и собрать возвращаемые значения в список, удобно использовать генератор списков:
result = [obj.method() for obj in mylist] result = [function(obj) for obj in mylist]
Чтобы просто вызвать метод или функцию, не сохраняя возвращаемые значения, достаточно обычного цикла for:
for obj in mylist:
obj.method()
for obj in mylist:
function(obj)
Почему a_tuple[i] += [‘item’] вызывает исключение, хотя сложение работает?
Это происходит из-за сочетания двух факторов: операторы расширенного присваивания являются операторами присваивания, а в Python существуют изменяемые и неизменяемые объекты.
Это обсуждение в целом относится к применению операторов расширенного присваивания к элементам кортежа, указывающим на изменяемые объекты, но для примера мы рассмотрим list и +=.
Предположим, вы написали:
>>> a_tuple = (1, 2) >>> a_tuple[0] += 1 Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
Причина исключения должна быть сразу понятна: к объекту, на который указывает a_tuple[0] (1), добавляется 1, в результате чего получается объект 2. Но при попытке присвоить результат вычисления 2 элементу 0 кортежа возникает ошибка, поскольку изменить объект, на который указывает элемент кортежа, нельзя.
В действительности это оператор расширенного присваивания примерно эквивалентен следующему:
>>> result = a_tuple[0] + 1 >>> a_tuple[0] = result Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
Ошибка возникает на этапе присваивания, поскольку кортеж неизменяем.
Когда вы пишете что-то вроде:
>>> a_tuple = (['foo'], 'bar') >>> a_tuple[0] += ['item'] Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
Это исключение несколько удивительнее, а ещё удивительнее то, что, несмотря на ошибку, добавление всё же произошло:
>>> a_tuple[0] ['foo', 'item']
Чтобы понять, почему это происходит, нужно знать следующее: (а) если объект реализует специальный метод __iadd__(), он вызывается при выполнении оператора расширенного присваивания +=, а его возвращаемое значение используется в операторе присваивания; (б) для списков __iadd__() эквивалентно вызову extend() для списка с последующим возвратом самого списка. Именно поэтому говорят, что для списков += — это сокращённая запись для list.extend():
>>> a_list = [] >>> a_list += [1] >>> a_list [1]
Это эквивалентно следующему:
>>> result = a_list.__iadd__([1]) >>> a_list = result
Объект, на который указывает a_list, был изменён, а указатель на изменённый объект присваивается обратно переменной a_list. В результате присваивание ничего не меняет, поскольку это указатель на тот же объект, на который ранее указывала a_list, однако операция присваивания всё равно выполняется.
Таким образом, в нашем примере с кортежем происходит нечто эквивалентное следующему:
>>> result = a_tuple[0].__iadd__(['item']) >>> a_tuple[0] = result Traceback (most recent call last): ... TypeError: 'tuple' object does not support item assignment
Вызов __iadd__() проходит успешно, и список расширяется. Но, хотя result указывает на тот же объект, на который уже указывает a_tuple[0], последнее присваивание всё равно приводит к ошибке, поскольку кортежи неизменяемы.
Мне нужна сложная сортировка: можно ли выполнить в Python преобразование Шварца?
Этот метод, приписываемый Рэндалу Шварцу из сообщества Perl, сортирует элементы списка по метрике, которая сопоставляет каждому элементу его «значение сортировки». В Python используйте аргумент key метода list.sort():
Isorted = L[:] Isorted.sort(key=lambda s: int(s[10:15]))
Как отсортировать один список по значениям из другого списка?
Объедините их в итератор кортежей, отсортируйте получившийся список, а затем выберите нужный элемент.
>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']
Объекты
Что такое класс?
Класс — это определённый тип объекта, создаваемый при выполнении инструкции class. Объекты классов используются как шаблоны для создания объектов-экземпляров, которые содержат как данные (атрибуты), так и код (методы), относящиеся к определённому типу данных.
Класс может быть основан на одном или нескольких других классах, называемых его базовыми классами. В этом случае он наследует атрибуты и методы базовых классов. Благодаря этому модель объектов можно постепенно уточнять с помощью наследования. Например, у вас может быть универсальный класс Mailbox, предоставляющий базовые методы доступа к почтовому ящику, и подклассы, такие как MboxMailbox, MaildirMailbox, OutlookMailbox, предназначенные для работы с различными конкретными форматами почтовых ящиков.
Что такое метод?
Метод — это функция некоторого объекта x, которую обычно вызывают как x.name(arguments...). Методы определяются как функции внутри определения класса:
class C:
def meth(self, arg):
return arg * 2 + self.attribute
Что такое self?
Self — это просто традиционное имя первого аргумента метода. Метод, определённый как meth(self, a, b, c), следует вызывать как x.meth(a, b, c) для некоторого экземпляра x класса, в котором находится определение; вызванный метод будет считать, что его вызвали как meth(x, a, b, c).
См. также Почему в определениях и вызовах методов необходимо явно указывать «self»?.
Как проверить, является ли объект экземпляром заданного класса или его подкласса?
Используйте встроенную функцию isinstance(obj, cls). Проверить, является ли объект экземпляром одного из нескольких классов, можно, передав кортеж вместо одного класса, например isinstance(obj, (class1, class2, ...)); также можно проверить, относится ли объект к одному из встроенных типов Python, например isinstance(obj, str) или isinstance(obj, (int, float, complex)).
Обратите внимание, что isinstance() также проверяет виртуальное наследование от абстрактного базового класса. Поэтому для зарегистрированного класса проверка вернёт True, даже если он не наследовался от него напрямую или косвенно. Чтобы проверить «настоящее наследование», просмотрите MRO класса:
from collections.abc import Mapping
class P:
pass
class C(P):
pass
Mapping.register(P)
>>> c = C() >>> isinstance(c, C) # direct True >>> isinstance(c, P) # indirect True >>> isinstance(c, Mapping) # virtual True # Actual inheritance chain >>> type(c).__mro__ (<class 'C'>, <class 'P'>, <class 'object'>) # Test for "true inheritance" >>> Mapping in type(c).__mro__ False
Обратите внимание, что в большинстве программ isinstance() нечасто используется для классов, определённых пользователем. Если вы сами разрабатываете классы, более правильный объектно-ориентированный подход — определять в классах методы, реализующие определённое поведение, вместо проверки класса объекта и выполнения разных действий в зависимости от того, к какому классу он относится. Например, если у вас есть функция, выполняющая некоторое действие:
def search(obj):
if isinstance(obj, Mailbox):
... # code to search a mailbox
elif isinstance(obj, Document):
... # code to search a document
elif ...
Лучше определить метод search() во всех классах и просто вызывать его:
class Mailbox:
def search(self):
... # code to search a mailbox
class Document:
def search(self):
... # code to search a document
obj.search()
Что такое делегирование?
Делегирование — это объектно-ориентированный приём (также называемый шаблоном проектирования). Допустим, у вас есть объект x, и вы хотите изменить поведение только одного из его методов. Можно создать новый класс, который предоставляет новую реализацию метода, поведение которого нужно изменить, а вызовы всех остальных методов делегирует соответствующим методам объекта x.
В Python делегирование реализовать легко. Например, следующий класс реализует объект, который ведёт себя как файл, но преобразует все записываемые данные в верхний регистр:
class UpperOut:
def __init__(self, outfile):
self._outfile = outfile
def write(self, s):
self._outfile.write(s.upper())
def __getattr__(self, name):
return getattr(self._outfile, name)
Здесь класс UpperOut переопределяет метод write(), преобразуя строку-аргумент в верхний регистр перед вызовом нижележащего метода self._outfile.write(). Все остальные методы делегируются нижележащему объекту self._outfile. Делегирование выполняется с помощью метода __getattr__(); дополнительную информацию об управлении доступом к атрибутам см. в справочнике по языку.
Обратите внимание, что в более общих случаях делегирование может быть сложнее. Если атрибуты нужно не только получать, но и задавать, класс должен также определить метод __setattr__(), причём сделать это нужно аккуратно. Базовая реализация __setattr__() примерно эквивалентна следующей:
class X:
...
def __setattr__(self, name, value):
self.__dict__[name] = value
...
Многие реализации __setattr__() вызывают object.__setattr__(), чтобы задать атрибут объекта self, не вызывая бесконечную рекурсию:
class X:
def __setattr__(self, name, value):
# Custom logic here...
object.__setattr__(self, name, value)
В качестве альтернативы атрибуты можно задать, напрямую добавив записи в self.__dict__.
Как вызвать из производного класса, расширяющего базовый класс, метод, определённый в базовом классе?
Используйте встроенную функцию super():
class Derived(Base):
def meth(self):
super().meth() # calls Base.meth
В этом примере super() автоматически определит экземпляр, из которого её вызвали (значение self), найдёт порядок разрешения методов (MRO) с помощью type(self).__mro__ и вернёт следующий класс после Derived в MRO: Base.
Как организовать код, чтобы упростить замену базового класса?
Можно присвоить базовый класс псевдониму и наследоваться от этого псевдонима. Тогда достаточно будет изменить только значение, присвоенное псевдониму. Кстати, этот приём также удобен, если вы хотите динамически выбирать базовый класс (например, в зависимости от доступности ресурсов). Пример:
class Base:
...
BaseAlias = Base
class Derived(BaseAlias):
...
Как создавать статические данные и статические методы класса?
В Python поддерживаются и статические данные, и статические методы (в смысле C++ или Java).
Чтобы создать статические данные, просто определите атрибут класса. Чтобы присвоить атрибуту новое значение, в присваивании необходимо явно указать имя класса:
class C:
count = 0 # number of times C.__init__ called
def __init__(self):
C.count = C.count + 1
def getcount(self):
return C.count # or return self.count
c.count также ссылается на C.count для любого c, для которого выполняется isinstance(c,
C), если оно не переопределено самим c или каким-либо классом в цепочке поиска базовых классов от c.__class__ до C.
Предупреждение: внутри метода класса C присваивание вида self.count = 42 создаёт новый, не связанный с ним экземпляр с именем «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 — это имя текущего класса без начальных символов подчёркивания.
Внутри класса идентификатор можно использовать без изменений, но для доступа к нему за пределами класса необходимо использовать искажённое имя:
class A:
def __one(self):
return 1
def two(self):
return 2 * self.__one()
class B(A):
def three(self):
return 3 * self._A__one()
four = 4 * A()._A__one()
В частности, это не гарантирует приватность, поскольку внешний пользователь всё ещё может намеренно обратиться к закрытому атрибуту; многие программисты на Python вообще не используют закрытые имена переменных.
См. также
Подробности и особые случаи описаны в спецификации искажения закрытых имён.
В моём классе определён __del__, но при удалении объекта он не вызывается.
Это может происходить по нескольким причинам.
Инструкция del не обязательно вызывает __del__() — она просто уменьшает счётчик ссылок на объект, и если он достигает нуля, вызывается __del__().
Если ваши структуры данных содержат циклические связи (например, дерево, в котором у каждого дочернего узла есть ссылка на родителя, а у каждого родителя — список дочерних узлов), счётчики ссылок никогда не обнулятся. Время от времени Python запускает алгоритм обнаружения таких циклов, но сборщик мусора может запуститься спустя некоторое время после исчезновения последней ссылки на вашу структуру данных, поэтому метод __del__() может быть вызван в неудобный и непредсказуемый момент. Это мешает воспроизвести проблему. Хуже того, порядок выполнения методов __del__() объектов произволен. Чтобы принудительно запустить сборку мусора, можно вызвать gc.collect(), но существуют патологические случаи, когда объекты никогда не будут собраны.
Несмотря на наличие сборщика циклов, объектам всё равно полезно определять явный метод close(), который вызывается, когда работа с ними завершена. Метод close() может удалять атрибуты, ссылающиеся на подобъекты. Не вызывайте __del__() напрямую — __del__() должен вызывать close(), а close() должен гарантировать возможность повторного вызова для одного и того же объекта.
Ещё один способ избежать циклических ссылок — использовать модуль weakref, позволяющий ссылаться на объекты, не увеличивая их счётчик ссылок. Например, в древовидных структурах данных для ссылок на родителей и соседние узлы (если они нужны!) следует использовать слабые ссылки.
Наконец, если ваш метод __del__() вызывает исключение, предупреждение выводится в sys.stderr.
Как получить список всех экземпляров заданного класса?
Python не отслеживает все экземпляры класса (или встроенного типа). Можно запрограммировать конструктор класса так, чтобы он отслеживал все экземпляры, храня список слабых ссылок на каждый из них.
Почему результат id() кажется неуникальным?
Встроенная функция id() возвращает целое число, уникальность которого гарантируется на протяжении всего времени существования объекта. Поскольку в CPython это адрес объекта в памяти, часто случается, что после удаления объекта из памяти следующий вновь созданный объект размещается по тому же адресу. Это иллюстрирует следующий пример:
>>> id(1000) 13901272 >>> id(2000) 13901272
Эти два идентификатора принадлежат разным целочисленным объектам, которые создаются до вызова id() и удаляются сразу после его выполнения. Чтобы убедиться, что объекты, идентификаторы которых вы хотите проверить, ещё существуют, создайте на них ещё одну ссылку:
>>> a = 1000; b = 2000 >>> id(a) 13901272 >>> id(b) 13891296
В каких случаях можно полагаться на проверку идентичности с помощью оператора is?
Оператор is проверяет идентичность объектов. Проверка a is b эквивалентна id(a) == id(b).
Важнейшее свойство проверки идентичности заключается в том, что объект всегда идентичен самому себе: a is a всегда возвращает True. Проверки идентичности обычно выполняются быстрее проверок на равенство. И, в отличие от проверок на равенство, они гарантированно возвращают логическое значение True или False.
Однако проверки идентичности можно использовать только вместо проверок на равенство, если идентичность объектов гарантирована. Как правило, это гарантируется в трёх случаях:
- Присваивания создают новые имена, но не меняют идентичность объекта. После присваивания
new = oldгарантируется, чтоnew is old. - Помещение объекта в контейнер, хранящий ссылки на объекты, не меняет его идентичность. После присваивания списка
s[0] = xгарантируется, чтоs[0] is x. - Если объект является синглтоном, это означает, что может существовать только один его экземпляр. После присваиваний
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
В коде стандартной библиотеки можно встретить несколько распространённых шаблонов правильного использования проверок идентичности:
- Как рекомендуется в PEP 8, проверка идентичности предпочтительна для проверки значения
None. Такой код читается как обычный английский текст и не допускает путаницы с другими объектами, логическое значение которых может быть ложным. -
Определить, передан ли необязательный аргумент, может быть непросто, если
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 - Реализациям контейнеров иногда необходимо дополнять проверки на равенство проверками идентичности. Это позволяет избежать проблем с такими объектами, как
float('NaN'), которые не равны сами себе.
Например, вот реализация collections.abc.Sequence.__contains__():
def __contains__(self, value):
for v in self:
if v is value or v == value:
return True
return False
Как подкласс может управлять данными, хранящимися в неизменяемом экземпляре?
При создании подкласса неизменяемого типа переопределите метод __new__(), а не метод __init__(). Последний выполняется только после создания экземпляра, поэтому изменять данные в неизменяемом экземпляре уже слишком поздно.
У всех этих неизменяемых классов сигнатура отличается от сигнатуры родительского класса:
from datetime import date
class FirstOfMonthDate(date):
"Always choose the first day of the month"
def __new__(cls, year, month, day):
return super().__new__(cls, year, month, 1)
class NamedInt(int):
"Allow text names for some numbers"
xlat = {'zero': 0, 'one': 1, 'ten': 10}
def __new__(cls, value):
value = cls.xlat.get(value, value)
return super().__new__(cls, value)
class TitleStr(str):
"Convert str to name suitable for a URL path"
def __new__(cls, s):
s = s.lower().replace(' ', '-')
s = ''.join([c for c in s if c.isalnum() or c == '-'])
return super().__new__(cls, s)
Использовать классы можно так:
>>> FirstOfMonthDate(2012, 2, 14)
FirstOfMonthDate(2012, 2, 1)
>>> NamedInt('ten')
10
>>> NamedInt(20)
20
>>> TitleStr('Blog: Why Python Rocks')
'blog-why-python-rocks'
Как кэшировать вызовы методов?
Два основных инструмента для кэширования методов — functools.cached_property() и functools.lru_cache(). Первый сохраняет результаты на уровне экземпляра, а второй — на уровне класса.
Подход с cached_property работает только с методами, не принимающими аргументов. Он не создаёт ссылку на экземпляр. Результат кэшированного метода хранится только до тех пор, пока существует экземпляр.
Преимущество в том, что кэшированный результат метода сразу освобождается, когда экземпляр больше не используется. Недостаток — если экземпляры накапливаются, накапливаются и результаты методов. Их объём может расти неограниченно.
Подход с lru_cache работает с методами, принимающими хешируемые аргументы. Он создаёт ссылку на экземпляр, если специально не передавать слабые ссылки.
Преимущество алгоритма вытеснения наименее недавно использованных элементов в том, что размер кэша ограничен заданным значением maxsize. Недостаток — экземпляры остаются живыми, пока не устареют в кэше или пока кэш не будет очищен.
В этом примере показаны различные методы:
class Weather:
"Lookup weather information on a government website"
def __init__(self, station_id):
self._station_id = station_id
# The _station_id is private and immutable
def current_temperature(self):
"Latest hourly observation"
# Do not cache this because old results
# can be out of date.
@cached_property
def location(self):
"Return the longitude/latitude coordinates of the station"
# Result only depends on the station_id
@lru_cache(maxsize=20)
def historic_rainfall(self, date, units='mm'):
"Rainfall on a given date"
# Depends on the station_id, date, and units.
В приведённом выше примере предполагается, что значение station_id никогда не меняется. Если соответствующие атрибуты экземпляра изменяемы, подход с cached_property неприменим, поскольку он не может обнаруживать изменения атрибутов.
Чтобы подход с lru_cache работал при изменяемом station_id, классу нужно определить методы __eq__() и __hash__(), чтобы кэш мог обнаруживать изменения соответствующих атрибутов:
class Weather:
"Example with a mutable station identifier"
def __init__(self, station_id):
self.station_id = station_id
def change_station(self, station_id):
self.station_id = station_id
def __eq__(self, other):
return self.station_id == other.station_id
def __hash__(self):
return hash(self.station_id)
@lru_cache(maxsize=20)
def historic_rainfall(self, date, units='cm'):
'Rainfall on a given date'
# Depends on the station_id, date, and units.
Модули
Как создать файл .pyc?
При первом импорте модуля (или если исходный файл изменился после создания текущего скомпилированного файла) в подкаталоге __pycache__ каталога, содержащего файл .py, должен создаваться файл .pyc со скомпилированным кодом. Имя файла .pyc начинается с того же имени, что и имя файла .py, и заканчивается на .pyc; средняя часть зависит от конкретного двоичного файла python, создавшего его. (Подробности см. в PEP 3147.)
Одна из причин, по которой файл .pyc может не создаваться, — проблема с правами доступа к каталогу, содержащему исходный файл: из-за этого невозможно создать подкаталог __pycache__. Например, такое может произойти, если вы разрабатываете программу под одной учётной записью, а запускаете под другой, например при тестировании на веб-сервере.
Если не задана переменная среды PYTHONDONTWRITEBYTECODE, файл .pyc создаётся автоматически при импорте модуля, если Python может (с учётом прав доступа, свободного места и т. д.) создать подкаталог __pycache__ и записать в него скомпилированный модуль.
Запуск Python со скриптом верхнего уровня не считается импортом, поэтому файл .pyc не создаётся. Например, если у вас есть модуль верхнего уровня foo.py, импортирующий другой модуль xyz.py, при запуске foo (вводом python foo.py в командной оболочке) для xyz будет создан файл .pyc, поскольку xyz импортируется, но для foo файл .pyc создан не будет, так как foo.py не импортируется.
Если вам нужно создать файл .pyc для foo — то есть создать файл .pyc для неимпортируемого модуля, — это можно сделать с помощью модулей py_compile и compileall.
Модуль py_compile позволяет вручную скомпилировать любой модуль. Например, можно интерактивно использовать функцию compile() из этого модуля:
>>> import py_compile
>>> py_compile.compile('foo.py')
Файл .pyc будет записан в подкаталог __pycache__ в том же месте, где находится foo.py (или можно переопределить это место с помощью необязательного параметра cfile).
Автоматически скомпилировать все файлы в одном или нескольких каталогах можно с помощью модуля compileall. Для этого введите в командной строке compileall.py и укажите путь к каталогу с файлами Python, которые нужно скомпилировать:
python -m compileall .
Как узнать имя текущего модуля?
Модуль может узнать собственное имя, обратившись к предопределённой глобальной переменной __name__. Если её значение равно '__main__', программа запущена как скрипт. Многие модули, которые обычно импортируются, также предоставляют интерфейс командной строки или самопроверку и выполняют этот код только после проверки __name__:
def main():
print('Running test...')
...
if __name__ == '__main__':
main()
Как организовать взаимный импорт модулей?
Предположим, у вас есть следующие модули:
foo.py:
from bar import bar_var foo_var = 1
bar.py:
from foo import foo_var bar_var = 2
Проблема в том, что интерпретатор выполнит следующие шаги:
- main импортирует
foo - Создаются пустые глобальные переменные для
foo -
Компилируется
fooи начинается его выполнение -
fooимпортируетbar - Создаются пустые глобальные переменные для
bar -
Компилируется
barи начинается его выполнение -
barимпортируетfoo(ничего не происходит, поскольку модуль с именемfooуже существует) - Механизм импорта пытается прочитать
foo_varиз глобальных переменныхfoo, чтобы присвоитьbar.foo_var = foo.foo_var
Последний шаг завершается ошибкой, поскольку Python ещё не закончил интерпретацию foo, и глобальный словарь символов для foo всё ещё пуст.
То же самое происходит, когда вы используете import foo, а затем пытаетесь обратиться к foo.foo_var в глобальном коде.
Эту проблему можно решить по крайней мере тремя способами.
Гвидо ван Россум рекомендует избегать любых случаев использования from <module> import ... и помещать весь код внутрь функций. Для инициализации глобальных переменных и переменных класса следует использовать только константы или встроенные функции. Это означает, что на всё из импортированного модуля нужно ссылаться как на <module>.<name>.
Джим Росклинд предлагает выполнять следующие действия в каждом модуле в указанном порядке:
- экспортируемые элементы (глобальные переменные, функции и классы, которым не нужны импортированные базовые классы)
-
инструкции
import - активный код (в том числе глобальные переменные, инициализируемые импортированными значениями).
Ван Россуму этот подход не очень нравится, поскольку импорты оказываются в необычном месте, но он работает.
Маттиас Урлихс рекомендует перестроить код так, чтобы рекурсивный импорт изначально не требовался.
Эти решения не исключают друг друга.
__import__(‘x.y.z’) возвращает <module ‘x’>; как получить z?
Вместо этого рассмотрите возможность использования удобной функции import_module() из importlib:
z = importlib.import_module('x.y.z')
Почему изменения не отображаются, когда я редактирую импортированный модуль и импортирую его повторно?
Из соображений эффективности и согласованности Python читает файл модуля только при первом импорте этого модуля. Если бы это было не так, в программе из множества модулей, каждый из которых импортирует один и тот же базовый модуль, этот базовый модуль разбирался бы снова и снова. Чтобы принудительно перечитать изменённый модуль, выполните следующее:
import importlib import modname importlib.reload(modname)
Предупреждение: этот способ не даёт стопроцентной гарантии. В частности, модули, содержащие инструкции вида
from modname import some_objects
продолжат работать со старой версией импортированных объектов. Если модуль содержит определения классов, уже существующие экземпляры классов не будут обновлены и не начнут использовать новое определение класса. Это может привести к следующему парадоксальному поведению:
>>> import importlib >>> import cls >>> c = cls.C() # Create an instance of C >>> importlib.reload(cls) <module 'cls' from 'cls.py'> >>> isinstance(c, cls.C) # isinstance is false?!? False
Суть проблемы становится очевидной, если вывести «идентичность» объектов классов:
>>> hex(id(c.__class__)) '0x7352a0' >>> hex(id(cls.C)) '0x4198d0'
© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/faq/programming.html