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

design.md

481 строк · 64.8 КБ · обычная страница · сырой текст · скачать

1> **Источник:** https://python-all.ru/3/faq/design.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# [Часто задаваемые вопросы по дизайну и истории](https://python-all.ru/3/faq/design.html#id2)89Содержание [Вопросы по дизайну и истории](https://python-all.ru/3/faq/design.html#design-and-history-faq) [Почему в Python для группировки инструкций используются отступы?](https://python-all.ru/3/faq/design.html#why-does-python-use-indentation-for-grouping-of-statements) [Почему при простых арифметических операциях получаются странные результаты?](https://python-all.ru/3/faq/design.html#why-am-i-getting-strange-results-with-simple-arithmetic-operations) [Почему вычисления с плавающей запятой такие неточные?](https://python-all.ru/3/faq/design.html#why-are-floating-point-calculations-so-inaccurate) [Почему строки в Python неизменяемы?](https://python-all.ru/3/faq/design.html#why-are-python-strings-immutable) [Почему self нужно явно указывать в определениях и вызовах методов?](https://python-all.ru/3/faq/design.html#why-must-self-be-used-explicitly-in-method-definitions-and-calls) [Почему в выражении нельзя использовать присваивание?](https://python-all.ru/3/faq/design.html#why-can-t-i-use-an-assignment-in-an-expression) [Почему в Python для одних возможностей используются методы (например, list.index()), а для других – функции (например, len(list))?](https://python-all.ru/3/faq/design.html#why-does-python-use-methods-for-some-functionality-e-g-list-index-but-functions-for-other-e-g-len-list) [Почему join() – метод строки, а не списка или кортежа?](https://python-all.ru/3/faq/design.html#why-is-join-a-string-method-instead-of-a-list-or-tuple-method) [Насколько быстры исключения?](https://python-all.ru/3/faq/design.html#how-fast-are-exceptions) [Почему в Python нет оператора switch или case?](https://python-all.ru/3/faq/design.html#why-isn-t-there-a-switch-or-case-statement-in-python) [Разве нельзя эмулировать потоки в интерпретаторе, вместо того чтобы полагаться на реализацию потоков, зависящую от ОС?](https://python-all.ru/3/faq/design.html#can-t-you-emulate-threads-in-the-interpreter-instead-of-relying-on-an-os-specific-thread-implementation) [Почему лямбда-выражения не могут содержать инструкции?](https://python-all.ru/3/faq/design.html#why-can-t-lambda-expressions-contain-statements) [Можно ли скомпилировать Python в машинный код, C или другой язык?](https://python-all.ru/3/faq/design.html#can-python-be-compiled-to-machine-code-c-or-some-other-language) [Как Python управляет памятью?](https://python-all.ru/3/faq/design.html#how-does-python-manage-memory) [Почему CPython не использует более традиционную схему сборки мусора?](https://python-all.ru/3/faq/design.html#why-doesn-t-cpython-use-a-more-traditional-garbage-collection-scheme) [Почему при завершении CPython освобождается не вся память?](https://python-all.ru/3/faq/design.html#why-isn-t-all-memory-freed-when-cpython-exits) [Почему существуют отдельные типы данных кортеж и список?](https://python-all.ru/3/faq/design.html#why-are-there-separate-tuple-and-list-data-types) [Как списки реализованы в CPython?](https://python-all.ru/3/faq/design.html#how-are-lists-implemented-in-cpython) [Как словари реализованы в CPython?](https://python-all.ru/3/faq/design.html#how-are-dictionaries-implemented-in-cpython) [Почему ключи словаря должны быть неизменяемыми?](https://python-all.ru/3/faq/design.html#why-must-dictionary-keys-be-immutable) [Почему list.sort() не возвращает отсортированный список?](https://python-all.ru/3/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list) [Как задать и обеспечить соблюдение спецификации интерфейса в Python?](https://python-all.ru/3/faq/design.html#how-do-you-specify-and-enforce-an-interface-spec-in-python) [Почему нет goto?](https://python-all.ru/3/faq/design.html#why-is-there-no-goto) [Почему сырые строки (r-строки) не могут заканчиваться обратной косой чертой?](https://python-all.ru/3/faq/design.html#why-can-t-raw-strings-r-strings-end-with-a-backslash) [Почему в Python нет оператора with для присваивания атрибутов?](https://python-all.ru/3/faq/design.html#why-doesn-t-python-have-a-with-statement-for-attribute-assignments) [Почему генераторы не поддерживают оператор with?](https://python-all.ru/3/faq/design.html#why-don-t-generators-support-the-with-statement) [Почему в операторах if/while/def/class требуется двоеточие?](https://python-all.ru/3/faq/design.html#why-are-colons-required-for-the-if-while-def-class-statements) [Почему Python разрешает запятые в конце списков и кортежей?](https://python-all.ru/3/faq/design.html#why-does-python-allow-commas-at-the-end-of-lists-and-tuples)1011## [Почему в Python для группировки операторов используется отступ?](https://python-all.ru/3/faq/design.html#id3)1213Гвидо ван Россум считает, что использование отступов для группировки чрезвычайно элегантно и значительно повышает читаемость типичной программы на Python. Большинство людей со временем начинают любить эту особенность.1415Поскольку нет фигурных скобок, не может быть расхождений между группировкой, воспринимаемой парсером, и человеческим восприятием. Иногда программисты на C сталкиваются с фрагментом кода вроде такого:1617```python18if (x <= y)19        x++;20        y--;21z++;22```2324Только оператор `x++` выполняется, если условие истинно, но отступ заставляет многих думать иначе. Даже опытные программисты на C иногда долго смотрят на этот код, гадая, почему `y` уменьшается даже для `x > y`.2526Из-за отсутствия фигурных скобок Python гораздо менее подвержен конфликтам стилей оформления кода. В C есть много разных способов расстановки фигурных скобок. Привыкнув к чтению и написанию кода в определённом стиле, нормально чувствовать некоторую неловкость при чтении (или необходимости писать) в другом стиле.2728Многие стили оформления размещают фигурные скобки на отдельных строках. Это делает программы значительно длиннее и тратит ценное пространство экрана, затрудняя получение хорошего обзора программы. В идеале функция должна помещаться на один экран (скажем, 20–30 строк). 20 строк Python могут выполнить гораздо больше работы, чем 20 строк C. Это связано не только с отсутствием фигурных скобок – отсутствие объявлений и высокоуровневые типы данных также играют роль – но синтаксис на основе отступов, безусловно, помогает.2930## [Почему при простых арифметических операциях получаются странные результаты?](https://python-all.ru/3/faq/design.html#id4)3132Смотрите следующий вопрос.3334## [Почему вычисления с плавающей запятой так неточны?](https://python-all.ru/3/faq/design.html#id5)3536Пользователи часто удивляются результатам вроде этого:3738```python39>>> 1.2 - 1.0400.1999999999999999641```4243и думают, что это ошибка в Python. Это не так. Это мало связано с Python и гораздо больше связано с тем, как нижележащая платформа обрабатывает числа с плавающей запятой.4445Тип [`float`](https://python-all.ru/3/library/functions.html#float) в CPython использует C `double` для хранения. Значение объекта `float` хранится в двоичном формате с плавающей запятой с фиксированной точностью (обычно 53 бита), и Python использует операции C, которые, в свою очередь, полагаются на аппаратную реализацию в процессоре, для выполнения операций с плавающей запятой. Это означает, что в отношении операций с плавающей запятой Python ведёт себя как многие популярные языки, включая C и Java.4647Многие числа, которые легко записываются в десятичном виде, не могут быть точно выражены в двоичном представлении с плавающей запятой. Например, после:4849```python50>>> x = 1.251```5253сохранённое значение для `x` является (очень хорошим) приближением к десятичному значению `1.2`, но не равно ему в точности. На типичной машине фактическое сохранённое значение равно:5455```python561.0011001100110011001100110011001100110011001100110011 (binary)57```5859что в точности равно:6061```python621.1999999999999999555910790149937383830547332763671875 (decimal)63```6465Типичная точность в 53 бита обеспечивает числам с плавающей точкой в Python 15–16 десятичных знаков точности.6667Для более полного объяснения см. главу [арифметика с плавающей запятой](https://python-all.ru/3/tutorial/floatingpoint.html#tut-fp-issues) в учебнике Python.6869## [Почему строки в Python неизменяемы?](https://python-all.ru/3/faq/design.html#id6)7071Есть несколько преимуществ.7273Первое – производительность: знание того, что строка неизменяема, позволяет выделить для неё память при создании, и требования к хранению фиксированы и неизменны. Это также одна из причин различия между кортежами и списками.7475Другое преимущество в том, что строки в Python считаются такими же «элементарными», как числа. Никакие действия не превратят значение 8 во что-то другое, и в Python никакие действия не превратят строку «eight» во что-то другое.7677## [Почему ‘self’ нужно явно указывать в определениях и вызовах методов?](https://python-all.ru/3/faq/design.html#id7)7879Идея была заимствована из Modula-3. Она оказалась очень полезной по ряду причин.8081Во-первых, так очевиднее, что используется метод или атрибут экземпляра, а не локальная переменная. Чтение `self.x` или `self.meth()` совершенно ясно показывает, что используется переменная экземпляра или метод, даже если вы не знаете определение класса наизусть. В C++ вы примерно можете понять это по отсутствию объявления локальной переменной (при условии, что глобальные переменные встречаются редко или легко узнаваемы) – но в Python нет объявлений локальных переменных, поэтому пришлось бы заглядывать в определение класса, чтобы быть уверенным. Некоторые стандарты оформления кода на C++ и Java требуют, чтобы атрибуты экземпляра имели префикс `m_`, так что эта явность полезна и в этих языках.8283Во-вторых, это означает, что не нужен специальный синтаксис для явной ссылки или вызова метода из определённого класса. В C++, если вы хотите использовать метод из базового класса, который переопределён в производном классе, нужно использовать оператор `::` – в Python можно написать `baseclass.methodname(self, <argument list>)`. Это особенно полезно для методов [`__init__()`](https://python-all.ru/3/reference/datamodel.html#object.__init__) и в целом в случаях, когда метод производного класса хочет расширить метод базового класса с тем же именем и поэтому должен как-то вызвать метод базового класса.8485Наконец, для переменных экземпляра это решает синтаксическую проблему с присваиванием: поскольку локальные переменные в Python (по определению!) – это те переменные, которым присваивается значение в теле функции (и которые не объявлены явно как глобальные), нужен способ сообщить интерпретатору, что присваивание предназначено для переменной экземпляра, а не локальной переменной, и желательно, чтобы этот способ был синтаксическим (из соображений эффективности). C++ делает это через объявления, но в Python объявлений нет, и было бы жаль вводить их только для этой цели. Использование явного `self.var` красиво решает эту проблему. Аналогично, при использовании переменных экземпляра необходимость писать `self.var` означает, что ссылки на неквалифицированные имена внутри метода не должны искать в каталогах экземпляра. Другими словами, локальные переменные и переменные экземпляра находятся в двух разных пространствах имён, и нужно сообщить Python, какое пространство имён использовать.8687## [Почему нельзя использовать присваивание в выражении?](https://python-all.ru/3/faq/design.html#id8)8889Начиная с Python 3.8, можно!9091Выражения присваивания с помощью оператора моржа `:=` присваивают значение переменной в выражении:9293```python94while chunk := fp.read(200):95   print(chunk)96```9798См. [**PEP 572**](https://python-all.ru/3/faq/design.html) для получения дополнительной информации.99100## [Почему Python использует методы для одних операций (например, list.index()), а функции для других (например, len(list))?](https://python-all.ru/3/faq/design.html#id9)101102Как сказал Гвидо:103104> (a) Для некоторых операций префиксная запись читается лучше, чем постфиксная – префиксные (и инфиксные!) операции имеют долгую традицию в математике, которая предпочитает обозначения, где визуальное представление помогает математику думать о задаче. Сравните, как легко мы переписываем формулу вроде x\*(a+b) в x\*a + x\*b, и как неуклюже делать то же самое с помощью чисто объектно-ориентированной нотации.105>106> (b) Когда я читаю код, в котором написано len(x), я *знаю*, что запрашивается длина чего-то. Это говорит мне о двух вещах: результат – целое число, а аргумент – некий контейнер. Напротив, когда я читаю x.len(), я уже должен знать, что x – это какой-то контейнер, реализующий интерфейс или наследующий от класса, у которого есть стандартный len(). Обратите внимание на путаницу, которая иногда возникает, когда класс, не реализующий отображение (mapping), имеет метод get() или keys() , или когда объект, не являющийся файлом, имеет метод write().107>108> –[https://mail.python.org/pipermail/python-3000/2006-November/004643.html](https://python-all.ru/3/faq/design.html)109110## [Почему join() – это метод строки, а не метод списка или кортежа?](https://python-all.ru/3/faq/design.html#id10)111112Строки стали гораздо больше похожи на другие стандартные типы начиная с Python 1.6, когда были добавлены методы, предоставляющие те же возможности, которые всегда были доступны через функции модуля string. Большинство этих новых методов были широко приняты, но один из них, который, по-видимому, вызывает дискомфорт у некоторых программистов, – это:113114```python115", ".join(['1', '2', '4', '8', '16'])116```117118который даёт результат:119120```python121"1, 2, 4, 8, 16"122```123124Есть два распространённых аргумента против этого использования.125126Первый звучит примерно так: «Выглядит действительно уродливо использовать метод строкового литерала (строковой константы)», на что можно ответить: возможно, но строковый литерал – это просто фиксированное значение. Если методы разрешены для имён, связанных со строками, нет логической причины делать их недоступными для литералов.127128Второе возражение обычно формулируется так: «Я действительно говорю последовательности объединить свои элементы с помощью строковой константы». К сожалению, это не так. По какой-то причине гораздо меньше затруднений вызывает то, что [`split()`](https://python-all.ru/3/library/stdtypes.html#str.split) – это метод строки, поскольку в этом случае легко видеть, что129130```python131"1, 2, 4, 8, 16".split(", ")132```133134– это инструкция строковому литералу вернуть подстроки, разделённые заданным разделителем (или, по умолчанию, произвольными последовательностями пробельных символов).135136[`join()`](https://python-all.ru/3/library/stdtypes.html#str.join) – это метод строки, потому что, используя его, вы указываете строке-разделителю перебрать последовательность строк и вставить себя между соседними элементами. Этот метод можно использовать с любым аргументом, который подчиняется правилам для объектов-последовательностей, включая любые новые классы, которые вы можете определить. Аналогичные методы существуют для объектов bytes и bytearray.137138## [Насколько быстры исключения?](https://python-all.ru/3/faq/design.html#id11)139140Блок [`try`](https://python-all.ru/3/reference/compound_stmts.html#try)/[`except`](https://python-all.ru/3/reference/compound_stmts.html#except) чрезвычайно эффективен, если исключения не возбуждаются. На самом деле перехват исключения – дорогостоящая операция. В версиях Python до 2.0 было принято использовать такой идиоматический код:141142```python143try:144    value = mydict[key]145except KeyError:146    mydict[key] = getvalue(key)147    value = mydict[key]148```149150Это имело смысл только тогда, когда вы ожидали, что ключ есть в словаре почти всё время. Если это было не так, вы писали так:151152```python153if key in mydict:154    value = mydict[key]155else:156    value = mydict[key] = getvalue(key)157```158159Для этого конкретного случая можно также использовать `value = dict.setdefault(key, getvalue(key))`, но только если вызов `getvalue()` достаточно дешёвый, потому что он вычисляется в любом случае.160161## [Почему в Python нет оператора switch или case?](https://python-all.ru/3/faq/design.html#id12)162163В целом, структурированные операторы switch выполняют один блок кода, когда выражение принимает определённое значение или набор значений. Начиная с Python 3.10 можно легко сопоставлять буквальные значения или константы внутри пространства имён с помощью оператора `match ... case`. См. [спецификацию](https://python-all.ru/3/reference/compound_stmts.html#match) и [руководство](https://python-all.ru/3/tutorial/controlflow.html#tut-match) для получения дополнительной информации об операторах [`match`](https://python-all.ru/3/reference/compound_stmts.html#match). Более старый альтернативный вариант – это последовательность `if... elif... elif... else`.164165Для случаев, когда нужно выбрать из очень большого числа возможностей, можно создать словарь, отображающий значения вариантов на вызываемые функции. Например:166167```python168functions = {'a': function_1,169             'b': function_2,170             'c': self.method_1}171172func = functions[value]173func()174```175176Для вызова методов объектов можно упростить ещё больше, используя встроенную функцию [`getattr()`](https://python-all.ru/3/library/functions.html#getattr) для получения методов с определённым именем:177178```python179class MyVisitor:180    def visit_a(self):181        ...182183    def dispatch(self, value):184        method_name = 'visit_' + str(value)185        method = getattr(self, method_name)186        method()187```188189Рекомендуется использовать префикс для имён методов, например `visit_` в этом примере. Без такого префикса, если значения поступают из ненадёжного источника, злоумышленник сможет вызвать любой метод вашего объекта.190191Имитация switch со сквозным выполнением (fallthrough), как в C (switch-case-default), возможна, но гораздо сложнее и реже нужна.192193## [Нельзя ли эмулировать потоки в интерпретаторе вместо того, чтобы полагаться на реализацию потоков, зависящую от ОС?](https://python-all.ru/3/faq/design.html#id13)194195Ответ 1: К сожалению, интерпретатор помещает как минимум один кадр стека C для каждого кадра стека Python. Кроме того, расширения могут вызывать Python в почти случайные моменты. Поэтому полная реализация потоков требует поддержки потоков на уровне C.196197Ответ 2: К счастью, существует [Stackless Python](https://python-all.ru/3/faq/design.html), который имеет полностью переработанный цикл интерпретатора, избавляющийся от стека C.198199## [Почему лямбда-выражения не могут содержать инструкции?](https://python-all.ru/3/faq/design.html#id14)200201Лямбда-выражения Python не могут содержать инструкции, потому что синтаксическая структура Python не поддерживает инструкции, вложенные внутрь выражений. Однако в Python это не является серьёзной проблемой. В отличие от лямбда-форм в других языках, где они добавляют функциональность, лямбды Python – это лишь сокращённая запись, если вам лень определять функцию.202203Функции в Python уже являются объектами первого класса и могут быть объявлены в локальной области видимости. Поэтому единственное преимущество использования лямбды вместо локально определённой функции – это то, что не нужно придумывать имя для функции, но это всего лишь локальная переменная, которой присваивается объект функции (который является точно таким же типом объекта, что и результат лямбда-выражения)!204205## [Можно ли скомпилировать Python в машинный код, C или другой язык?](https://python-all.ru/3/faq/design.html#id15)206207[Cython](https://python-all.ru/3/faq/design.html) компилирует модифицированную версию Python с необязательными аннотациями в расширения C. [Nuitka](https://python-all.ru/3/faq/design.html) – это многообещающий компилятор Python в код C++, стремящийся поддерживать весь язык Python.208209## [Как Python управляет памятью?](https://python-all.ru/3/faq/design.html#id16)210211The details of Python memory management depend on the implementation. The standard implementation of Python, [CPython](https://python-all.ru/3/glossary.html#term-CPython), uses reference counting to detect inaccessible objects, and another mechanism to collect reference cycles, periodically executing a cycle detection algorithm which looks for inaccessible cycles and deletes the objects involved. The [`gc`](https://python-all.ru/3/library/gc.html#module-gc) module provides functions to perform a garbage collection, obtain debugging statistics, and tune the collector’s parameters.212213Однако другие реализации (такие как [Jython](https://python-all.ru/3/faq/design.html) или [PyPy](https://python-all.ru/3/faq/design.html)) могут полагаться на другой механизм, например, полноценный сборщик мусора. Это различие может вызвать некоторые тонкие проблемы переносимости, если код на Python зависит от поведения реализации с подсчётом ссылок.214215В некоторых реализациях Python следующий код (который нормально работает в CPython) вероятно исчерпает файловые дескрипторы:216217```python218for file in very_long_list_of_files:219    f = open(file)220    c = f.read(1)221```222223Действительно, благодаря схеме подсчёта ссылок и деструкторов в CPython, каждое новое присваивание `f` закрывает предыдущий файл. Однако в традиционном сборщике мусора эти файловые объекты будут собраны (и закрыты) только через разные и, возможно, длительные интервалы.224225Чтобы написать код, который будет работать с любой реализацией Python, следует явно закрывать файлы или использовать оператор [`with`](https://python-all.ru/3/reference/compound_stmts.html#with); это будет работать независимо от схемы управления памятью:226227```python228for file in very_long_list_of_files:229    with open(file) as f:230        c = f.read(1)231```232233## [Почему CPython не использует более традиционную схему сборки мусора?](https://python-all.ru/3/faq/design.html#id17)234235Во-первых, это не является стандартной возможностью C, и поэтому она не переносима. (Да, мы знаем о библиотеке Boehm GC. Она содержит фрагменты ассемблерного кода для *большинства* распространённых платформ, но не для всех, и хотя она в основном прозрачна, она не полностью прозрачна; требуются патчи, чтобы Python работал с ней.)236237Традиционный GC также становится проблемой, когда Python встраивается в другие приложения. В то время как в отдельном Python можно заменить стандартные `malloc()` и `free()` на версии, предоставляемые библиотекой GC, приложение, встраивающее Python, может захотеть иметь свою *собственную* замену для `malloc()` и `free()` и не использовать версии Python. Сейчас CPython работает с любым, что правильно реализует `malloc()` и `free()`.238239## [Почему не вся память освобождается при завершении CPython?](https://python-all.ru/3/faq/design.html#id18)240241Объекты, на которые есть ссылки из глобальных пространств имён модулей Python, не всегда освобождаются при завершении Python. Это может происходить при наличии циклических ссылок. Также существуют определённые фрагменты памяти, выделяемые библиотекой C, которые невозможно освободить (например, инструмент вроде Purify будет жаловаться на них). Однако Python активно очищает память при завершении и старается уничтожить каждый объект.242243Чтобы заставить Python удалить определённые объекты при освобождении, используйте модуль [`atexit`](https://python-all.ru/3/library/atexit.html#module-atexit) для запуска функции, которая принудительно выполнит эти удаления.244245## [Почему существуют отдельные типы данных кортеж (tuple) и список (list)?](https://python-all.ru/3/faq/design.html#id19)246247Списки и кортежи, хотя и похожи во многих отношениях, обычно используются принципиально разными способами. Кортежи можно рассматривать как аналоги Pascal `records` или C `structs`; это небольшие коллекции связанных данных, которые могут быть разных типов и обрабатываются как группа. Например, декартова координата уместно представляется кортежем из двух или трёх чисел.248249Списки, с другой стороны, больше похожи на массивы в других языках. Они обычно содержат переменное количество объектов одного типа, которые обрабатываются по одному. Например, [`os.listdir('.')`](https://python-all.ru/3/library/os.html#os.listdir) возвращает список строк, представляющих файлы в текущем каталоге. Функции, работающие с этим выводом, в целом не сломаются, если добавить в каталог ещё один-два файла.250251Кортежи неизменяемы, то есть после создания кортежа нельзя заменить какой-либо его элемент новым значением. Списки изменяемы, то есть элементы списка всегда можно изменить. Только неизменяемые элементы можно использовать в качестве ключей словаря, поэтому в качестве ключей можно использовать только кортежи, но не списки.252253## [Как реализованы списки в CPython?](https://python-all.ru/3/faq/design.html#id20)254255Списки в CPython – это на самом деле массивы переменной длины, а не связные списки в стиле Lisp. Реализация использует непрерывный массив ссылок на другие объекты и хранит указатель на этот массив и его длину в структуре заголовка списка.256257Это делает индексацию списка `a[i]` операцией, стоимость которой не зависит от размера списка или значения индекса.258259При добавлении или вставке элементов массив ссылок изменяет размер. Применяется некоторая хитрость для повышения производительности при многократном добавлении: когда массив нужно увеличить, выделяется дополнительное пространство, чтобы следующие несколько раз не требовалось реально изменять размер.260261## [Как реализованы словари в CPython?](https://python-all.ru/3/faq/design.html#id21)262263Словари в CPython реализованы как хэш-таблицы с изменяемым размером. По сравнению с B-деревьями, это обеспечивает лучшую производительность поиска (самой распространённой операции) в большинстве случаев, а реализация проще.264265Словари работают путём вычисления хэш-кода для каждого ключа, хранящегося в словаре, с помощью встроенной функции [`hash()`](https://python-all.ru/3/library/functions.html#hash). Хэш-код сильно варьируется в зависимости от ключа и посевного значения (seed) процесса; например, `'Python'` может хэшироваться в `-539294296`, а `'python'` – строка, отличающаяся на один бит, может хэшироваться в `1142331976`. Затем хэш-код используется для вычисления позиции во внутреннем массиве, где будет храниться значение. Если предположить, что ключи имеют разные хэш-значения, это означает, что словари выполняют поиск ключа за постоянное время – *O*(1), в нотации «О-большое».266267## [Почему ключи словарей должны быть неизменяемыми?](https://python-all.ru/3/faq/design.html#id22)268269Реализация словарей на основе хэш-таблиц использует хэш-значение, вычисленное из значения ключа, для поиска ключа. Если бы ключ был изменяемым объектом, его значение могло бы измениться, и, следовательно, его хэш также мог бы измениться. Но поскольку тот, кто изменяет объект-ключ, не может знать, что он используется в качестве ключа словаря, он не может переместить запись в словаре. Тогда при попытке найти тот же объект в словаре он не будет найден, потому что его хэш-значение стало другим. Если попытаться найти старое значение, оно тоже не будет найдено, поскольку значение объекта, находящегося в той хэш-корзине, будет другим.270271Если нужен словарь, индексируемый списком, просто преобразуйте список в кортеж; функция `tuple(L)` создаёт кортеж с теми же элементами, что и список `L`. Кортежи неизменяемы и поэтому могут использоваться в качестве ключей словаря.272273Некоторые неприемлемые решения, которые предлагались:274275- Хэшировать списки по их адресу (идентификатору объекта). Это не работает, потому что если создать новый список с тем же значением, он не будет найден; например:276277  ```python278  mydict = {[1, 2]: '12'}279  print(mydict[[1, 2]])280  ```281282  вызовет исключение [`KeyError`](https://python-all.ru/3/library/exceptions.html#KeyError), потому что идентификатор `[1, 2]`, используемый во второй строке, отличается от идентификатора в первой строке. Другими словами, ключи словарей следует сравнивать с помощью `==`, а не [`is`](https://python-all.ru/3/reference/expressions.html#is).283- Создавать копию при использовании списка в качестве ключа. Это не работает, потому что список, будучи изменяемым объектом, может содержать ссылку на самого себя, и тогда код копирования попадёт в бесконечный цикл.284- Разрешить списки в качестве ключей, но предупредить пользователя не изменять их. Это породило бы класс трудноотслеживаемых ошибок в программах, когда пользователь забыл или случайно изменил список. Это также нарушает важный инвариант словарей: каждое значение в `d.keys()` может использоваться в качестве ключа словаря.285- Помечайте списки как доступные только для чтения, как только они используются в качестве ключа словаря. Проблема в том, что изменить значение может не только объект верхнего уровня: можно использовать кортеж, содержащий список, в качестве ключа. Добавление чего-либо в качестве ключа в словарь потребует пометить все достижимые оттуда объекты как доступные только для чтения – и снова самореферентные объекты могут вызвать бесконечный цикл.286287Существует трюк, позволяющий обойти это, если нужно, но используйте его на свой страх и риск: можно обернуть изменяемую структуру в экземпляр класса, у которого есть и метод [`__eq__()`](https://python-all.ru/3/reference/datamodel.html#object.__eq__), и метод [`__hash__()`](https://python-all.ru/3/reference/datamodel.html#object.__hash__). Затем необходимо убедиться, что хеш-значение для всех таких объектов-обёрток, находящихся в словаре (или другой хеш-структуре), остаётся фиксированным, пока объект находится в словаре (или другой структуре).288289```python290class ListWrapper:291    def __init__(self, the_list):292        self.the_list = the_list293294    def __eq__(self, other):295        return self.the_list == other.the_list296297    def __hash__(self):298        l = self.the_list299        result = 98767 - len(l)*555300        for i, el in enumerate(l):301            try:302                result = result + (hash(el) % 9999999) * 1001 + i303            except Exception:304                result = (result % 7777777) + i * 333305        return result306```307308Обратите внимание, что вычисление хеша усложняется тем, что некоторые элементы списка могут быть нехешируемыми, а также возможностью арифметического переполнения.309310Кроме того, всегда должно выполняться условие: если `o1 == o2` (то есть `o1.__eq__(o2) is True`), то `hash(o1) == hash(o2)` (то есть `o1.__hash__() == o2.__hash__()`), независимо от того, находится объект в словаре или нет. Если не соблюдать эти ограничения, словари и другие хеш-структуры будут работать некорректно.311312В случае `ListWrapper`, когда объект-обёртка находится в словаре, обёрнутый список не должен изменяться, чтобы избежать аномалий. Не делайте этого, если вы не готовы серьёзно обдумать требования и последствия их несоблюдения. Считайте, что вы предупреждены.313314## [Почему list.sort() не возвращает отсортированный список?](https://python-all.ru/3/faq/design.html#id23)315316В ситуациях, где важна производительность, создание копии списка только для сортировки было бы расточительным. Поэтому [`list.sort()`](https://python-all.ru/3/library/stdtypes.html#list.sort) сортирует список на месте. Чтобы напомнить вам об этом, он не возвращает отсортированный список. Так вы не попадётесь в ловушку случайной перезаписи списка, когда вам нужна отсортированная копия, но также необходимо сохранить и неотсортированную версию.317318Если нужно вернуть новый список, используйте вместо этого встроенную функцию [`sorted()`](https://python-all.ru/3/library/functions.html#sorted). Эта функция создаёт новый список из переданного итерируемого объекта, сортирует его и возвращает. Например, вот как выполнить итерацию по ключам словаря в отсортированном порядке:319320```python321for key in sorted(mydict):322    ...  # делайте что угодно с mydict[key]...323```324325## [Как задать и обеспечить соблюдение спецификации интерфейса в Python?](https://python-all.ru/3/faq/design.html#id24)326327Спецификация интерфейса модуля, предоставляемая такими языками, как C++ и Java, описывает прототипы методов и функций модуля. Многие считают, что проверка соблюдения спецификаций интерфейса на этапе компиляции помогает при построении больших программ.328329В Python 2.6 добавлен модуль [`abc`](https://python-all.ru/3/library/abc.html#module-abc), позволяющий определять абстрактные базовые классы (ABC). Затем можно использовать [`isinstance()`](https://python-all.ru/3/library/functions.html#isinstance) и [`issubclass()`](https://python-all.ru/3/library/functions.html#issubclass) для проверки, реализует ли экземпляр или класс конкретный ABC. Модуль [`collections.abc`](https://python-all.ru/3/library/collections.abc.html#module-collections.abc) определяет набор полезных ABC, таких как [`Iterable`](https://python-all.ru/3/library/collections.abc.html#collections.abc.Iterable), [`Container`](https://python-all.ru/3/library/collections.abc.html#collections.abc.Container) и [`MutableMapping`](https://python-all.ru/3/library/collections.abc.html#collections.abc.MutableMapping).330331Для Python многие преимущества спецификаций интерфейса могут быть получены за счёт соответствующей дисциплины тестирования компонентов.332333Хороший набор тестов для модуля может одновременно служить регрессионным тестом, спецификацией интерфейса модуля и набором примеров. Многие модули Python можно запускать как скрипт для простого «самотестирования». Даже модули, использующие сложные внешние интерфейсы, часто можно тестировать изолированно, используя тривиальные «заглушки», эмулирующие внешний интерфейс. Модули [`doctest`](https://python-all.ru/3/library/doctest.html#module-doctest) и [`unittest`](https://python-all.ru/3/library/unittest.html#module-unittest) или сторонние тестовые фреймворки можно использовать для построения исчерпывающих наборов тестов, которые выполняют каждую строку кода в модуле.334335Надлежащая дисциплина тестирования может помочь в создании больших сложных приложений на Python так же, как и спецификации интерфейса. Фактически, она может быть даже лучше, потому что спецификация интерфейса не может проверить некоторые свойства программы. Например, метод [`list.append()`](https://python-all.ru/3/library/stdtypes.html#list.append) должен добавлять новые элементы в конец некоторого внутреннего списка; спецификация интерфейса не может проверить, что ваша реализация `list.append()` действительно делает это правильно, но проверить это свойство в наборе тестов тривиально.336337Написание наборов тестов очень полезно, и, возможно, стоит проектировать код так, чтобы его было легко тестировать. Одна из набирающих популярность методик, разработка через тестирование, предполагает написание частей набора тестов до того, как написан какой-либо реальный код. Конечно, Python позволяет быть небрежным и вообще не писать тесты.338339## [Почему в Python нет goto?](https://python-all.ru/3/faq/design.html#id25)340341В 1970-х годах люди осознали, что неограниченное использование goto может привести к запутанному «спагетти»-коду, который трудно понять и изменить. В языке высокого уровня в нём нет необходимости, пока есть способы ветвления (в Python – с помощью оператора [`if`](https://python-all.ru/3/reference/compound_stmts.html#if) и выражений [`or`](https://python-all.ru/3/reference/expressions.html#or), [`and`](https://python-all.ru/3/reference/expressions.html#and), `if`/[`else`](https://python-all.ru/3/reference/compound_stmts.html#else)) и цикла (с помощью операторов [`while`](https://python-all.ru/3/reference/compound_stmts.html#while) и [`for`](https://python-all.ru/3/reference/compound_stmts.html#for), возможно, содержащих [`continue`](https://python-all.ru/3/reference/simple_stmts.html#continue) и [`break`](https://python-all.ru/3/reference/simple_stmts.html#break)).342343Также можно использовать исключения для организации «структурированного goto», которое работает даже между вызовами функций. Многие считают, что исключения удобно эмулируют все разумные применения конструкций `go` или `goto` из C, Fortran и других языков. Например:344345```python346class label(Exception): pass  # объявить метку347348try:349    ...350    if condition: raise label()  # goto label351    ...352except label:  # куда перейти353    pass354...355```356357Это не позволяет перепрыгнуть в середину цикла, но это обычно считается злоупотреблением `goto` в любом случае. Используйте с осторожностью.358359## [Почему сырые строки (r-строки) не могут заканчиваться обратным слешем?](https://python-all.ru/3/faq/design.html#id26)360361Более точно, они не могут заканчиваться нечётным количеством обратных слешей: непарный обратный слеш в конце экранирует закрывающий символ кавычки, оставляя строку незавершённой.362363Сырые строки были разработаны для упрощения создания входных данных для обработчиков (в основном, движков регулярных выражений), которые хотят выполнять свою собственную обработку экранирования обратным слешем. Такие обработчики в любом случае считают непарный завершающий обратный слеш ошибкой, поэтому сырые строки это запрещают. Взамен они позволяют передать символ кавычки, экранируя его обратным слешем. Эти правила хорошо работают, когда r-строки используются по назначению.364365Если вы пытаетесь построить пути Windows, учтите, что все системные вызовы Windows принимают и прямые слеши:366367```python368f = open("/mydir/file.txt")  # работает отлично!369```370371Если вы пытаетесь построить путь для команды DOS, попробуйте, например, один из372373```python374dir = r"\this\is\my\dos\dir" "\\"375dir = r"\this\is\my\dos\dir\ "[:-1]376dir = "\\this\\is\\my\\dos\\dir\\"377```378379## [Почему в Python нет оператора with для присваивания атрибутов?](https://python-all.ru/3/faq/design.html#id27)380381В Python есть оператор [`with`](https://python-all.ru/3/reference/compound_stmts.html#with), который оборачивает выполнение блока, вызывая код при входе в блок и выходе из него. В некоторых языках есть конструкция, которая выглядит так:382383```python384with obj:385    a = 1               # эквивалентно obj.a = 1386    total = total + 1   # obj.total = obj.total + 1387```388389В Python такая конструкция была бы неоднозначной.390391Другие языки, такие как Object Pascal, Delphi и C++, используют статическую типизацию, поэтому можно однозначно узнать, какому члену выполняется присваивание. В этом и заключается основная суть статической типизации – компилятор *всегда* знает область видимости каждой переменной на этапе компиляции.392393Python использует динамическую типизацию. Невозможно заранее узнать, какой атрибут будет использован во время выполнения. Атрибуты-члены могут добавляться или удаляться из объектов на лету. Из-за этого невозможно при простом чтении понять, на какой атрибут идёт ссылка: локальный, глобальный или атрибут-член?394395Например, рассмотрим следующий неполный фрагмент кода:396397```python398def foo(a):399    with a:400        print(x)401```402403Фрагмент предполагает, что у `a` должен быть атрибут-член с именем `x`. Однако в Python нет ничего, что сообщало бы об этом интерпретатору. Что должно произойти, если `a`, скажем, является целым числом? Если существует глобальная переменная с именем `x`, будет ли она использоваться внутри блока [`with`](https://python-all.ru/3/reference/compound_stmts.html#with)? Как видите, динамическая природа Python делает такой выбор гораздо труднее.404405Однако основное преимущество [`with`](https://python-all.ru/3/reference/compound_stmts.html#with) и подобных языковых возможностей (сокращение объёма кода) легко достигается в Python с помощью присваивания. Вместо:406407```python408function(args).mydict[index][index].a = 21409function(args).mydict[index][index].b = 42410function(args).mydict[index][index].c = 63411```412413напишите это:414415```python416ref = function(args).mydict[index][index]417ref.a = 21418ref.b = 42419ref.c = 63420```421422Это также даёт побочный эффект ускорения выполнения, так как привязки имён в Python разрешаются во время выполнения, и второй вариант требует разрешения только один раз.423424Подобные предложения, которые вводили бы синтаксис для дальнейшего сокращения объёма кода, например, использование «ведущей точки», были отклонены в пользу явности (см. [https://mail.python.org/pipermail/python-ideas/2016-May/040070.html](https://python-all.ru/3/faq/design.html)).425426## [Почему генераторы не поддерживают оператор with?](https://python-all.ru/3/faq/design.html#id28)427428По техническим причинам генератор, используемый непосредственно как менеджер контекста, не будет работать корректно. Когда, как чаще всего, генератор используется как итератор, выполняющийся до конца, закрытие не требуется. Если же оно нужно, оберните его как [`contextlib.closing(generator)`](https://python-all.ru/3/library/contextlib.html#contextlib.closing) в операторе [`with`](https://python-all.ru/3/reference/compound_stmts.html#with).429430## [Почему двоеточия обязательны для операторов if/while/def/class?](https://python-all.ru/3/faq/design.html#id29)431432Двоеточие требуется в первую очередь для улучшения читаемости (один из результатов экспериментального языка ABC). Рассмотрим это:433434```python435if a == b436    print(a)437```438439против440441```python442if a == b:443    print(a)444```445446Обратите внимание, что второй вариант читается немного легче. Также обратите внимание, как двоеточие выделяет пример в этом ответе из FAQ; это стандартное использование в английском языке.447448Ещё одна второстепенная причина заключается в том, что двоеточие упрощает работу редакторов с подсветкой синтаксиса; они могут искать двоеточия, чтобы решить, когда нужно увеличить отступ, вместо более тщательного разбора текста программы.449450## [Почему Python допускает запятые в конце списков и кортежей?](https://python-all.ru/3/faq/design.html#id30)451452Python позволяет добавлять завершающую запятую в конце списков, кортежей и словарей:453454```python455[1, 2, 3,]456('a', 'b', 'c',)457d = {458    "A": [1, 5],459    "B": [6, 7],  # последняя завершающая запятая необязательна, но считается хорошим стилем460}461```462463Для этого есть несколько причин.464465Когда литерал списка, кортежа или словаря разбит на несколько строк, проще добавлять новые элементы, поскольку не нужно помнить о добавлении запятой в предыдущей строке. Строки также можно переставлять без возникновения синтаксической ошибки.466467Случайный пропуск запятой может привести к ошибкам, которые трудно диагностировать. Например:468469```python470x = [471  "fee",472  "fie"473  "foo",474  "fum"475]476```477478Этот список выглядит так, будто в нём четыре элемента, но на самом деле их три: «fee», «fiefoo» и «fum». Всегда добавляя запятую, можно избежать этого источника ошибок.479480Разрешение завершающей запятой также может упростить программную генерацию кода.481