Чистыйкод дляпродолжающи х
Скачать 7.85 Mb.
|
import this The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. --snip-- ПРИМЕЧАНИЕ Как ни странно, записаны только 19 принципов. Говорят, Гвидо ван Россум, создатель Python, заявил, что отсутствующий 20-й пункт был «странной шуткой Тима Петерса, по- нятной только посвященным». Тим оставил в списке пустое место и предложил заполнить его Гвидо, но похоже, у того так и не нашлось времени. В конечном итоге эти тезисы — всего лишь изложение мнения, с которым про- граммисты могут соглашаться или не соглашаться. Как и во всех хороших сводах нравственных норм, они противоречат друг другу, что обеспечивает наибольшую гибкость в их реализации. Моя интерпретация этих тезисов изложена ниже. Красивое лучше, чем уродливое. Красивым обычно считается код, который легко читается и воспринимается. Нередко программисты пишут код слишком быстро, не заботясь об удобочитаемости. Компьютер выполнит нечитаемый код, но у про- граммистов возникнут сложности с его сопровождением и отладкой. Красота — по- нятие субъективное, но код, при написании которого программист не позаботился о том, как он будет читаться, другим часто кажется уродливым. Одна из причин популярности Python как раз связана с тем, что его синтаксис не загромождается загадочными знаками препинания, как в других языках, что упрощает работу с ним. Явное лучше, чем неявное. Если бы в качестве пояснения данного правила я сказал «это очевидно», это было бы ужасно. Так и в коде лучше выражаться развернуто и явно. Не стоит прятать функциональность в невнятном коде, понимание которого требует знания всех тонкостей языка. Простое лучше, чем сложное. Сложное лучше, чем запутанное. Эти два утверж- дения напоминают нам, что все можно строить как простыми, так и сложными средствами. Если перед вами стоит простая задача, для решения которой нужна лопата, использовать 50-тонный гидравлический бульдозер неэффективно. Но для выполнения грандиозной задачи сложность управления одним бульдозером ничто по сравнению с координацией команды из 100 землекопов. Отдавайте предпочтение простоте перед сложностью, но знайте границы простоты. Плоское лучше, чем вложенное. Программисты любят делить свой код на ка- тегории, особенно если те содержат подкатегории, которые содержат другие «Дзен Python» 115 подкатегории. Эти иерархии часто продуцируют не столько организацию, сколько бюрократизм. Ничто не мешает вам написать код, содержащийся всего в одной структуре данных или модуле верхнего уровня. Если в вашем коде часто встреча- ются конструкции вида spam.eggs.bacon.ham() или spam['eggs']['bacon']['ham'] , вы его переусложнили. Разреженное лучше, чем плотное. Программисты любят втискивать макси- мум функциональности в минимальный объем кода, как в следующей строке: print('\n'.join("%i bytes = %i bits which has %i possiblevalues." % (j, j*8, 256**j-1) for j in (1 << i for i in range(8)))) . Хотя такой код наверняка произ- ведет впечатление на друзей, он приведет в ярость коллег, которым придется в нем разбираться. Не стремитесь к тому, чтобы ваш код делал много всего и сразу. Код, распределенный по нескольким строкам, часто читается проще, чем плотные одно- строчные конструкции. Это правило означает приблизительно то же, что и «простое лучше, чем сложное». Удобочитаемость важна. Хотя имя strcmp() наверняка означает «сравнение строк» для тех, кто программировал на C с 1970-х годов, у современных компьютеров хватает памяти для полных имен функций. Не пропускайте буквы в именах и не пишите слишком лаконичный код. Не жалейте времени на поиск содержательных, конкретных имен для ваших переменных и функций. Пустая строка между раз- делами кода решает ту же задачу, что и разбивка на абзацы в книге: она сообщает читателю, какие части должны читаться вместе. Это правило означает приблизи- тельно то же, что и «красивое лучше, чем уродливое». Особые случаи не настолько особые, чтобы нарушать правила. При этом прак- тичность важнее безупречности. Эти два афоризма противоречат друг другу. В программировании полно передовых практик, которые программистам следует использовать в своем коде. Желание обойти эти приемы ради быстрого хитроум- ного трюка соблазнительно, но иногда приводит к беспорядочному клубку непо- следовательного и нечитаемого кода. С другой стороны, фанатичное следование правилам может стать причиной абстрактного, неудобочитаемого кода. Например, в Java попытка написать весь код в парадигме объектно-ориентированного про- граммирования часто приводит к использованию большого объема шаблонов даже в самой мелкой программе. С опытом вы научитесь проходить по узкой кромке между этими двумя положениями. А со временем не только узнаете правила, но и поймете, когда их можно нарушать. Ошибки никогда не должны замалчиваться. Если только они не замалчиваются явно. Хотя программисты часто игнорируют сообщения об ошибках, это не значит, что программа должна перестать их выдавать. Чаще всего ошибки замалчиваются, когда функции возвращают коды ошибок или None вместо выдачи исключений. Эти два тезиса утверждают, что лучше программировать с расчетом на быстрый сбой 116 Глава 6.Написание питонического кода и аварийное завершение, чем на замалчивание ошибки с продолжением выполне- ния. Ошибки, которые неизбежно возникнут позднее, создадут больше проблем с отладкой, потому что они проявят себя далеко от причины, их вызвавшей. Хотя вы можете взять за правило явно игнорировать ошибки, возникающие в вашей программе, убедитесь, что это ваше сознательное решение. Столкнувшись с неоднозначностью, боритесь с искушением угадать. Компьюте- ры делают людей суеверными: чтобы изгнать демонов из наших компьютеров, мы выполняем священный ритуал, перезагружая их. Предполагается, что это решит любую непонятную проблему. Однако в компьютерах нет ничего волшебного. Если код не работает, тому есть причина, и проблему можно решить, только при- звав на помощь критическое мышление. Боритесь с искушением искать причину наугад, пока что-то не заработает; часто вы всего лишь маскируете проблему, а не решаете ее. Должен быть один — и желательно только один — очевидный способ сделать это. Этот тезис напрямую противоречит девизу языка программирования Perl: «Это можно сделать разными способами!». Как выясняется, возможность написать код для решения одной задачи тремя-четырьмя разными способами становится пал- кой о двух концах: у вас появляется большая гибкость в написании кода, но зато вам придется изучать все возможные способы его написания, чтобы читать чужой код. Гибкость не оправдывает лишних усилий, которые потребуются для изучения языка программирования. Хотя это может быть и не очевидно, если только вы не голландец. Это шутка. Ибо Гвидо ван Россум, создатель Python, — голландец. Сейчас лучше, чем никогда. Хотя никогда зачастую лучше, чем *прямо* сейчас. Это о том, что код, который работает медленно, очевидно хуже, чем тот, который работает быстро. Но лучше дождаться завершения программы, чем завершить ее слишком рано с неправильными результатами. Если реализацию сложно объяснить, идея плоха. Если реализацию легко объяс- нить, возможно, идея хороша. Многие вещи усложняются со временем: налоговое законодательство, романтические отношения, книги по программированию на язы- ке Python. То же можно сказать и о программах. Эти два положения напоминают нам: если код настолько сложен, что программист не сможет его понять и отладить, это плохой код. Но если код объясняется легко, это не обязательно означает, что он хорош. К сожалению, понять, как сделать код простым настолько, насколько это возможно, и ни на йоту проще, достаточно сложно. Пространства имен — отличная штука. Сделаем их побольше! Пространства имен представляют собой изолированные контейнеры для идентификаторов, предот- вращающие конфликты имен. Например, встроенная функция open() и функция Как полюбить значимые отступы 117 .open() у webbrowser имеют одинаковые имена, но это разные функции. Импор- тирование webbrowser не замещает встроенную функцию open() , потому что две функции существуют в разных пространствах имен: встроенном пространстве имен и пространстве имен модуля webbrowser соответственно. Однако не забывайте, что плоское лучше вложенного: какими бы замечательными ни были пространства имен, создавать их следует только для предотвращения конфликтов имен, а не для добавления лишней систематизации. Как и в отношении всех мнений в области программирования, вы можете возра- зить мне, что мои советы могут быть просто неактуальными для вашей ситуации. Споры о том, как следует писать код или какой код считать питоническим, редко бывают такими плодотворными, как это кажется на первый взгляд. (Если только вы не собираете в книгу одни лишь субъективные мнения.) Как полюбить значимые отступы Самая распространенная претензия к Python, которую я слышу от программистов с опытом работы на других языках, — необычность и непривычность значимых отступов (которые часто по ошибке называют значимыми пробелами). Количество отступов в начале строки кода имеет смысл в Python, потому что оно определяет, какие строки кода принадлежат тому же программному блоку. Группировка блоков кода в Python при помощи отступов может показаться стран- ной, потому что в других языках блоки начинаются и завершаются фигурными скобками: { and } . Однако программисты с опытом работы на других языках тоже обычно снабжают свои блоки отступами, как и программисты на Python, чтобы их код лучше читался. Например, в Java значимых отступов нет. Программистам Java не нужно использовать отступы в блоках, но они обычно все равно делают это ради удобочитаемости. В следующем примере функция Java main() содержит только один вызов функции println() : // Пример на Java public static void main(String[] args) { System.out.println("Hello, world!"); } Этот код Java вполне нормально работал бы и без отступов в строке println() , потому что начало и конец блоков в Java отмечаются фигурными скобками, а не отступами. Python не разрешает применять отступы по вашему усмотрению, а за- ставляет программиста последовательно обеспечивать удобочитаемость кода. Однако следует заметить, что в Python нет значимых пробелов, потому что Python не ограничивает использование пробельных символов вне отступов (и 2 + 2 , и 2+2 являются допустимыми выражениями Python). 118 Глава 6.Написание питонического кода Некоторые программисты считают, что открывающая фигурная скобка должна быть в одной строке с открывающей командой, а другие полагают, что ее следует разме- щать в следующей строке. Программисты могут без конца спорить о преимуществах выбранного стиля. Python изящно обходит эту проблему, вообще отказавшись от фигурных скобок, чтобы программисты занимались более продуктивной работой. Я бы предпочел, чтобы все языки программирования последовали примеру Python в области группировки блоков. Но некоторые люди все равно привыкли к фигурным скобкам и требуют добавить их в будущую версию Python — при всей их непитоничности. Модуль Python __future__ выполняет обратное импортирование функциональности в более ран- ние версии Python, но при попытке импортировать фигурные скобки в Python обнаруживается пасхалка — скрытое послание: >>> from __future__ import braces SyntaxError: not a chance Я бы не рассчитывал на то, что фигурные скобки будут добавлены в Python в обо- зримом будущем. Использование модуля timeit для оценки быстродействия «Предварительная оптимизация — корень всех зол», — говорил Дональд Кнут, зна- менитый ученый из Стэнфорда. Предварительная оптимизация, или оптимизация быстродействия программы до того, как вы будете располагать реальными данными о ее быстродействии, часто встречается тогда, когда программисты пытаются приме- нять хитроумные программные трюки для экономии памяти или ускорения работы кода. Например, некоторые программисты используют алгоритм XOR (исключающее ИЛИ), для того чтобы поменять местами два целых числа в памяти без использо- вания третьей временной переменной (как предполагается, для экономии памяти): >>> a, b = 42, 101 # Создание двух переменных. >>> print(a, b) 42 101 >>> a = a ^ b >>> b = a ^ b >>> a = a ^ b >>> print(a, b) # Значения поменялись местами. 101 42 Если вы не знакомы с алгоритмом XOR (использующим поразрядный оператор XOR ^ ; о нем можно узнать по адресу https://ru.wikipedia.org/wiki/XOR-обмен), этот код выглядит загадочно. Проблема хитроумных трюков в том, что они приводят Использование модуля timeit для оценки быстродействия 119 к созданию запутанного, нечитаемого кода. Как говорится в тезисах «Дзен Python», «удобочитаемость важна». Что еще хуже, ваш хитроумный трюк может оказаться не таким уж хитрым и умным. Нельзя бездоказательно полагать, что он поможет; единственный способ это узнать — измерить и сравнить время выполнения. Вспомните принцип «Дзена Python» — «столкнувшись с неоднозначностью, боритесь с искушением угадать». Профилирова- нием называется систематический анализ скорости, использования памяти и других характеристик программы. Модуль timeit стандартной библиотеки Python может профилировать скорость выполнения способом, управляющим основными искажа- ющими факторами, из-за чего данные получаются более точными, чем при простой регистрации времени запуска и завершения программы. Не пишите собственный непитонический код профилирования, который может выглядеть примерно так: import time startTime = time.time() # Сохранение времени запуска. for i in range(1000000): # Код выполняется 1 000 000 раз. a, b = 42, 101 a = a ^ b b = a ^ b a = a ^ b print(time.time() - startTime, 'seconds') # Вычисление затраченного времени. На моей машине при выполнении этого кода выводится следующий результат: 0.28080058097839355 seconds Вместо этого можно передать код Python в строковом аргументе функции timeit. timeit() ; функция сообщает среднее время, затраченное на выполнение строки кода 1 000 000 раз. Если вы хотите проверить фрагмент из нескольких строк кода, разделите их символом ; : >>> timeit.timeit('a, b = 42, 101; a = a ^ b; b = a ^ b; a = a ^ b') 0.19474047500000324 На моем компьютере выполнение этого кода заняло приблизительно 1/5 секунды. Насколько это быстро? Сравним с кодом перестановки целых чисел, использующим третью временную переменную: >>> timeit.timeit('a, b = 42, 101; temp = a; a = b; b = temp') 0.09632604099999753 Сюрприз! Код с третьей временной переменной не только лучше читается, но и вы- полняется почти вдвое быстрее метода XOR! «Умный» способ экономит несколько байтов памяти, но за счет скорости и удобочитаемости кода. Если только вы не пишете программы для крупного центра обработки данных, жертвовать удобочи- таемостью для экономии нескольких байтов памяти или наносекунд процессорного 120 Глава 6.Написание питонического кода времени не стоит. В конце концов, память стоит дешево, и только на чтение этого абзаца вы потратили миллиарды наносекунд. Что еще лучше, значения двух переменных можно поменять местами при помощи трюка множественного присваивания, также называемого итерируемой распаковкой: >>> timeit.timeit('a, b = 42, 101; a, b = b, a') 0.08467035000001033 Этот код получается не только самым удобочитаемым, но и самым быстрым! И мы знаем это не потому, что предположили, но и потому, что провели объективные измерения. Функция time.time() также может получить второй строковый аргумент с кодом настройки. Код настройки выполняется однократно до кода первой строки для выполнения любой необходимой подготовки. Этот код может импортировать какой-нибудь модуль, присвоить начальное значение переменной или выполнить другое необходимое действие. Также можно изменить количество испытаний по умолчанию; для этого следует передать целое число в ключевом аргументе number Например, следующий тест измеряет время, необходимое модулю Python random для генерирования 10 000 000 случайных чисел от 1 до 100. (На моей машине для этого требуется около 10 секунд.) >>> timeit.timeit('random.randint(1, 100)', 'import random', number=10000000) 10.020913950999784 Хорошее правило для программистов: сначала заставьте свой код работать, а потом переходите к оптимизации. Когда у вас уже имеется работоспособная программа, тогда и стоит браться за повышение ее эффективности. Неправильное использование синтаксиса Если Python — не первый ваш язык программирования, то при написании кода Python вы можете использовать стратегии, знакомые вам по другим языкам. А может, вы изобрели необычный способ написания кода Python, потому что не знали, что существуют общепринятые практики. Ваш неуклюжий код работает, но изучение стандартных способов написания питонического кода сэкономит время и усилия. В этом разделе я объясняю неправильные решения, используемые про- граммистами, и то, как должен выглядеть правильно написанный код. Использование enumerate() вместо range() При переборе списка или другой последовательности некоторые программисты используют функции range() и len() для генерирования целых индексов от 0 до длины последовательности (не включая последнее значение). В таких циклах for Неправильное использование синтаксиса 121 часто используется переменная с именем i (сокращение от index ). Например, вве- дите следующий непитонический пример в интерактивной оболочке: >>> animals = ['cat', 'dog', 'moose'] >>> for i in range(len(animals)): ... print(i, animals[i]) 0 cat 1 dog 2 moose Идиома range(len()) прямолинейна, но далеко не идеальна, потому что она плохо читается. Вместо этого лучше передать список или последовательность встроенной функции enumerate() , которая возвращает целочисленный индекс и элемент с этим индексом. Например, можно написать питонический код следующего вида: >>> # Пример питонического кода >>> |