Документация Python неофициальный перевод
Содержание страницы

Часто задаваемые вопросы по дизайну и историиDesign and History FAQ

Содержание

Почему в Python для группировки операторов используется отступ?Why does Python use indentation for grouping of statements?

Гвидо ван Россум считает, что использование отступов для группировки чрезвычайно элегантно и значительно повышает читаемость типичной программы на Python. Большинство людей со временем начинают любить эту особенность.

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

if (x <= y)
        x++;
        y--;
z++;

Если условие истинно, выполняется только оператор x++, но отступ может заставить думать иначе. Даже опытные программисты на C будут иногда подолгу смотреть на это, гадая, почему y уменьшается даже для x > y.

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

Многие стили оформления размещают фигурные скобки на отдельных строках. Это делает программы значительно длиннее и тратит ценное пространство экрана, затрудняя получение хорошего обзора программы. В идеале функция должна помещаться на один экран (скажем, 20–30 строк). 20 строк Python могут выполнить гораздо больше работы, чем 20 строк C. Это связано не только с отсутствием фигурных скобок – отсутствие объявлений и высокоуровневые типы данных также играют роль – но синтаксис на основе отступов, безусловно, помогает.

Почему при простых арифметических операциях получаются странные результаты?Why am I getting strange results with simple arithmetic operations?

Смотрите следующий вопрос.

Почему вычисления с плавающей точкой такие неточные?Why are floating point calculations so inaccurate?

Люди часто удивляются результатам вроде такого:

>>> 1.2 - 1.0
0.19999999999999996

и считают это ошибкой в Python. Это не так. Это не связано с Python, а с тем, как базовая платформа C обрабатывает числа с плавающей запятой, и в конечном итоге с неточностями, возникающими при записи чисел в виде строки из фиксированного количества цифр.

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

В десятичной арифметике есть много чисел, которые нельзя представить фиксированным количеством десятичных знаков, например 1/3 = 0.3333333333…….

В двоичной системе 1/2 = 0.1, 1/4 = 0.01, 1/8 = 0.001 и т.д. 0.2 равно 2/10 равно 1/5, что даёт двоичную дробь 0.001100110011001…

Числа с плавающей запятой имеют точность всего 32 или 64 бита, поэтому цифры обрываются в какой-то момент, и полученное число в десятичной системе равно 0.199999999999999996, а не 0.2.

Функция repr() числа с плавающей точкой выводит столько цифр, сколько необходимо, чтобы eval(repr(f)) == f было истинно для любого float f. Функция str() выводит меньше цифр, и это часто даёт более разумное число, которое, вероятно, и имелось в виду:

>>> 1.1 - 0.9
0.20000000000000007
>>> print 1.1 - 0.9
0.2

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

epsilon = 0.0000000000001  # Допустимая малая погрешность
expected_result = 0.4

if expected_result-epsilon <= computation() <= expected_result+epsilon:
    ...

Дополнительную информацию см. в главе Арифметика с плавающей запятой в учебнике Python.

Почему строки в Python неизменяемы?Why are Python strings immutable?

Есть несколько преимуществ.

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

Другое преимущество в том, что строки в Python считаются такими же «элементарными», как числа. Никакие действия не превратят значение 8 во что-то другое, и в Python никакие действия не превратят строку «eight» во что-то другое.

Почему ‘self’ нужно явно указывать в определениях и вызовах методов?Why must ‘self’ be used explicitly in method definitions and calls?

Идея была заимствована из Modula-3. Она оказалась очень полезной по ряду причин.

Во-первых, так очевиднее, что используется метод или атрибут экземпляра, а не локальная переменная. Чтение self.x или self.meth() совершенно ясно показывает, что используется переменная экземпляра или метод, даже если вы не знаете определение класса наизусть. В C++ вы примерно можете понять это по отсутствию объявления локальной переменной (при условии, что глобальные переменные встречаются редко или легко узнаваемы) – но в Python нет объявлений локальных переменных, поэтому пришлось бы заглядывать в определение класса, чтобы быть уверенным. Некоторые стандарты оформления кода на C++ и Java требуют, чтобы атрибуты экземпляра имели префикс m_, так что эта явность полезна и в этих языках.

