Очаровательный Python: Изящество и неловкость Python. Часть 1 (исходники)Источник: IBM developerWorks Россия Дэвид Мерц
По сравнению с "золотым веком" популярности Python 1.5.2 - в течение многих лет стабильной и надежной версии языка - Python приобрел множество новых синтаксических возможностей и встроенных функций и типов. Для каждого изменения в отдельности имелось достаточно веское основание, однако в целом из-за них современный Python - уже не тот язык, который при достаточном опыте можно выучить за один вечер. Помимо этого, с некоторыми изменениями связаны не только преимущества, но и потенциальные неприятности. В этой статье я рассмотрю некоторые неочевидные возможности последних версий Python и постараюсь определить, какие из них действительно полезны, а какие - просто лишнее усложнение языка. Моя статья - это попытка указать на несколько важных моментов специалистам, не использующим Python постоянно: от программистов на других языках до ученых, для которых программирование - только вспомогательный инструмент. При возникновении затруднений я предлагаю возможные решения. Проклятие упорядоченийПри переходе с Python 2.0 на Python 2.1 произошла загадочная вещь. Сравнимые в прошлом объекты в новой версии при сравнении вызывали исключения. В частности, стало невозможным сравнение комплексных чисел как с другими комплексными (тип complex), так и с действительными (типы int, float и long) числами. В действительности эта проблема появлялась и ранее, при сравнении обычиных и Unicode-строк, но только в некоторых особых случаях. По моему мнению, это - неудачное и просто очень странное изменение. В старой доброй версии 1.5.2 я был уверен, что оператор неравенства возвратит какое-либо значение вне зависимости от типов сравниваемых объектов. Конечно, зачастую смысла в этом результате не было (нельзя сравнивать строку с числом), но по крайней мере это был результат. После внесения изменений некоторые адепты Python завели спор о том, что правильно было бы наложить запрет на любые сравнения объектов разных типов - по крайней мере при отсутствии явно определенных операторов сравнения. Мне кажется, что при наличии пользовательских классов и множественного наследования этот вариант вызовет сильные затруднения. К тому же было бы крайне неудобно не иметь возможности сравнивать друг с другом float, int или long (или, скажем, Однако каким бы оно ни было, появились бы сильные отличия от ранних версий языка. На настоящий момент мы имеем совершенно беспорядочные правила сравнения, при которых не спасает даже знание типов сравниваемых объектов, а отношения неравенства не являются транзитивными или замкнутыми: Листинг 1. Возможность сравнения зависит и от типа, и от значения
В качестве особо утонченного издевательства, несмотря на то, что комплексные числа теперь несравнимы с большинством других численных типов, операторы неравенства тем не менее возвращают вполне определенное значение при сравнении с большинством нечисленных типов. Я понимаю, что "чистая" теория утверждает, что Листинг 2. "Сюрпризы" при сравнении
С точки зрения "чистой" теории ни одно из этих сравнений недопустимо. Чудеса клоунады: сортировка гетерогенных последовательностейИногда заходит спор о том, корректно ли сравнивать экземпляры несопоставимых типов. Но Python с легкостью производит подобные сравнения, и это хорошо соотносится с принципом "duck typing" (судить об объекте не по его типу , а по его поведению ). Коллекции в Python часто состоят из объектов различных типов, в предположении, что удастся сделать что-либо похожее с каждым из объектов. Частый пример такого действия - кодирование нескольких абсолютно различных по типу объектов для передачи по внешним каналам. Для большинства подобных действий не требуется определять отношения неравенства. Однако есть один очень частый случай, когда наличие сравнений оказывается крайне полезным: сортировка , обычно для списков (lists) или аналогичных пользовательских типов. Зачастую необходимо обрабатывать коллекцию в осмысленном порядке (например, просматривать данные от меньших элементов к большим). Иногда же требуется просто определить жесткий порядок элементов в нескольких коллекциях (например, чтобы определить различия между ними). В таких случаях может понадобиться выполнять одни действия, когда объект содержится в обоих списках, и другие, когда он находится только в одной из коллекций. Вызов Листинг 3. Выполнение различных действий в зависимости от вхождения элемента в два списка
Иногда удобно локально определить упорядочение даже при наличии разнородных элементов (например, обрабатывать числа с плавающей запятой "по порядку", хотя нельзя сказать, больше они или меньше обрабатываемых там же строк). Проблемы сортировкиЕстественно, приведенный выше алгоритм зачастую вызывает ошибки, причем практически случайным образом. Например, вот небольшой набор списков, которые могут встретиться в подобной программе в роли Листинг 4. Винегрет из сортируемых и несортируемых списков
Я написал небольшую программу, которая пытается отсортировать каждый список: Листинг 5. Результаты сортировки
Часть полученных результатов следует из ранее описанных проблем. Однако обратите внимание на списки (9) и (10), которые содержат одни и те же элементы в разном порядке: успех сортировки зависит не только от типов и значений элементов , но и от деталей конкретной реализации Устраняем проблемы сравненияПосле версии 1.5.2 в Python появился очень полезный тип данных: множества (sets), сначала как стандартный модуль, а впоследствии и во встроенном варианте (хотя некоторые дополнительные возможности по-прежнему вынесены в модуль). Во многих аналогичных только что описанным случаях для получения объединения или пересечения достаточно вместо того, чтобы писать собственные программы сравнения, просто использовать вместо списков (lists) множества (sets). Например: Листинг 6. Множества и операции на них
Приведенный пример явно обозначает одну странность: операции на множествах используют отношение равенства, а не эквивалентности. Возможно, в этом есть смысл, но меня лично удивляет, когда в объединении содержится число с плавающей запятой Листинг 7. Странные результаты операций над множествами
Все же в первом приближении множества очень полезны. Тем не менее стоит помнить о возможности обойти проблему при помощи собственных функций сравнения. В Python до версии 2.4 была возможность определить собственную функцию сравнения Решением проблемы неэффективности использования Листинг 8. Стабильный и универсальный алгоритм сортировки
Учтите, что порядок элементов может оказаться не совсем таким, каким вы ожидали его увидеть: он не такой, каким был бы при успешной сортировке без использования оболочек. Например, элементы различных численных типов не перемешиваются внутри последовательности, а оказываются в разных частях отсортированного списка. Но по меньшей мере обеспечен постоянный порядок элементов для практически любого списка (хотя при наличии объектов пользовательских типов такая процедура все-таки может подвести). Генераторы как "не вполне последовательности"В своем развитии Python значительно увеличивал ориентированность на итераторы (laziness). Уже в нескольких последних версиях языка есть возможность определения генераторов при помощи ключевого слова Итерирование по Python развивается в направлении использования отложенных вычислений для создания последовательностей, и это замечательно. Такой подход позволяет экономить оперативную память и ускорять действия, особенно при работе с очень большими последовательностями. Проблема заключается в некоторой "шизофреничности" Python в том, что касается сходств и различий между "настоящими" последовательностями и итераторами. Основная сложность в том, что такой подход нарушает принцип "duck typing" - возможность работать с объектом до тех пор, пока он правильно воспринимает запросы, не налагая каких-либо ограничений на его тип. Многие итераторы (или другие подобные объекты) иногда работают как последовательности, а иногда нет, и наоборот: последовательности могут вести себя как итераторы, но не всегда. Такое поведение может быть далеко не очевидно для находящихся вне узкого круга погруженных в священные таинства Python. РазличияГлавное общее свойство и итераторов, и последовательностей - в том, что каждый из них поддерживает собственно итерирование по себе - с использованием ли цикла Листинг 9. Последовательности и итераторы
Для каждого из данных объектов можно написать Листинг 10. Успех и ошибки индексации
Приложив некоторые усилия, можно получить индексацию на любой последовательности или итераторе. Простейший способ - запустить цикл вплоть до нужного места. Можно использовать более изощренные способы, например такой: Листинг 11. Эмулируем индексирование
Предварительный вызов Листинг 12. Достаем несколько элементовe
Класс-оболочкаДля удобства приведенные процедуры можно объединить в класс-оболочку: Листинг 13. Индексация на итераторах
При некотором желании можно заставить объект работать и как последовательность, и как итератор. Но количество усилий, которое для этого требуется, неоправданно велико; индексирование должно бы "просто" работать, вне зависимости от того, используется ли оно для последовательности или для итератора. Следует заметить, что оболочка В следующей статье этой серии я постараюсь рассмотреть концепцию свойств, то есть вызов методов при помощи синтаксиса атрибутов. |