design.md
1> **Источник:** https://python-all.ru/2.6/faq/design.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# FAQ по дизайну и истории89## Почему в Python для группировки инструкций используются отступы?1011Гвидо ван Россум считает, что использование отступов для группировки чрезвычайно элегантно и значительно повышает читаемость типичной программы на Python. Большинство людей со временем начинают любить эту особенность.1213Поскольку нет фигурных скобок, не может быть расхождений между группировкой, воспринимаемой парсером, и человеческим восприятием. Иногда программисты на C сталкиваются с фрагментом кода вроде такого:1415```python16if (x <= y)17 x++;18 y--;19z++;20```2122Выполняется только оператор `x++`, если условие истинно, но отступы заставляют думать иначе. Даже опытные программисты на C иногда долго смотрят на это, недоумевая, почему `y` уменьшается даже для `x > y`.2324Из-за отсутствия скобок начала и конца блока Python гораздо менее подвержен конфликтам стилей оформления кода. В C существует множество способов расстановки фигурных скобок. Если вы привыкли читать и писать код в одном стиле, вы будете чувствовать себя по меньшей мере неуютно, читая (или будучи вынужденным писать) код в другом стиле.2526Многие стили кодирования размещают открывающие/закрывающие скобки на отдельной строке. Это делает программы значительно длиннее и тратит ценное экранное пространство, затрудняя получение хорошего обзора программы. В идеале функция должна помещаться на одном экране (скажем, 20–30 строк). 20 строк Python могут сделать гораздо больше работы, чем 20 строк C. Это не только из-за отсутствия скобок begin/end – отсутствие объявлений и высокоуровневые типы данных также играют роль, – но синтаксис, основанный на отступах, безусловно, помогает.2728## Почему простые арифметические операции дают странные результаты?2930Смотрите следующий вопрос.3132## Почему вычисления с плавающей запятой так неточны?3334Люди часто удивляются результатам вроде такого:3536```python37>>> 1.2 - 1.0380.19999999999999999639```4041и считают это ошибкой в Python. Это не так. Это не связано с Python, а с тем, как базовая платформа C обрабатывает числа с плавающей запятой, и в конечном итоге с неточностями, возникающими при записи чисел в виде строки из фиксированного количества цифр.4243Внутреннее представление чисел с плавающей запятой использует фиксированное количество двоичных разрядов для представления десятичного числа. Некоторые десятичные числа не могут быть точно представлены в двоичной системе, что приводит к небольшим ошибкам округления.4445В десятичной математике есть много чисел, которые невозможно представить с фиксированным количеством десятичных цифр, например, 1/3 = 0.3333333333.......4647В двоичной системе 1/2 = 0.1, 1/4 = 0.01, 1/8 = 0.001 и т. д. .2 равно 2/10, что равно 1/5, что даёт двоичную дробь 0.001100110011001...4849Числа с плавающей запятой имеют точность всего 32 или 64 бита, поэтому цифры обрываются в какой-то момент, и полученное число в десятичной системе равно 0.199999999999999996, а не 0.2.5051Функция `repr()` числа с плавающей запятой выводит столько цифр, сколько необходимо, чтобы `eval(repr(f)) == f` было истинно для любого числа f с плавающей запятой. Функция `str()` выводит меньше цифр, и это часто даёт более разумное число, которое, вероятно, подразумевалось:5253```python54>>> 0.2550.2000000000000000156>>> print 0.2570.258```5960Одно из следствий этого – сравнение результата некоторых вычислений с числом с плавающей запятой через `==` может приводить к ошибкам. Незначительные неточности могут привести к тому, что `==` не сработает. Вместо этого нужно проверять, что разница между двумя числами меньше определённого порога:6162```python63epsilon = 0.0000000000001 # Допустимая малая погрешность64expected_result = 0.46566if expected_result-epsilon <= computation() <= expected_result+epsilon:67 ...68```6970Дополнительную информацию см. в главе [*Арифметика с плавающей запятой*](https://python-all.ru/2.6/tutorial/floatingpoint.html#tut-fp-issues) в учебнике Python.7172## Почему строки в Python неизменяемы?7374Есть несколько преимуществ.7576Первое – производительность: знание того, что строка неизменяема, позволяет выделить для неё память при создании, и требования к хранению фиксированы и неизменны. Это также одна из причин различия между кортежами и списками.7778Другое преимущество в том, что строки в Python считаются такими же «элементарными», как числа. Никакие действия не превратят значение 8 во что-то другое, и в Python никакие действия не превратят строку «eight» во что-то другое.7980## Почему 'self' нужно указывать явно в определениях и вызовах методов?8182Идея была заимствована из Modula-3. Она оказалась очень полезной по ряду причин.8384Во-первых, становится более очевидным, что вы используете метод или атрибут экземпляра, а не локальную переменную. Чтение `self.x` или `self.meth()` абсолютно ясно показывает, что используется переменная экземпляра или метод, даже если вы не знаете определение класса наизусть. В C++ вы можете примерно определить это по отсутствию объявления локальной переменной (предполагая, что глобальные переменные редки или легко узнаваемы) – но в Python нет объявлений локальных переменных, поэтому нужно искать определение класса, чтобы быть уверенным. Некоторые стандарты кодирования C++ и Java требуют, чтобы атрибуты экземпляра имели префикс `m_`, так что эта явность полезна и в этих языках.8586Во-вторых, это означает, что не требуется специального синтаксиса, если вы хотите явно сослаться или вызвать метод из определённого класса. В C++, если вы хотите использовать метод из базового класса, который переопределён в производном классе, нужно использовать оператор `::` – в Python вы можете написать `baseclass.methodname(self, <argument list>)`. Это особенно полезно для методов [`__init__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__init__) и в целом в случаях, когда метод производного класса хочет расширить метод базового класса с тем же именем и поэтому должен каким-то образом вызвать метод базового класса.8788Наконец, для переменных экземпляра это решает синтаксическую проблему с присваиванием: поскольку локальные переменные в Python – это (по определению!) те переменные, которым присваивается значение в теле функции (и которые не объявлены явно глобальными), должен быть способ сообщить интерпретатору, что присваивание предназначено для переменной экземпляра, а не для локальной переменной, и желательно, чтобы это было синтаксически (из соображений эффективности). C++ делает это через объявления, но в Python нет объявлений, и было бы жаль вводить их только для этой цели. Использование явного `self.var` отлично решает эту проблему. Аналогично, при использовании переменных экземпляра необходимость писать `self.var` означает, что ссылки на неквалифицированные имена внутри метода не должны искать в директориях экземпляра. Другими словами, локальные переменные и переменные экземпляра находятся в двух разных пространствах имён, и нужно указать Python, какое пространство имён использовать.8990## Почему нельзя использовать присваивание в выражении?9192Многие, привыкшие к C или Perl, жалуются, что хотят использовать эту идиому C:9394```c95while (line = readline(f)) {96 // сделать что-то со строкой97}98```99100тогда как в Python приходится писать так:101102```python103while True:104 line = f.readline()105 if not line:106 break107 ... # сделать что-то со строкой108```109110Причина, по которой в выражениях Python не допускается присваивание, – распространённая, трудновыявляемая ошибка в других языках, возникающая из-за такой конструкции:111112```c113if (x = 0) {114 // обработка ошибок115}116else {117 // код, который работает только для ненулевого x118}119```120121Ошибка – простая опечатка: было написано `x = 0`, что присваивает переменной `x` значение 0, в то время как предполагалось сравнение `x == 0`.122123Предлагалось множество альтернатив. Большинство из них – это хаки, которые экономят немного набора, но используют произвольный или непонятный синтаксис или ключевые слова и не проходят простой критерий для предложений по изменению языка: конструкция должна интуитивно подсказывать правильный смысл читателю, который с ней ещё не знаком.124125Интересное явление: большинство опытных программистов Python узнают идиому `while True` и, похоже, не сильно скучают по конструкции присваивания в выражении; только новички выражают сильное желание добавить это в язык.126127Существует альтернативный способ записи этого, который кажется привлекательным, но в целом менее надёжный, чем решение с “while True”:128129```python130line = f.readline()131while line:132 ... # сделать что-то со строкой...133 line = f.readline()134```135136Проблема в том, что если вы передумаете относительно того, как именно вы получаете следующую строку (например, захотите изменить её на `sys.stdin.readline()`), вам придётся не забыть изменить два места в программе – второе вхождение скрыто внизу цикла.137138Лучший подход – использовать итераторы, что позволяет перебирать объекты с помощью оператора `for`. Например, в текущей версии Python файловые объекты поддерживают протокол итератора, поэтому теперь можно просто написать:139140```python141for line in f:142 ... # сделать что-то со строкой...143```144145## Почему для одних возможностей Python использует методы (например, list.index()), а для других – функции (например, len(list))?146147Основная причина в истории. Функции использовались для тех операций, которые были общими для группы типов и должны были работать даже для объектов, у которых вообще не было методов (например, кортежей). Также удобно иметь функцию, которую можно легко применить к аморфной коллекции объектов при использовании функциональных возможностей Python (`map()`, `apply()` и других).148149На самом деле, реализация `len()`, `max()`, `min()` как встроенных функций требует меньше кода, чем их реализация в качестве методов для каждого типа. Можно спорить об отдельных случаях, но это часть Python, и сейчас уже слишком поздно вносить такие фундаментальные изменения. Функции должны остаться, чтобы избежать массового нарушения работоспособности кода.150151> **Примечание**152>153> Для строковых операций Python перешёл от внешних функций (модуль `string`) к методам. Однако `len()` по-прежнему является функцией.154155## Почему join() – это метод строки, а не списка или кортежа?156157Строки стали гораздо больше похожи на другие стандартные типы начиная с Python 1.6, когда были добавлены методы, предоставляющие те же возможности, которые всегда были доступны через функции модуля string. Большинство этих новых методов были широко приняты, но один из них, который, по-видимому, вызывает дискомфорт у некоторых программистов, – это:158159```python160", ".join(['1', '2', '4', '8', '16'])161```162163который даёт результат:164165```python166"1, 2, 4, 8, 16"167```168169Есть два распространённых аргумента против этого использования.170171Первый звучит примерно так: «Выглядит действительно уродливо использовать метод строкового литерала (строковой константы)», на что можно ответить: возможно, но строковый литерал – это просто фиксированное значение. Если методы разрешены для имён, связанных со строками, нет логической причины делать их недоступными для литералов.172173Второе возражение обычно формулируется так: «Я действительно говорю последовательности объединить свои элементы с помощью строковой константы». К сожалению, это не так. По какой-то причине, похоже, гораздо меньше проблем с тем, чтобы сделать [`split()`](https://python-all.ru/2.6/library/stdtypes.html#str.split) методом строки, так как в этом случае легко увидеть, что174175```python176"1, 2, 4, 8, 16".split(", ")177```178179является инструкцией строковому литералу вернуть подстроки, разделённые заданным разделителем (или, по умолчанию, произвольными последовательностями пробельных символов). В этом случае строка Unicode возвращает список строк Unicode, строка ASCII возвращает список строк ASCII, и все довольны.180181[`join()`](https://python-all.ru/2.6/library/stdtypes.html#str.join) – это строковый метод, потому что при его использовании вы указываете строке-разделителю перебрать последовательность строк и вставить себя между соседними элементами. Этот метод можно использовать с любым аргументом, который подчиняется правилам для объектов последовательности, включая любые новые классы, которые вы можете определить самостоятельно.182183Поскольку это строковый метод, он может работать как со строками Unicode, так и с обычными строками ASCII. Если бы `join()` был методом типов последовательности, то типам последовательности пришлось бы решать, какой тип строки возвращать в зависимости от типа разделителя.184185Если ни один из этих аргументов вас не убеждает, то пока вы можете продолжать использовать функцию `join()` из модуля string, которая позволяет писать186187```python188string.join(['1', '2', '4', '8', '16'], ", ")189```190191## Насколько быстры исключения?192193Блок try/except очень эффективен. Собственно перехват исключения обходится дорого. В версиях Python до 2.0 было принято использовать такой идиоматический код:194195```python196try:197 value = mydict[key]198except KeyError:199 mydict[key] = getvalue(key)200 value = mydict[key]201```202203Это имело смысл только тогда, когда вы ожидали, что ключ есть в словаре почти всё время. Если это было не так, вы писали так:204205```python206if mydict.has_key(key):207 value = mydict[key]208else:209 mydict[key] = getvalue(key)210 value = mydict[key]211```212213> **Примечание**214>215> В Python 2.0 и выше это можно записать как `value = mydict.setdefault(key, getvalue(key))`.216217## Почему в Python нет оператора switch или case?218219Это можно легко сделать с помощью последовательности `if... elif... elif... else`. Было несколько предложений синтаксиса switch, но пока нет консенсуса относительно того, нужно ли и как реализовать проверку диапазонов. Смотрите [**PEP 275**](https://python-all.ru/2.6/faq/design.html) для полных подробностей и текущего статуса.220221Для случаев, когда нужно выбрать из очень большого числа возможностей, можно создать словарь, отображающий значения вариантов на вызываемые функции. Например:222223```python224def function_1(...):225 ...226227functions = {'a': function_1,228 'b': function_2,229 'c': self.method_1, ...}230231func = functions[value]232func()233```234235Для вызова методов объектов можно ещё больше упростить, используя встроенную функцию [`getattr()`](https://python-all.ru/2.6/library/functions.html#getattr) для получения методов с определённым именем:236237```python238def visit_a(self, ...):239 ...240...241242def dispatch(self, value):243 method_name = 'visit_' + str(value)244 method = getattr(self, method_name)245 method()246```247248Рекомендуется использовать префикс для имён методов, например `visit_` в этом примере. Без такого префикса, если значения поступают из ненадёжного источника, злоумышленник сможет вызвать любой метод вашего объекта.249250## Нельзя ли эмулировать потоки в интерпретаторе, вместо того чтобы полагаться на OS-зависимую реализацию потоков?251252Ответ 1: К сожалению, интерпретатор помещает как минимум один кадр стека C для каждого кадра стека Python. Кроме того, расширения могут вызывать Python в почти случайные моменты. Поэтому полная реализация потоков требует поддержки потоков на уровне C.253254Ответ 2: К счастью, существует [Stackless Python](https://python-all.ru/2.6/faq/design.html), с полностью переработанным циклом интерпретатора, который избегает C-стека. Он всё ещё экспериментальный, но выглядит многообещающе. Хотя он двоично совместим со стандартным Python, пока неясно, войдёт ли Stackless в ядро – возможно, он слишком революционен.255256## Почему лямбда-формы не могут содержать инструкции?257258Лямбда-формы в Python не могут содержать инструкции, потому что синтаксическая структура Python не позволяет вкладывать инструкции в выражения. Однако в Python это не является серьёзной проблемой. В отличие от лямбда-форм в других языках, которые добавляют функциональность, лямбды в Python – это просто сокращённая запись на случай, если вам лень определять функцию.259260Функции в Python уже являются объектами первого класса и могут объявляться в локальной области видимости. Поэтому единственное преимущество использования лямбда-формы вместо локально определённой функции – это то, что не нужно придумывать имя для функции. Но имя – это просто локальная переменная, которой присваивается объект функции (точно тот же тип объекта, который возвращает лямбда-форма)!261262## Можно ли скомпилировать Python в машинный код, C или другой язык?263264Нелегко. Высокоуровневые типы данных Python, динамическая типизация объектов и вызов интерпретатора во время выполнения (с помощью [`eval()`](https://python-all.ru/2.6/library/functions.html#eval) или [`exec`](https://python-all.ru/2.6/reference/simple_stmts.html#exec)) вместе означают, что «скомпилированная» программа на Python, вероятно, будет состоять в основном из вызовов к среде выполнения Python, даже для таких, казалось бы, простых операций, как `x+1`.265266Несколько проектов, описанных в новостной группе Python или на прошлых [конференциях по Python](https://python-all.ru/2.6/faq/design.html), показали, что такой подход возможен, хотя достигнутое ускорение пока скромное (например, 2 раза). Jython использует ту же стратегию для компиляции в байт-код Java. (Джим Хугунин продемонстрировал, что в сочетании с анализом всей программы для небольших демонстрационных примеров возможно ускорение в 1000 раз. Подробнее см. в материалах [конференции по Python 1997 года](https://python-all.ru/2.6/faq/design.html).)267268Внутренне исходный код Python всегда транслируется в байт-код, и этот байт-код затем выполняется виртуальной машиной Python. Чтобы избежать накладных расходов на многократный разбор и трансляцию модулей, которые редко меняются, этот байт-код записывается в файл с именем, оканчивающимся на «.pyc» всякий раз, когда модуль разбирается. Когда соответствующий .py-файл изменяется, он разбирается и транслируется заново, а файл .pyc перезаписывается.269270После загрузки файла .pyc разницы в производительности нет, так как байт-код, считанный из файла .pyc, в точности совпадает с байт-кодом, созданным при прямой трансляции. Единственное отличие в том, что загрузка кода из файла .pyc выполняется быстрее, чем разбор и трансляция .py-файла, поэтому наличие предварительно скомпилированных .pyc-файлов ускоряет запуск скриптов Python. При желании модуль Lib/compileall.py можно использовать для создания валидных .pyc-файлов для заданного набора модулей.271272Обратите внимание, что основной скрипт, выполняемый Python, даже если его имя файла оканчивается на .py, не компилируется в файл .pyc. Он компилируется в байт-код, но байт-код не сохраняется в файл. Обычно основные скрипты довольно короткие, поэтому это не сильно сказывается на скорости.273274Существует также несколько программ, которые упрощают смешивание кода Python и C разными способами для повышения производительности. См., например, [Psyco](https://python-all.ru/2.6/faq/design.html), [Pyrex](https://python-all.ru/2.6/faq/design.html), [PyInline](https://python-all.ru/2.6/faq/design.html), [Py2Cmod](https://python-all.ru/2.6/faq/design.html) и [Weave](https://python-all.ru/2.6/faq/design.html).275276## Как Python управляет памятью?277278Детали управления памятью в Python зависят от реализации. Стандартная реализация Python на C использует подсчёт ссылок для обнаружения недоступных объектов и другой механизм для сбора циклических ссылок, периодически выполняя алгоритм обнаружения циклов, который ищет недоступные циклы и удаляет вовлечённые объекты. Модуль [`gc`](https://python-all.ru/2.6/library/gc.html#module-gc) предоставляет функции для выполнения сборки мусора, получения статистики отладки и настройки параметров сборщика.279280Jython полагается на среду выполнения Java, поэтому используется сборщик мусора JVM. Это отличие может вызвать некоторые тонкие проблемы при переносе, если ваш код на Python зависит от поведения реализации с подсчётом ссылок.281282Иногда объекты временно застревают в трассировках и поэтому не освобождаются, когда можно было бы ожидать. Очистите трассировки с помощью:283284```python285import sys286sys.exc_clear()287sys.exc_traceback = sys.last_traceback = None288```289290Трассировки используются для сообщения об ошибках, реализации отладчиков и связанных вещей. Они содержат часть состояния программы, извлечённую во время обработки исключения (обычно самого последнего исключения).291292При отсутствии циклических ссылок и трассировок программам на Python не нужно явно управлять памятью.293294Почему Python не использует более традиционную схему сборки мусора? Во-первых, это не является стандартной возможностью C, а значит, она непереносима. (Да, мы знаем о библиотеке Boehm GC. У неё есть куски ассемблерного кода для *большинства* распространённых платформ, но не для всех, и хотя она в основном прозрачна, она не полностью прозрачна; требуются патчи, чтобы заставить Python работать с ней.)295296Traditional 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.297298В Jython следующий код (который нормально работает в CPython), скорее всего, исчерпает файловые дескрипторы задолго до того, как закончится память:299300```python301for file in very_long_list_of_files:302 f = open(file)303 c = f.read(1)304```305306При использовании текущей схемы подсчёта ссылок и деструкторов каждое новое присваивание переменной f закрывает предыдущий файл. При использовании сборки мусора это не гарантируется. Если вы хотите написать код, который будет работать с любой реализацией Python, следует явно закрывать файл или использовать оператор [`with`](https://python-all.ru/2.6/reference/compound_stmts.html#with); это будет работать независимо от сборки мусора:307308```python309for file in very_long_list_of_files:310 with open(file) as f:311 c = f.read(1)312```313314## Почему при выходе из Python освобождается не вся память?315316Объекты, на которые есть ссылки из глобальных пространств имён модулей Python, не всегда освобождаются при завершении Python. Это может происходить при наличии циклических ссылок. Также существуют определённые фрагменты памяти, выделяемые библиотекой C, которые невозможно освободить (например, инструмент вроде Purify будет жаловаться на них). Однако Python активно очищает память при завершении и старается уничтожить каждый объект.317318Если вы хотите принудительно заставить Python удалять определённые объекты при освобождении памяти, используйте модуль [`atexit`](https://python-all.ru/2.6/library/atexit.html#module-atexit) для запуска функции, которая выполнит эти удаления.319320## Почему существуют отдельные типы данных – кортеж и список?321322Списки и кортежи, хотя и похожи во многих отношениях, обычно используются принципиально по-разному. Кортежи можно рассматривать как аналоги записей Pascal или структур C; это небольшие коллекции связанных данных, которые могут быть разных типов и обрабатываются как группа. Например, декартова координата уместно представляется в виде кортежа из двух или трёх чисел.323324Списки, с другой стороны, больше похожи на массивы в других языках. Они обычно содержат переменное количество объектов одного типа, с которыми работают по одному. Например, `os.listdir('.')` возвращает список строк, представляющих файлы в текущем каталоге. Функции, работающие с этим выводом, обычно не сломаются, если вы добавите в каталог ещё один-два файла.325326Кортежи неизменяемы, то есть после создания кортежа нельзя заменить какой-либо его элемент новым значением. Списки изменяемы, то есть элементы списка всегда можно изменить. Только неизменяемые элементы можно использовать в качестве ключей словаря, поэтому в качестве ключей можно использовать только кортежи, но не списки.327328## Как реализованы списки?329330Списки Python – это на самом деле массивы переменной длины, а не связные списки в стиле Lisp. Реализация использует непрерывный массив ссылок на другие объекты и хранит указатель на этот массив и его длину в структуре заголовка списка.331332Это делает индексацию списка `a[i]` операцией, стоимость которой не зависит ни от размера списка, ни от значения индекса.333334При добавлении или вставке элементов массив ссылок изменяет размер. Применяется некоторая хитрость для повышения производительности при многократном добавлении: когда массив нужно увеличить, выделяется дополнительное пространство, чтобы следующие несколько раз не требовалось реально изменять размер.335336## Как реализованы словари?337338Словари Python реализованы как изменяемые хеш-таблицы. По сравнению с B-деревьями это обеспечивает лучшую производительность поиска (самой частой операции) в большинстве случаев, а реализация проще.339340Словари работают путём вычисления хеш-кода для каждого ключа, хранящегося в словаре, с помощью встроенной функции [`hash()`](https://python-all.ru/2.6/library/functions.html#hash). Хеш-код сильно варьируется в зависимости от ключа; например, «Python» даёт хеш -539294296, а «python» (строка, отличающаяся на один бит) даёт 1142331976. Затем хеш-код используется для вычисления позиции во внутреннем массиве, где будет храниться значение. Если предположить, что все ключи имеют разные хеш-значения, это означает, что словари выполняют поиск ключа за постоянное время – O(1) в обозначениях компьютерных наук. Это также означает, что порядок ключей не сохраняется, и обход массива с помощью `.keys()` и `.items()` выдаёт содержимое словаря в произвольном, перемешанном порядке.341342## Почему ключи словаря должны быть неизменяемыми?343344Реализация словарей на основе хэш-таблиц использует хэш-значение, вычисленное из значения ключа, для поиска ключа. Если бы ключ был изменяемым объектом, его значение могло бы измениться, и, следовательно, его хэш также мог бы измениться. Но поскольку тот, кто изменяет объект-ключ, не может знать, что он используется в качестве ключа словаря, он не может переместить запись в словаре. Тогда при попытке найти тот же объект в словаре он не будет найден, потому что его хэш-значение стало другим. Если попытаться найти старое значение, оно тоже не будет найдено, поскольку значение объекта, находящегося в той хэш-корзине, будет другим.345346Если вам нужен словарь, индексируемый списком, просто преобразуйте список в кортеж; функция `tuple(L)` создаёт кортеж с теми же элементами, что и список `L`. Кортежи неизменяемы и поэтому могут использоваться в качестве ключей словаря.347348Некоторые неприемлемые решения, которые предлагались:349350- Хэшировать списки по их адресу (идентификатору объекта). Это не работает, потому что если создать новый список с тем же значением, он не будет найден; например:351352 ```python353 mydict = {[1, 2]: '12'}354 print mydict[[1, 2]]355 ```356357 вызовет исключение KeyError, потому что идентификатор `[1, 2]`, используемый во второй строке, отличается от идентификатора в первой строке. Другими словами, ключи словаря следует сравнивать с помощью `==`, а не с помощью [`is`](https://python-all.ru/2.6/reference/expressions.html#is).358- Создавать копию при использовании списка в качестве ключа. Это не работает, потому что список, будучи изменяемым объектом, может содержать ссылку на самого себя, и тогда код копирования попадёт в бесконечный цикл.359- Разрешить списки в качестве ключей, но предупредить пользователя не изменять их. Это позволило бы появлению класса трудно отслеживаемых ошибок в программах, когда вы забыли или случайно изменили список. Это также нарушает важное свойство словарей: каждое значение в `d.keys()` может использоваться как ключ словаря.360- Помечайте списки как доступные только для чтения, как только они используются в качестве ключа словаря. Проблема в том, что изменить значение может не только объект верхнего уровня: можно использовать кортеж, содержащий список, в качестве ключа. Добавление чего-либо в качестве ключа в словарь потребует пометить все достижимые оттуда объекты как доступные только для чтения – и снова самореферентные объекты могут вызвать бесконечный цикл.361362Существует трюк, позволяющий обойти это, если необходимо, но используйте его на свой страх и риск: Можно обернуть изменяемую структуру в экземпляр класса, у которого есть и метод [`__eq__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__eq__), и метод [`__hash__()`](https://python-all.ru/2.6/reference/datamodel.html#object.__hash__). Затем нужно убедиться, что хеш-значение для всех таких объектов-обёрток, находящихся в словаре (или другой структуре на основе хеша), остаётся неизменным, пока объект находится в словаре (или другой структуре).363364```python365class ListWrapper:366 def __init__(self, the_list):367 self.the_list = the_list368 def __eq__(self, other):369 return self.the_list == other.the_list370 def __hash__(self):371 l = self.the_list372 result = 98767 - len(l)*555373 for i, el in enumerate(l):374 try:375 result = result + (hash(el) % 9999999) * 1001 + i376 except Exception:377 result = (result % 7777777) + i * 333378 return result379```380381Обратите внимание, что вычисление хеша усложняется тем, что некоторые элементы списка могут быть нехешируемыми, а также возможностью арифметического переполнения.382383Кроме того, всегда должно выполняться условие: если `o1 == o2` (т.е. `o1.__eq__(o2) is True`), то `hash(o1) == hash(o2)` (т.е. `o1.__hash__() == o2.__hash__()`), независимо от того, находится объект в словаре или нет. Если не соблюдать эти ограничения, словари и другие структуры на основе хеша будут работать некорректно.384385В случае ListWrapper, когда объект-обёртка находится в словаре, обёрнутый список не должен изменяться, чтобы избежать аномалий. Не делайте этого, если вы не готовы тщательно обдумать требования и последствия их неправильного выполнения. Считайте себя предупреждёнными.386387## Почему list.sort() не возвращает отсортированный список?388389В ситуациях, когда производительность важна, создание копии списка только для его сортировки было бы расточительством. Поэтому `list.sort()` сортирует список на месте. Чтобы напомнить вам об этом, он не возвращает отсортированный список. Так вы случайно не перезапишете список, когда вам нужна отсортированная копия, но при этом нужно сохранить и исходный.390391В Python 2.4 была добавлена новая встроенная функция – [`sorted()`](https://python-all.ru/2.6/library/functions.html#sorted). Эта функция создаёт новый список из переданного итерируемого объекта, сортирует его и возвращает. Например, вот как перебрать ключи словаря в отсортированном порядке:392393```python394for key in sorted(mydict):395 ... # делайте что угодно с mydict[key]...396```397398## Как указать и обеспечить соблюдение спецификации интерфейса в Python?399400Спецификация интерфейса модуля, предоставляемая такими языками, как C++ и Java, описывает прототипы методов и функций модуля. Многие считают, что проверка соблюдения спецификаций интерфейса на этапе компиляции помогает при построении больших программ.401402Python 2.6 добавляет модуль [`abc`](https://python-all.ru/2.6/library/abc.html#module-abc), который позволяет определять абстрактные базовые классы (ABC). Затем можно использовать [`isinstance()`](https://python-all.ru/2.6/library/functions.html#isinstance) и [`issubclass()`](https://python-all.ru/2.6/library/functions.html#issubclass) для проверки, реализует ли экземпляр или класс конкретный ABC. Модуль [`collections`](https://python-all.ru/2.6/library/collections.html#module-collections) определяет набор полезных ABC, таких как `Iterable`, `Container` и `MutableMapping`.403404В Python многие преимущества спецификаций интерфейсов достигаются за счёт правильной дисциплины тестирования компонентов. Также есть инструмент PyChecker, который помогает находить проблемы, вызванные наследованием.405406Хороший набор тестов для модуля может одновременно служить регрессионным тестом, спецификацией интерфейса модуля и набором примеров. Многие модули Python можно запускать как скрипт для выполнения простого «самотестирования». Даже модули, использующие сложные внешние интерфейсы, часто можно тестировать изолированно, применяя тривиальные «заглушки», эмулирующие внешний интерфейс. Модули [`doctest`](https://python-all.ru/2.6/library/doctest.html#module-doctest) и [`unittest`](https://python-all.ru/2.6/library/unittest.html#module-unittest) или сторонние тестовые фреймворки можно использовать для построения исчерпывающих наборов тестов, проверяющих каждую строку кода в модуле.407408Правильная дисциплина тестирования может помочь в создании больших сложных приложений на Python не хуже, чем спецификации интерфейсов. На самом деле она может быть даже лучше, поскольку спецификация интерфейса не может проверить некоторые свойства программы. Например, метод `append()` ожидается, что он добавляет новые элементы в конец некоторого внутреннего списка; спецификация интерфейса не может проверить, что ваша реализация `append()` действительно делает это правильно, но в наборе тестов проверить это свойство тривиально.409410Написание тестовых наборов очень полезно, и, возможно, стоит проектировать код с расчётом на лёгкое тестирование. Одна из набирающих популярность методик, разработка через тестирование (test-directed development), требует сначала написать части тестового набора, а затем уже писать сам код. Конечно, Python позволяет быть небрежным и вообще не писать тесты.411412## Почему значения по умолчанию разделяются между объектами?413414Подобная ошибка часто подстерегает начинающих программистов. Рассмотрим следующую функцию:415416```python417def foo(mydict={}): # Опасность: общая ссылка на один словарь для всех вызовов418 ... compute something ...419 mydict[key] = value420 return mydict421```422423При первом вызове этой функции `mydict` содержит один элемент. Во второй раз `mydict` содержит два элемента, потому что когда `foo()` начинает выполняться, `mydict` уже содержит один элемент.424425Часто ожидается, что при вызове функции для значений по умолчанию создаются новые объекты. Но это не так. Значения по умолчанию создаются ровно один раз, при определении функции. Если этот объект изменяется (как словарь в данном примере), то последующие вызовы функции будут обращаться к этому изменённому объекту.426427По определению, неизменяемые объекты, такие как числа, строки, кортежи и `None`, защищены от изменений. Изменения изменяемых объектов, таких как словари, списки и экземпляры классов, могут привести к путанице.428429Из-за этой особенности рекомендуется не использовать изменяемые объекты в качестве значений по умолчанию. Вместо этого используйте `None` в качестве значения по умолчанию, а внутри функции проверяйте, равен ли параметр `None`, и создавайте новый список/словарь/что угодно, если это так. Например, не пишите:430431```python432def foo(mydict={}):433 ...434```435436а:437438```python439def foo(mydict=None):440 if mydict is None:441 mydict = {} # создать новый словарь для локального пространства имён442```443444Эта возможность может быть полезной. Если есть функция, вычисление которой требует много времени, распространённый приём – кэшировать параметры и результат каждого вызова функции, а при повторном запросе тех же значений возвращать сохранённый результат. Это называется «мемоизация» и может быть реализовано так:445446```python447# Callers will never provide a third parameter for this function.448def expensive (arg1, arg2, _cache={}):449 if (arg1, arg2) in _cache:450 return _cache[(arg1, arg2)]451452 # Calculate the value453 result = ... expensive computation ...454 _cache[(arg1, arg2)] = result # Store result in the cache455 return result456```457458Вместо значения по умолчанию можно использовать глобальную переменную со словарём; это дело вкуса.459460## Почему нет goto?461462Исключения можно использовать для создания «структурированного goto», которое работает даже между вызовами функций. Многие считают, что исключения могут удобно эмулировать все разумные применения конструкций «go» или «goto» из C, Fortran и других языков. Например:463464```python465class label: pass # объявить метку466467try:468 ...469 if (condition): raise label() # перейти к метке470 ...471except label: # куда перейти472 pass473...474```475476Это не позволяет перепрыгнуть в середину цикла, но это обычно считается злоупотреблением goto в любом случае. Используйте с осторожностью.477478## Почему сырые строки (r-strings) не могут заканчиваться обратной косой чертой?479480Более точно, они не могут заканчиваться нечётным количеством обратных слешей: непарный обратный слеш в конце экранирует закрывающий символ кавычки, оставляя строку незавершённой.481482Сырые строки были разработаны для упрощения создания входных данных для обработчиков (в основном, движков регулярных выражений), которые хотят выполнять свою собственную обработку экранирования обратным слешем. Такие обработчики в любом случае считают непарный завершающий обратный слеш ошибкой, поэтому сырые строки это запрещают. Взамен они позволяют передать символ кавычки, экранируя его обратным слешем. Эти правила хорошо работают, когда r-строки используются по назначению.483484Если вы пытаетесь построить пути Windows, учтите, что все системные вызовы Windows принимают и прямые слеши:485486```python487f = open("/mydir/file.txt") # работает отлично!488```489490Если вы пытаетесь построить путь для команды DOS, попробуйте, например, один из491492```python493dir = r"\this\is\my\dos\dir" "\\"494dir = r"\this\is\my\dos\dir\ "[:-1]495dir = "\\this\\is\\my\\dos\\dir\\"496```497498## Почему в Python нет оператора “with” для присваивания атрибутов?499500В Python есть инструкция ‘with’, которая оборачивает выполнение блока, вызывая код при входе в блок и при выходе из него. В некоторых языках есть конструкция, которая выглядит так:501502```python503with obj:504 a = 1 # эквивалентно obj.a = 1505 total = total + 1 # obj.total = obj.total + 1506```507508В Python такая конструкция была бы неоднозначной.509510Другие языки, такие как Object Pascal, Delphi и C++, используют статическую типизацию, поэтому можно однозначно узнать, какому члену выполняется присваивание. В этом и заключается основная суть статической типизации – компилятор *всегда* знает область видимости каждой переменной на этапе компиляции.511512Python использует динамическую типизацию. Невозможно заранее узнать, какой атрибут будет использован во время выполнения. Атрибуты-члены могут добавляться или удаляться из объектов на лету. Из-за этого невозможно при простом чтении понять, на какой атрибут идёт ссылка: локальный, глобальный или атрибут-член?513514Например, рассмотрим следующий неполный фрагмент кода:515516```python517def foo(a):518 with a:519 print x520```521522Фрагмент предполагает, что «a» должно иметь атрибут с именем «x». Однако в Python нет ничего, что говорило бы об этом интерпретатору. Что должно произойти, если «a» является, скажем, целым числом? Если существует глобальная переменная с именем «x», будет ли она использоваться внутри блока with? Как видите, динамическая природа Python делает такой выбор гораздо более сложным.523524Однако основное преимущество «with» и подобных языковых возможностей (сокращение объёма кода) легко достигается в Python с помощью присваивания. Вместо:525526```python527function(args).mydict[index][index].a = 21528function(args).mydict[index][index].b = 42529function(args).mydict[index][index].c = 63530```531532напишите это:533534```python535ref = function(args).mydict[index][index]536ref.a = 21537ref.b = 42538ref.c = 63539```540541Это также даёт побочный эффект ускорения выполнения, так как привязки имён в Python разрешаются во время выполнения, и второй вариант требует разрешения только один раз.542543## Почему двоеточия обязательны для операторов if/while/def/class?544545Двоеточие требуется в первую очередь для улучшения читаемости (один из результатов экспериментального языка ABC). Рассмотрим это:546547```python548if a == b549 print a550```551552против553554```python555if a == b:556 print a557```558559Обратите внимание, что второй вариант читается немного легче. Также обратите внимание, как двоеточие выделяет пример в этом ответе из FAQ; это стандартное использование в английском языке.560561Ещё одна второстепенная причина заключается в том, что двоеточие упрощает работу редакторов с подсветкой синтаксиса; они могут искать двоеточия, чтобы решить, когда нужно увеличить отступ, вместо более тщательного разбора текста программы.562563## Почему Python разрешает запятые в конце списков и кортежей?564565Python позволяет добавлять завершающую запятую в конце списков, кортежей и словарей:566567```python568[1, 2, 3,]569('a', 'b', 'c',)570d = {571 "A": [1, 5],572 "B": [6, 7], # последняя завершающая запятая необязательна, но считается хорошим стилем573}574```575576Для этого есть несколько причин.577578Когда литерал списка, кортежа или словаря разбит на несколько строк, добавлять новые элементы проще, потому что не нужно помнить о добавлении запятой в предыдущей строке. Строки также можно сортировать в редакторе, не создавая синтаксической ошибки.579580Случайный пропуск запятой может привести к ошибкам, которые трудно диагностировать. Например:581582```python583x = [584 "fee",585 "fie"586 "foo",587 "fum"588]589```590591Этот список выглядит так, будто в нём четыре элемента, но на самом деле их три: «fee», «fiefoo» и «fum». Всегда добавляя запятую, можно избежать этого источника ошибок.592593Разрешение завершающей запятой также может упростить программную генерацию кода.594