Во-вторых, это означает, что не нужен специальный синтаксис для явной ссылки или вызова метода из определённого класса. В C++, если вы хотите использовать метод из базового класса, который переопределён в производном классе, нужно использовать оператор :: – в Python можно написать baseclass.methodname(self, <argument list>). Это особенно полезно для методов __init__() и в целом в случаях, когда метод производного класса хочет расширить метод базового класса с тем же именем и поэтому должен как-то вызвать метод базового класса.

Наконец, для переменных экземпляра это решает синтаксическую проблему с присваиванием: поскольку локальные переменные в Python (по определению!) – это те переменные, которым присваивается значение в теле функции (и которые не объявлены явно как глобальные), нужен способ сообщить интерпретатору, что присваивание предназначено для переменной экземпляра, а не локальной переменной, и желательно, чтобы этот способ был синтаксическим (из соображений эффективности). C++ делает это через объявления, но в Python объявлений нет, и было бы жаль вводить их только для этой цели. Использование явного self.var красиво решает эту проблему. Аналогично, при использовании переменных экземпляра необходимость писать self.var означает, что ссылки на неквалифицированные имена внутри метода не должны искать в каталогах экземпляра. Другими словами, локальные переменные и переменные экземпляра находятся в двух разных пространствах имён, и нужно сообщить Python, какое пространство имён использовать.

Почему нельзя использовать присваивание в выражении?Why can’t I use an assignment in an expression?

Многие, привыкшие к C или Perl, жалуются, что хотят использовать эту идиому C:

while (line = readline(f)) {
    // сделать что-то со строкой
}

тогда как в Python приходится писать так:

while True:
    line = f.readline()
    if not line:
        break
    ...  # сделать что-то со строкой

Причина, по которой в выражениях Python не допускается присваивание, – распространённая, трудновыявляемая ошибка в других языках, возникающая из-за такой конструкции:

if (x = 0) {
    // обработка ошибок
}
else {
    // код, который работает только для ненулевого x
}

Ошибка – простая опечатка: была написана x = 0, которая присваивает 0 переменной x, хотя подразумевалось сравнение x == 0.

Предлагалось множество альтернатив. Большинство из них – это хаки, которые экономят немного набора, но используют произвольный или непонятный синтаксис или ключевые слова и не проходят простой критерий для предложений по изменению языка: конструкция должна интуитивно подсказывать правильный смысл читателю, который с ней ещё не знаком.

Интересное явление: большинство опытных программистов Python узнают идиому while True и, похоже, не особенно страдают от отсутствия конструкции присваивания в выражениях; только новички активно желают добавить её в язык.

Существует альтернативный способ записи этого, который кажется привлекательным, но в целом менее надёжный, чем решение с “while True”:

line = f.readline()
while line:
    ...  # сделать что-то со строкой...
    line = f.readline()

Проблема с таким подходом: если нужно изменить способ получения следующей строки (например, заменить его на sys.stdin.readline()), придётся не забыть изменить два места в программе – второе вхождение скрыто внизу цикла.

Лучший подход – использовать итераторы, что позволяет перебирать объекты с помощью оператора for. Например, в текущей версии Python файловые объекты поддерживают протокол итератора, поэтому теперь можно просто написать:

for line in f:
    ...  # сделать что-то со строкой...

Почему Python использует методы для одних операций (например, list.index()), а функции для других (например, len(list))?Why does Python use methods for some functionality (e.g. list.index()) but functions for other (e.g. len(list))?

Как сказал Гвидо:

(a) Для некоторых операций префиксная запись читается лучше, чем постфиксная – префиксные (и инфиксные!) операции имеют долгую традицию в математике, которая предпочитает обозначения, где визуальное представление помогает математику думать о задаче. Сравните, как легко мы переписываем формулу вроде x*(a+b) в x*a + x*b, и как неуклюже делать то же самое с помощью чисто объектно-ориентированной нотации.

(b) Когда я читаю код, в котором написано len(x), я знаю, что запрашивается длина чего-то. Это говорит мне о двух вещах: результат – целое число, а аргумент – некий контейнер. Напротив, когда я читаю x.len(), я уже должен знать, что x – это какой-то контейнер, реализующий интерфейс или наследующий от класса, у которого есть стандартный len(). Обратите внимание на путаницу, которая иногда возникает, когда класс, не реализующий отображение (mapping), имеет метод get() или keys() , или когда объект, не являющийся файлом, имеет метод write().

https://mail.python.org/pipermail/python-3000/2006-November/004643.html

Почему join() – это метод строки, а не метод списка или кортежа?Why is join() a string method instead of a list or tuple method?

Строки стали гораздо больше похожи на другие стандартные типы начиная с Python 1.6, когда были добавлены методы, предоставляющие те же возможности, которые всегда были доступны через функции модуля string. Большинство этих новых методов были широко приняты, но один из них, который, по-видимому, вызывает дискомфорт у некоторых программистов, – это:

", ".join(['1', '2', '4', '8', '16'])

который даёт результат:

"1, 2, 4, 8, 16"

Есть два распространённых аргумента против этого использования.

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

Второе возражение обычно формулируется так: «Я действительно говорю последовательности объединить свои элементы с помощью строковой константы». К сожалению, это не так. По какой-то причине гораздо меньше затруднений вызывает то, что split() – это метод строки, поскольку в этом случае легко видеть, что

"1, 2, 4, 8, 16".split(", ")

является инструкцией строковому литералу вернуть подстроки, разделённые заданным разделителем (или, по умолчанию, произвольными последовательностями пробельных символов). В этом случае строка Unicode возвращает список строк Unicode, строка ASCII возвращает список строк ASCII, и все довольны.

join() – это строковый метод, потому что при его использовании строке-разделителю указывается перебрать последовательность строк и вставить себя между соседними элементами. Этот метод можно использовать с любым аргументом, который подчиняется правилам для объектов-последовательностей, включая любые новые классы, которые могут быть определены самостоятельно.

Поскольку это строковый метод, он может работать как со строками Unicode, так и с обычными строками ASCII. Если бы join() был методом типов-последовательностей, то типам-последовательностям пришлось бы решать, строку какого типа возвращать в зависимости от типа разделителя.

Если ни один из этих аргументов не кажется убедительным, то пока можно продолжать использовать функцию join() из модуля string, с помощью которой можно писать

string.join(['1', '2', '4', '8', '16'], ", ")

Насколько быстры исключения?How fast are exceptions?

Блок try/except чрезвычайно эффективен, если исключения не выбрасываются. Однако перехват исключения – дорогостоящая операция. В версиях Python до 2.0 было распространено использование такой идиомы:

try:
    value = mydict[key]
except KeyError:
    mydict[key] = getvalue(key)
    value = mydict[key]

Это имело смысл только тогда, когда вы ожидали, что ключ есть в словаре почти всё время. Если это было не так, вы писали так:

if key in mydict:
    value = mydict[key]
else:
    value = mydict[key] = getvalue(key)

Примечание

В Python 2.0 и выше это можно записать как value = mydict.setdefault(key, getvalue(key)).

Почему в Python нет оператора switch или case?Why isn’t there a switch or case statement in Python?

Это легко сделать с помощью последовательности if... elif... elif... else. Было несколько предложений по синтаксису switch, но пока нет консенсуса по поводу того, нужно ли и как реализовать проверки диапазонов. См. PEP 275 для полных подробностей и текущего статуса.

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

def function_1(...):
    ...

functions = {'a': function_1,
             'b': function_2,
             'c': self.method_1, ...}

func = functions[value]
func()

Для вызова методов объектов можно упростить ещё больше, используя встроенную функцию getattr() для получения методов с определённым именем:

def visit_a(self, ...):
    ...
...

def dispatch(self, value):
    method_name = 'visit_' + str(value)
    method = getattr(self, method_name)
    method()

Рекомендуется использовать префикс для имён методов, например visit_ в этом примере. Без такого префикса, если значения поступают из ненадёжного источника, злоумышленник сможет вызвать любой метод вашего объекта.

Нельзя ли эмулировать потоки в интерпретаторе вместо того, чтобы полагаться на реализацию потоков, зависящую от ОС?Can’t you emulate threads in the interpreter instead of relying on an OS-specific thread implementation?

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

Ответ 2: К счастью, существует Stackless Python, который имеет полностью переработанный цикл интерпретатора, избавляющийся от стека C.

Почему лямбда-выражения не могут содержать инструкции?Why can’t lambda expressions contain statements?

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

Функции в Python и так являются объектами первого класса, и их можно объявлять в локальной области видимости. Поэтому единственное преимущество использования lambda вместо локально определённой функции в том, что не нужно придумывать имя для функции – но это лишь локальная переменная, которой присваивается объект функции (который является объектом того же типа, что и результат лямбда-выражения)!

Можно ли скомпилировать Python в машинный код, C или другой язык?Can Python be compiled to machine code, C or some other language?

Cython компилирует модифицированную версию Python с необязательными аннотациями в расширения C. Nuitka – перспективный компилятор Python в код C++, нацеленный на поддержку полного языка Python. Для компиляции в Java можно рассмотреть VOC.

Как Python управляет памятью?How does Python manage memory?

Детали управления памятью в Python зависят от реализации. Стандартная реализация Python на C использует подсчёт ссылок для обнаружения недоступных объектов и другой механизм для сбора циклических ссылок, периодически выполняя алгоритм обнаружения циклов, который ищет недоступные циклы и удаляет задействованные объекты. Модуль gc предоставляет функции для выполнения сборки мусора, получения статистики отладки и настройки параметров сборщика.

Jython полагается на среду выполнения Java, поэтому используется сборщик мусора JVM. Это отличие может вызвать некоторые тонкие проблемы при переносе, если ваш код на Python зависит от поведения реализации с подсчётом ссылок.

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

import sys
sys.exc_clear()
sys.exc_traceback = sys.last_traceback = None

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

При отсутствии циклических ссылок и трассировок программам на Python не нужно явно управлять памятью.

Почему Python не использует более традиционную схему сборки мусора? Во-первых, это не является стандартной возможностью C, а значит, она непереносима. (Да, мы знаем о библиотеке Boehm GC. У неё есть куски ассемблерного кода для большинства распространённых платформ, но не для всех, и хотя она в основном прозрачна, она не полностью прозрачна; требуются патчи, чтобы заставить Python работать с ней.)

Traditional GC also becomes a problem when Python is embedded into other applications. While in a standalone Python it’s fine to replace the standard malloc() and free() with versions provided by the GC library, an application embedding Python may want to have its own substitute for malloc() and free(), and may not want Python’s. Right now, Python works with anything that implements malloc() and free() properly.

В Jython следующий код (который нормально работает в CPython), скорее всего, исчерпает файловые дескрипторы задолго до того, как закончится память:

for file in very_long_list_of_files:
    f = open(file)
    c = f.read(1)

При использовании текущей схемы подсчёта ссылок и деструкторов каждое новое присваивание f закрывает предыдущий файл. При использовании сборки мусора это не гарантируется. Если требуется написать код, который будет работать с любой реализацией Python, следует явно закрыть файл или использовать оператор with; это будет работать независимо от сборки мусора:

for file in very_long_list_of_files:
    with open(file) as f:
        c = f.read(1)

Почему не вся память освобождается при завершении Python?Why isn’t all memory freed when Python exits?

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

Чтобы заставить Python удалить определённые объекты при освобождении, используйте модуль atexit для запуска функции, которая принудительно выполнит эти удаления.

Почему существуют отдельные типы данных кортеж (tuple) и список (list)?Why are there separate tuple and list data types?

Списки и кортежи, хотя и похожи во многих отношениях, обычно используются принципиально по-разному. Кортежи можно рассматривать как аналоги записей Pascal или структур C; это небольшие коллекции связанных данных, которые могут быть разных типов и обрабатываются как группа. Например, декартова координата уместно представляется в виде кортежа из двух или трёх чисел.

Списки, с другой стороны, больше похожи на массивы в других языках. Они обычно содержат переменное количество объектов одного типа, которые обрабатываются по одному. Например, os.listdir('.') возвращает список строк, представляющих файлы в текущем каталоге. Функции, работающие с этим результатом, обычно не сломаются, если добавить в каталог ещё один или два файла.

Кортежи неизменяемы, то есть после создания кортежа нельзя заменить какой-либо его элемент новым значением. Списки изменяемы, то есть элементы списка всегда можно изменить. Только неизменяемые элементы можно использовать в качестве ключей словаря, поэтому в качестве ключей можно использовать только кортежи, но не списки.

Как реализованы списки в CPython?How are lists implemented in CPython?

Списки в CPython – это на самом деле массивы переменной длины, а не связные списки в стиле Lisp. Реализация использует непрерывный массив ссылок на другие объекты и хранит указатель на этот массив и его длину в структуре заголовка списка.

Это делает индексацию списка a[i] операцией, стоимость которой не зависит от размера списка или значения индекса.

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

Как реализованы словари в CPython?How are dictionaries implemented in CPython?

Словари в CPython реализованы как хэш-таблицы с изменяемым размером. По сравнению с B-деревьями, это обеспечивает лучшую производительность поиска (самой распространённой операции) в большинстве случаев, а реализация проще.

Словари работают путём вычисления хеш-кода для каждого ключа, хранящегося в словаре, с помощью встроенной функции hash(). Хеш-код сильно варьируется в зависимости от ключа; например, «Python» хешируется в -539294296, а «python», строка, отличающаяся на один бит, хешируется в 1142331976. Затем хеш-код используется для вычисления позиции во внутреннем массиве, где будет храниться значение. Если предположить, что все ключи имеют разные хеш-значения, это означает, что словари выполняют поиск ключа за постоянное время – O(1) в обозначениях компьютерных наук. Это также означает, что не поддерживается никакой упорядоченности ключей, и обход массива, как это делают .keys() и .items(), выводит содержимое словаря в некотором произвольном перемешанном порядке.

Почему ключи словарей должны быть неизменяемыми?Why must dictionary keys be immutable?

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

Если нужен словарь, индексируемый списком, просто преобразуйте список в кортеж; функция tuple(L) создаёт кортеж с теми же элементами, что и список L. Кортежи неизменяемы и поэтому могут использоваться в качестве ключей словаря.

Некоторые неприемлемые решения, которые предлагались:

  • Хэшировать списки по их адресу (идентификатору объекта). Это не работает, потому что если создать новый список с тем же значением, он не будет найден; например:

    mydict = {[1, 2]: '12'}
    print mydict[[1, 2]]
    

    будет вызывать исключение KeyError, потому что идентификатор [1, 2], использованный во второй строке, отличается от идентификатора в первой строке. Иными словами, ключи словаря следует сравнивать с помощью ==, а не is.

  • Создавать копию при использовании списка в качестве ключа. Это не работает, потому что список, будучи изменяемым объектом, может содержать ссылку на самого себя, и тогда код копирования попадёт в бесконечный цикл.

  • Разрешить списки в качестве ключей, но предупредить пользователя не изменять их. Это породило бы класс трудноотслеживаемых ошибок в программах, когда пользователь забыл или случайно изменил список. Это также нарушает важный инвариант словарей: каждое значение в d.keys() может использоваться в качестве ключа словаря.

  • Помечайте списки как доступные только для чтения, как только они используются в качестве ключа словаря. Проблема в том, что изменить значение может не только объект верхнего уровня: можно использовать кортеж, содержащий список, в качестве ключа. Добавление чего-либо в качестве ключа в словарь потребует пометить все достижимые оттуда объекты как доступные только для чтения – и снова самореферентные объекты могут вызвать бесконечный цикл.

Существует трюк, чтобы обойти это, если необходимо, но используйте его на свой страх и риск: можно обернуть изменяемую структуру в экземпляр класса, который имеет как метод __eq__(), так и __hash__(). Затем нужно убедиться, что хеш-значение для всех таких объектов-обёрток, находящихся в словаре (или другой структуре на основе хеша), остаётся фиксированным, пока объект находится в словаре (или другой структуре).

class ListWrapper:
    def __init__(self, the_list):
        self.the_list = the_list

    def __eq__(self, other):
        return self.the_list == other.the_list

    def __hash__(self):
        l = self.the_list
        result = 98767 - len(l)*555
        for i, el in enumerate(l):
            try:
                result = result + (hash(el) % 9999999) * 1001 + i
            except Exception:
                result = (result % 7777777) + i * 333
        return result

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

Кроме того, всегда должно выполняться условие: если o1 == o2 (то есть o1.__eq__(o2) is True), то hash(o1) == hash(o2) (то есть o1.__hash__() == o2.__hash__()), независимо от того, находится объект в словаре или нет. Если не соблюдать эти ограничения, словари и другие хеш-структуры будут работать некорректно.

В случае ListWrapper, когда объект-обёртка находится в словаре, обёрнутый список не должен изменяться, чтобы избежать аномалий. Не делайте этого, если вы не готовы тщательно обдумать требования и последствия их неправильного выполнения. Считайте себя предупреждёнными.

Почему list.sort() не возвращает отсортированный список?Why doesn’t list.sort() return the sorted list?

В ситуациях, где важна производительность, создание копии списка только для сортировки было бы расточительным. Поэтому list.sort() сортирует список на месте. Чтобы напомнить вам об этом, он не возвращает отсортированный список. Так вы не попадётесь в ловушку случайной перезаписи списка, когда вам нужна отсортированная копия, но также необходимо сохранить и неотсортированную версию.

В Python 2.4 была добавлена новая встроенная функция – sorted(). Эта функция создаёт новый список из переданного итерируемого объекта, сортирует его и возвращает. Например, вот как перебирать ключи словаря в отсортированном порядке:

for key in sorted(mydict):
    ...  # делайте что угодно с mydict[key]...

Как задать и обеспечить соблюдение спецификации интерфейса в Python?How do you specify and enforce an interface spec in Python?

Спецификация интерфейса модуля, предоставляемая такими языками, как C++ и Java, описывает прототипы методов и функций модуля. Многие считают, что проверка соблюдения спецификаций интерфейса на этапе компиляции помогает при построении больших программ.

В Python 2.6 добавлен модуль abc, позволяющий определять абстрактные базовые классы (ABC). Затем можно использовать isinstance() и issubclass() для проверки, реализует ли экземпляр или класс конкретный ABC. Модуль collections определяет набор полезных ABC, таких как Iterable, Container и MutableMapping.

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

Хороший набор тестов для модуля может одновременно служить регрессионным тестом, спецификацией интерфейса модуля и набором примеров. Многие модули Python можно запускать как скрипт для простого «самотестирования». Даже модули, использующие сложные внешние интерфейсы, часто можно тестировать изолированно, используя тривиальные «заглушки», эмулирующие внешний интерфейс. Модули doctest и unittest или сторонние тестовые фреймворки можно использовать для построения исчерпывающих наборов тестов, которые выполняют каждую строку кода в модуле.

Надлежащая дисциплина тестирования может помочь в создании больших сложных приложений на Python так же, как и спецификации интерфейса. Фактически, она может быть даже лучше, потому что спецификация интерфейса не может проверить некоторые свойства программы. Например, метод append() должен добавлять новые элементы в конец некоторого внутреннего списка; спецификация интерфейса не может проверить, что ваша реализация append() действительно делает это правильно, но проверить это свойство в наборе тестов тривиально.

Написание тестовых наборов очень полезно, и, возможно, стоит проектировать код с расчётом на лёгкое тестирование. Одна из набирающих популярность методик, разработка через тестирование (test-directed development), требует сначала написать части тестового набора, а затем уже писать сам код. Конечно, Python позволяет быть небрежным и вообще не писать тесты.

Почему в Python нет goto?Why is there no goto?

Исключения можно использовать для создания «структурированного goto», которое работает даже между вызовами функций. Многие считают, что исключения могут удобно эмулировать все разумные применения конструкций «go» или «goto» из C, Fortran и других языков. Например:

class label: pass  # объявить метку

try:
    ...
    if condition: raise label()  # перейти к метке
    ...
except label:  # куда перейти
    pass
...

Это не позволяет перепрыгнуть в середину цикла, но это обычно считается злоупотреблением goto в любом случае. Используйте с осторожностью.

Почему сырые строки (r-строки) не могут заканчиваться обратным слешем?Why can’t raw strings (r-strings) end with a backslash?

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

Сырые строки были разработаны для упрощения создания входных данных для обработчиков (в основном, движков регулярных выражений), которые хотят выполнять свою собственную обработку экранирования обратным слешем. Такие обработчики в любом случае считают непарный завершающий обратный слеш ошибкой, поэтому сырые строки это запрещают. Взамен они позволяют передать символ кавычки, экранируя его обратным слешем. Эти правила хорошо работают, когда r-строки используются по назначению.

Если вы пытаетесь построить пути Windows, учтите, что все системные вызовы Windows принимают и прямые слеши:

f = open("/mydir/file.txt")  # работает отлично!

Если вы пытаетесь построить путь для команды DOS, попробуйте, например, один из

dir = r"\this\is\my\dos\dir" "\\"
dir = r"\this\is\my\dos\dir\ "[:-1]
dir = "\\this\\is\\my\\dos\\dir\\"

Почему в Python нет оператора with для присваивания атрибутов?Why doesn’t Python have a “with” statement for attribute assignments?

В Python есть инструкция ‘with’, которая оборачивает выполнение блока, вызывая код при входе в блок и при выходе из него. В некоторых языках есть конструкция, которая выглядит так:

with obj:
    a = 1               # эквивалентно obj.a = 1
    total = total + 1   # obj.total = obj.total + 1

В Python такая конструкция была бы неоднозначной.

Другие языки, такие как Object Pascal, Delphi и C++, используют статическую типизацию, поэтому можно однозначно узнать, какому члену выполняется присваивание. В этом и заключается основная суть статической типизации – компилятор всегда знает область видимости каждой переменной на этапе компиляции.

Python использует динамическую типизацию. Невозможно заранее узнать, какой атрибут будет использован во время выполнения. Атрибуты-члены могут добавляться или удаляться из объектов на лету. Из-за этого невозможно при простом чтении понять, на какой атрибут идёт ссылка: локальный, глобальный или атрибут-член?

Например, рассмотрим следующий неполный фрагмент кода:

def foo(a):
    with a:
        print x

Фрагмент предполагает, что «a» должно иметь атрибут с именем «x». Однако в Python нет ничего, что говорило бы об этом интерпретатору. Что должно произойти, если «a» является, скажем, целым числом? Если существует глобальная переменная с именем «x», будет ли она использоваться внутри блока with? Как видите, динамическая природа Python делает такой выбор гораздо более сложным.

Однако основное преимущество «with» и подобных языковых возможностей (сокращение объёма кода) легко достигается в Python с помощью присваивания. Вместо:

function(args).mydict[index][index].a = 21
function(args).mydict[index][index].b = 42
function(args).mydict[index][index].c = 63

напишите это:

ref = function(args).mydict[index][index]
ref.a = 21
ref.b = 42
ref.c = 63

Это также даёт побочный эффект ускорения выполнения, так как привязки имён в Python разрешаются во время выполнения, и второй вариант требует разрешения только один раз.

Почему двоеточия обязательны для операторов if/while/def/class?Why are colons required for the if/while/def/class statements?

Двоеточие требуется в первую очередь для улучшения читаемости (один из результатов экспериментального языка ABC). Рассмотрим это:

if a == b
    print a

против

if a == b:
    print a

Обратите внимание, что второй вариант читается немного легче. Также обратите внимание, как двоеточие выделяет пример в этом ответе из FAQ; это стандартное использование в английском языке.

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

Почему Python допускает запятые в конце списков и кортежей?Why does Python allow commas at the end of lists and tuples?

Python позволяет добавлять завершающую запятую в конце списков, кортежей и словарей:

[1, 2, 3,]
('a', 'b', 'c',)
d = {
    "A": [1, 5],
    "B": [6, 7],  # последняя завершающая запятая необязательна, но считается хорошим стилем
}

Для этого есть несколько причин.

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

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

x = [
  "fee",
  "fie"
  "foo",
  "fum"
]

Этот список выглядит так, будто в нём четыре элемента, но на самом деле их три: «fee», «fiefoo» и «fum». Всегда добавляя запятую, можно избежать этого источника ошибок.

Разрешение завершающей запятой также может упростить программную генерацию кода.