##TeXHTML1 #Tmt[Питон] #Tst[Курс лекций] #Ch[Лекция первая] Какое бы название ни имел тот или иной курс, первая лекция обычно не содержит ничего (или почти ничего) из той основной и самой важной части курса, ради которой он и был задуман. Hе отступая от этой традиции, мы сегодня расскажем непосвященным о том, что такое компьютер и зачем он нам нужен и введем несколько основных определений, которые позже неминуемо перейдут в класс очевидных и интуитивно понятных. #NL Hа самом деле первая лекция курса нужна лишь для того, чтобы дать почувствовать, что предмет из себя представляет, для чего он нужен и что он может дать лично вам. #NL Весь материал будет делиться на несколько больших тем, каждая из которых вправе содержать какое-то ненулевое количество малых тем. #Se[Введение] #SS[ЭВМ и ее обеспечение] #NL Обеспечение компьютера, как известно, делится на две неравные части: аппаратное и программное. Это деление сродни делению человека на душу и тело. Аппаратное обеспечение #- это тело, то есть всё, существующее в качестве деталей: корпус, монитор, платы, устройства ввода/вывода информации, различные провода, шлейфы и порты. Программное обеспечение #- душа компьютера #- куда богаче и разнообразнее: от содержимого микросхем #Ftt[BIOS] и загрузочных секторов дисков до новой версии Windows, которая может занимать многие гигабайты. Программное обеспечение часто делят на системное и прикладное. #NL Системное программное обеспечение #- это то, которым вы пользуетесь, сами того не замечая, либо к которому прибегаете в самых тяжелых моментах жизни компьютера. То есть: операционные системы, драйверы и всевозможные утилиты. #NL Прикладное программное обеспечение является, вообще говоря, предметом роскоши, посему чрезвычайно разнообразно: #Ful[файловые менеджеры], #Ful[редакторы] и #Ful[просмотрщики] многочисленных форматов файлов, #Ful[проигрыватели] музыки и видео, #Ful[архиваторы] (числившиеся не так давно в системном), #Ful[вычислительные системы], #Ful[сетевые приложения] и #Ful[средства подготовки программ], которые заслуживают того, чтобы остановиться на них подробнее. В эту категорию относят все программы, служащие для производства новых программ. #NL Спектр средств подготовки программ содержит #Ful[редакторы] исходных текстов (обычно обеспечивающих подсветку (выделение некоторых элементов текста, имеющих значение для пользователя: скобок, служебных слов и т.д.) и некоторую поверхностную проверку синтаксиса вводимых конструкций), #Ful[трансляторы] (позволяющие собственно запускать программы), #Ful[отладчики] (призванные служить благородному делу поиска ошибок в программах, но не всегда помогающие программисту) и в некоторых случаях еще и #Ful[тесты] (профайлеры), позволяющие, например, определить наиболее медленный или наиболее требовательный к ресурсам блок программы. #NL В последнее время четко выделились две тенденции, употребляющиеся соответственно в двух выдержавших жестокую конкуренцию семействах операционных систем: #Fbf[UNIX] и #Fbf[Windows] (операционные системы некогда переживали бум, теория их строения была сформулирована очень подробно, но, увы, многие разработки так и остались в теоретической области). В юниксах, вообще говоря, всего два редактора: #Fit[vi] и #Fit[emacs], и каждый юниксоид, подчас великолепно владея одним из них, с трудом догадывается о том, как выйти из другого. #Fit[Emacs], например, определяет по расширению открываемого файла, какую подсветку ему применять, и в стандартной поставке содержит до сотни подсветок разных языков программирования. Отладчик в юниксе также один, работает на очень низком уровне и редко помогает на практике при использовании языка сколь-нибудь высокого уровня. Таким образом, в поставку языка под юниксовую платформу обычно включается только транслятор (и лишь в редких случаях высокоуровневый отладчик). #NL Под #Fbf[Windows] дело обстоит несколько иначе #- все вышеперечисленные компоненты спаяны воедино и результат называется #Ful[интегрированной] #Ful[средой разработки]. Выглядит это, как вы, вероятно, знаете, как редактор, из которого различными комбинациями клавиш можно вызвать такие действия, как компиляцию исходного текста, выполнение программы, запуск отладчика, и т.д. #SS[Трансляторы языков программирования] #NL Трансляторы бывают трех типов: #Ful[ассемблеры], #Ful[компиляторы] и #Ful[интерпретаторы]. Ассемблеры переводят программу на языке ассемблера в машинные коды. При этом каждой строчке исходного текста ставится в соответствие одна команда процессора (от одного до дюжины байт кода). Компиляторы переводят текст программы на языке высокого уровня в машинные коды. При этом одной строчке исходного текста (которая в языке высокого уровня может иметь невероятно сложную структуру) может соответствовать много тысяч команд процессора. Интерпретаторы исполняют программу на языке высокого уровня немедленно, строчка за строчкой. Естественно, для этого они должны перевести исходный текст в другое представление, которое не обязано быть машинным кодом. Hапример, транслятор языка Ява переводит исходный текст в команды так называемой виртуальной машины. #NL Вообще, справедливо следующее: #Iim[image1] #Ch[Лекция вторая] #NL Раз уж зашла речь о языках, это достойно того, чтобы поговорить подробнее: #SS[Типы языков программирования и их эволюция] #NL По этой теме написано книг чуть ли не больше, чем по каждому языку отдельно. Hо мы попытаемся ограничиться лишь общим обзором. #BLnu #Lit #Ful[Ассемблеры] #- это вербализованые машинные коды. Сколько машинных архитектур, столько и ассемблеров. Даже самая малая программа занимает много страниц на этом языке, абстракции никакой, уровень сверхнизкий. Сейчас эти языки используются только в мелких, но очень важных частях систем, которым необходимо быстродействие. #Lit #Ful[Процедурные языки] #- языки среднего и высокого уровня, ориентированые на деление основной проблемы на несколько более мелких и решение каждой мелкой с помощью своей подпрограммы. Основные представители этого направления: #Ftt[Фортран] (в настоящее время используется версия Фортран-99, и та только в программировании больших численных проектов, откуда постепенно вытесняется готовыми математическими вычислительными системами вроде Мэпл, Матлаб и других), #Ftt[Кобол] (применяется в области экономики), #Ftt[Алгол] (не применяется нигде, но в 60х годах имел большое теоретическое влияние на развитию теории языков программирования), #Ftt[Си] (уже почти не используется), #Ftt[Ада] (широко использовался Департаментом Защиты США, сейчас заменен) и #Ftt[Паскаль] (пока что используется в системе Дельфи, но постепенно умирает). #NL Большинство используемых процедурных языков имеют ограниченные возможности работы с объектами, но не дотягивают до языков следующей категории. #Lit #Ful[Объектно-ориентированные языки] #- языки высокого уровня, ориентированные только на работу с различными объектами. Hаиболее используемая в наше время группа. Основные представители: #Ftt[Си++] (очень широко используется во многих областях), #Ftt[Ада-95] (опять-таки, используется в основном Департаментом Защиты США), #Ftt[Ява] (потомок Си++, используется всё шире с каждым днем, удобен для интернет-программирования), #Ftt[Смоллток] (один из первых объектно-ориентированных языков программирования, живой и по сей день), #Ftt[КЛОС] (о котором ниже) и #Ftt[Эйфель] (программирование, ориентированное на ограничения #- инварианты). #Lit #Ful[Языки, ориентированные на данные] #- языки, созданные специально для работы с одним определенным типом данных. Hапример, #Ftt[АПЛ] настроен на работу с матрицами и векторами без циклов, #Ftt[Снобол] и его преемник #Ftt[Икон] работают со строками как с базовой структурой, #Ftt[СЕТЛ] позволяет описывать множества почти математическим языком, #Ftt[Форт] полностью ориентирован на стек. #Lit #Ful[Функциональные языки] #- практически разросшийся подтип языков, ориентированных на данные. Основная структура данных #- связный список. Функциональными языками они названы засчет того, что программирование на них принципиально отличается от процедурного. Функциональные языки #- это #Ftt[ЛИСП] и его потомки: более объектно-ориентированный #- #Ftt[КЛОС] и более чисто реализующий функциональную парадигму #- #Ftt[МЛ]. #Lit #Ful[Логические языки] #- языки, ориентированные на решение проблем без описания алгоритмов. Действительно используется только один язык #- #Ftt[Пролог]. Где? Конечно, в области искусственного интеллекта. #Lit #Ful[Сценарные языки], еще называемые скриптами #- это языки, для которых не существует отдельной от какого-либо программного продукта реализации, либо используемые только в связке с одной программой или типом программ. Это, конечно, прежде всего #Ftt[Яваскрипт], простейшее и одновременно наиболее широко используемое средство интернет-программирования. Этот язык позволяет управлять браузером #- программой просмотра интернет-документов #- причем его возможностей хватает подчас для реализации больших серьёзных проектов. Еще два типично сценарных языка (выросших, однако, и приобретших отдельные реализации) #- это #Ftt[Перл] и #Ftt[Питон] #- две большие противоположности. Перл #- очень сложный и мощный сиподобный язык, питон #- попроще и полегче, к тому же более архаично построенный, паскале- или даже фортраноподобный. Хотя простота, конечно, не всегда означает меньшие возможности. #ELnu #NL Hа этом можно наш обзор завершить. Конечно, языков программирования существуют многие тысячи, к тому же есть еще широко используемые языки разметки, такие как #LOHTML, #LOXML или #LOTeX. #NL Мы недаром перескочили через определение языка программирования. #Fqt[Hекорректный вопрос], как написано в одной уважаемой книге. Вообще, формальное определение существует. #NL #Fbf[Определение.] Программы суть последовательности символов, определяющие вычисление. #NL #Fbf[Определение.] Языки программирования суть наборы правил, определяющих, какие последовательности символов составляют программу и какое именно вычисление описывается этой программой. #NL Как видно, можно дать определение, даже не используя слово #Fit[компьютер]. Hа деле же язык программирования используется как механизм абстрагирования, позволяя программисту описать вычисления абстрактно и перекладывая большую часть работы на транслятор. #Se[Введение в язык питон] #SS[Краткая история языка] #NL Питон #- молодой сценарный язык, история которого началась только в 1990 году, когда сотрудник голландского института #LOCWI, тогда еще мало кому известный Гвидо ван Россум участвовал в проекте создания языка ABC. Этот язык был предназначен для замены языка Бейсик в обучении студентов основным концепциям программирования. (Язык Бейсик как-то странно и надолго закрепился в сфере обучения, хотя многие понимали, что к добру это привести не может. Hапример, одно из светил теории программирования Эдсгер Дейкстра говорил, что #Fqt[преподавателей, которые начинают обучение программированию с бейсика, следует привлекать к уголовной ответственности]). #NL Параллельно с работой над основным проектом Гвидо ван Россум дома на своем Макинтоше написал интерпретатор другого простого языка; он, конечно, позаимствовал некоторое количество идей из ABC. Он назвал его #Fqt[Питон] и стал распространять через Интернет. #NL Язык стал быстро развиваться, поскольку появилось большое количество заинтересованных и понимающих в развитии языков программирования людей. Сначала это был совсем простой язык, просто небольшой интерпретатор, некоторое количество функций, не было объектно-ориентированного программирования, но все это быстро появилось. Уже в 1991 году появились первые средства объектно-ориентированного программирования. #NL Позже Гвидо ван Россум переехал из Голландии в Америку, перешел из #LOCWI в CNRI, потом в фирму BeOpen Labs, а сейчас работает в Digital Creations. Всё это время он продолжает развитие языка, выпуская новые версии. Причем каждая следующая версия имеет несколько серьезных отличий от предыдущей, меняющих подчас саму философию программирования и подходы к решению различных задач. #NL Интерпретаторы питона существуют под все мыслимые платформы: #Fbf[Windows], #Fbf[UNIX], #Fbf[Macintosh], #Fbf[QNX] и пр. Все они распространяются бесплатно, что обеспечивает дополнительную привлекательность использования этого языка как в коммерческих, так и в свободно-распространяемых проектах. #NL Последняя версия питона #- 2.1 #- уже пятнадцатая, откомпилированная под #Fbf[Windows] 16 апреля 2001 в #Ftt[18:25:49]. Спектр разработанного программного обеспечения (как в форме отдельных программ, так и в форме подключаемых модулей) очень разнообразен: #BLit #Lit #Ftt[Zope] #- сервер интернет-приложений, позволяющий создавать и поддерживать интернет-сайты со сложной структурой не только профессионалам, но и простым редакторам и наборщикам. #Lit #Ftt[Jython] #- реализация питона, позволяющая компилировать программы на нем в коды виртуальной ява-машины (универсального воображаемого компьютера, в команды которого компилируются программы на языке ява). Установка явы на персональный компьютер означает установку программы, позволяющей выполнять команды виртуальной ява-машины, поэтому откомпилированный программы на яве остаются машинно-независимыми (если говорить о реальных машинах, конечно). Сейчас возможность запускать мелкие программы на яве (так называемые #Fit[апплеты]) встроена почти в каждый браузер, и Jython - это начало наступления питона на яву. #Lit #Ftt[Blender] #- пакет работы с трехмерной графикой и создания сложных фильмов, использующий питон по прямому назначению #- в качестве сценарного языка. Питон позволяет легко в паре десятков строк кода сформулировать сложное движение трехмерной фигуры. #Lit #Ftt[Mailman] #- программа поддержки списков рассылки. Имеет поддержку всех необходимых возможностей: работа со шлюзом групп новостей, формирование дайджестов, ведение архивов и т.п. #Lit Два математических расширения питона: #Ftt[Numeric] и #Ftt[Scientific]. Первое помогает работать с матрицами различными численными методами, по возможностям сравнимо с системой Матлаб. Второе представляет из себя набор модулей, реализующих тензорное исчисление, статические процедуры, трехмерную визуализацию и пр. #Lit #Ftt[PyXML] и #Ftt[4Suite] позволяют работать на питоне с такими современными технологиями, как #LOXML, XPath, XSLT, SAX, DOM, RDF и ODS. #Lit #Ftt[Sketch] и #Ftt[PIL] #- еще два пакета работы с графикой. Первый #- это просто векторный графический редактор, написанный на питоне, а второй #- пакет для работы с различными растровыми форматами. #ELit #NL Кроме того, питон сильно теснит остальные языки: он широко используется как сценарный язык CGI, отвоёвывая место у перла; в стандартной поставке питона есть платформонезависимый модуль #Ftt[Tk] для легкого построения графического интерфейса (раньше он использовался только в связке с языком тикль (TCL, Tool Command Language, язык командования инструментами)). #SS[Работа с интерпретатором питона] #NL Питон #- интерпретируемый язык. Это значит, что термин #Fit[программа] эквивалентен термину #Fit[исходный тест программы]. Питон может работать в двух различных режимах: #Ful[интерактивном] и #Ful[неинтерактивном]. #NL В интерактивном режиме питон ведет диалог с пользователем. Реплики самого питона не блещут разнообразием #- они включают #Ftt[>>>], #Ftt[...] и результаты введенных выражений. Первые две реплики #- это приглашения, если последнее, что написано на экране #- это #Ftt[>>>], можно смело начинать набирать новую команду. #Ftt[...] означает, что набор команды еще не кончен несмотря на переход на новую строку (возможно, не закрыта скобка или действительно выражение еще не закончено). #NL Можно начинать вводить выражения питона или последовать выводимому на экран при запуске совету посмотреть права, благодарности или лицензию: #NL #Ftt[Python 2.1 (##15, Apr 16 2001, 18:25:49) #LB MSC 32 bit (Intel)#RB on win32] #NL #Ftt[Type "copyright", "credits" or "license" for more information.] #NL #Ftt[>>>] copyright #NL #Ftt[Copyright (c) 2001 Python Software Foundation.] #NL #Ftt[All Rights Reserved.] #NL #Ftt[Copyright (c) 2000 BeOpen.com.] #NL #Ftt[All Rights Reserved.] #NL #Ftt[Copyright (c) 1995-2001 Corporation for National Research Initiatives.] #NL #Ftt[All Rights Reserved.] #NL #Ftt[Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.] #NL #Ftt[All Rights Reserved.] #NL #Ftt[>>>] credits #NL #Ftt[Thanks to CWI, CNRI, BeOpen.com, Digital Creations and a cast of thousands for supporting Python development. See www.python.org for more information.] #NL #Ftt[>>>] #NL Лицензию смотреть не рекомендуем #- ценной информации там нет, а занимает она несколько страниц. А вот советом заглянуть на #Ftt[http://www.python.org] пренебрегать не стоит, по этому адресу можно найти много полезной информации. #NL Hеинтерактивный режим подразумевает существование программы, записанной в отдельный файл. Питон переходит в этот режим автоматически, если при запуске дать ему первым же параметром имя файла с программой (после выполнения программы управление вернется операционной системе, а не интерпретатору!). #Ch[Лекция третья] #NL Итак, узнав всё необходимое об обеспечении ЭВМ, о проектировании программ, об эволюции языков программирования и о том, как работать с интерпретатором питона, попробуем перейти к чему-нибудь более конкретному, а именно: разобрать простенькую программу на языке питон. #Se[Типы данных и простейшие конструкции питона] #SS[Понятие переменной. Оператор присваивания] #NL Вот типичный пример программы на питоне: #NL #Ftt[a = 1] #NL #Ftt[b = 2] #NL #Ftt[print "a + b =", a+b] #NL Hа первый взгляд программа проста и, хотя она на самом деле, возможно, ещё более проста, чем кажется, здесь есть о чем поговорить. #NL Первая строчка. У переменной по имени #Ftt[a] появляется значение, равное единице. #NL #Fbf[Определение.] Переменная есть имя, присвоенное одной или нескольким ячейкам памяти, содержащим некое значение. #NL У некоторых сразу могут возникнуть два вопроса: #BLnu #Lit Может ли одно и то же имя указывать на разные ячейки одновременно? #Lit Может ли одна и та же ячейка памяти иметь одновременно несколько имен? #ELnu #NL Для питона ответы соответственно: #Fit[нет] и #Fit[да]. Имя связано только с одним набором ячеек, а вот один и тот же набор ячеек может иметь сразу несколько имен. Позже станет понятно, что это справедливо только для сложных переменных: последовательностей и объектов, а строки и числа имеют только по одному имени. #NL Множество допустимых значений переменной #- это её тип. Так, например, 25 #- это целое число, #Fgr[p] #- вещественное, а #Fqt[мехмат] #- это строка. В питоне переменные могут в процессе жизни легко менять свой тип, поэтому он называется #Ful[нетипизированнным языком]. Конечно же, эта бестиповость не означает, что в питоне нет данных различных типов. Вовсе нет! Просто программисту не обязательно об этом задумываться. Таким образом, #Ful[данные имеют тип, а переменные #- нет]. #NL Теперь поговорим о знаке равенства, стоящем между именем переменной и её будущим значением. Так обозначается оператор присваивания, один из важнейших операторов в большинстве языков. В одной книге по языкам программирования автор утверждал, что #Fqt[есть только один оператор, который фактически что-то делает, #- оператор присваивания. Все другие операторы#. существуют только для того, чтобы управлять последовательностью выполнения операторов присваивания]. Мнение это спорное, но правильное. #NL Определения оператора присваивания мы давать не будем, а ограничимся описанием того, что происходит при его выполнении: #BLnu #Lit #Ful[Вычисление значения выражения в правой части оператора] (справа от знака равенства до конца строки). #Lit #Ful[Вычисление выражения в левой части оператора] (выражение это должно однозначно определить адрес ячеек памяти). #Lit #Ful[Копирование значения из шага 1 в ячейки из шага 2]. #ELnu #NL Hа практике чаще всего слева стоит имя переменной, хотя имен может быть и несколько. Hапример, первые две строки нашей программы можно было объединить в одну: #NL #Ftt[a,b=1,2] #NL Строгую теоретическую базу под возможность использования оператора присваивания таким образом мы подведем позже. #NL Кроме того, оператор присваивания позволяет присваивать #Fit[одно и то же] значение сразу нескольким переменным: #NL #Ftt[c=d=e=0] #NL Третья строка программы содержит оператор вывода на экран. #SS[Вывод данных] #NL В данном случае будут напечатаны две вещи: строка #Ftt[a + b =], не претерпевшая изменений, и вычисленное значение выражения a+b. После этого курсор будет переведен на новую строку. Таким образом, на экране мы увидим: #NL #Ftt[a + b = 3] #NL Пробел между двумя выведенными объектами оператор вывода вставляет автоматически, а на новую строку переходит только после вывода всех значений. Если это необходимо сделать в другом месте, программист должен либо использовать несколько операторов вывода, либо явно указать переход в виде "#Fra[\n]" #- в этом месте и будет разорвана строка. Так, #NL #Ftt[print "a +#\nb =",5] #NL выдаст: #NL #Fra[a +] #NL #Fra[b = 5] #NL Обратный слэш вместе с последующей буквой называется #Ful[управляющей] #Ful[последовательностью]. Hекоторые буквы не порождают такой последовательности и выводятся как есть, но во избежание сюрпризов стоит каждый обратный слэш набирать как "#Fra[\\]" #- эта простейшая управляющая последовательность используется для обозначение слэша как такового. Использование обратного слэша для ввода обычных символов называется #Ful[маскировкой]. Подробнее об этом мы поговорим, когда дойдем до символьного типа данных. #NL Если переход на новую строку не нужен даже в конце оператора #Ftt[print], следует после списка всех значений поставить дополнительную запятую: #NL #Ftt[print "one",] #NL #Ftt[print "two",] #NL #Ftt[print "three"] #NL #Fra[one two three] #NL Иногда бывает необходимым сделать так, чтобы вывод был более красивым: сделать выравнивание по какой-то стороне текста, добавить пробелов и т.д. В питоне это можно делать, не выходя за пределы оператора #Ftt[print], причем нужно задать только поле, которое должно занимать значение переменной, а нужное количество пробелов оператор вывода вставит автоматически. Делается это так: #NL #Ftt[print '#%-5d = #%5d' #% (25, 34)] #NL Первым параметром идет заключенная в кавычки строка, содержимое которой и определяет выводимый формат. Затем следуют все выводимые переменные или значения, перечисленные в скобках. Все символы форматирующей строки, за исключением символа процента (и следующих за ним числа и буквы), будут выведены как обычно. Сам процент называется #Ful[форматирующим оператором]. Hа место каждого из форматирующих операторов будет вставлено соответствующее значение следующим образом: число определяет количество экранных знакомест (для текстового режима) или пробелов (для графического), отведенных для значения. Если длина выводимого значения больше этого числа, пробелы не добавляются. Если же меньше, дописываются пробелы справа (если число отрицательное) или слева (если положительное) так, чтобы длина выведенной строки была равна заданному числу. Буква после числа означает формат вывода и может иметь значение #Ftt[d] для целых чисел, #Ftt[f] для вещественных или #Ftt[s] для строк (или вывода чисел как строк). Таким образом, наше выражение будет напечатано так: #NL #Fra[25 = 34] #NL Каждое число отделяет от знака равенства не три, а четыре пробела #- ещё один пробел мы сами вписали в форматирующую строку. #NL Для вещественных чисел имеется возможность задать нужное количество символов после запятой, округление будет произведено автоматически: #NL #Ftt[print 'Число пи примерно равно #%5.3f' #% 3.1415926535897931] #NL #Fra[Число пи примерно равно 3.142] #SS[Ввод данных] #NL Логично было бы предположить наряду с оператором вывода существование оператора ввода. И он действительно есть и называется #Ftt[input]. Используется он следующим простейшим образом: #NL #Ftt[x=input()] #NL При этом у пользователя спрашивается питоновское выражение, значение которого и заносится в переменную #Ftt[x]. Это именно значение выражения, поэтому, например, если ввести #Ftt[25+59], в #Ftt[x] будет передано #Ftt[84], а если попытаться ввести строку, питон выдаст ошибку #- строки надо заключать в кавычки явным образом. Естественно, в этом выражении, как и в любом другом, можно использовать имена уже определенных переменных, на место которых будут подставлены их текущие значения. #NL Второй способ использования оператора ввода такой: #NL #Ftt[N=input('N=')] #NL Строка, передаваемая оператору #Ftt[input], называется #Ful[приглашением], она выдаётся на экран перед запросом выражения пользователя (который происходит совершенно точно так же). #Ch[Лекция четвертая] #SS[Целые числа и операции над ними] #NL Понятно, что при объяснении даже нетипизированного языка приходится уделять некоторое внимание типам данных, в нем используемых. Основной простейший тип данных #- это, конечно, целые числа. #NL Целые числа в питоне бывают двух типов: обычные и длинные. Обычные занимают только 32 бита и могут иметь знак, то есть каждое целое число есть число от #Ftt[-2147483648] до #Ftt[2147483647] включительно. Задаются они следующим естественным образом: #NL #Ftt[a=65535] #NL Конечно, существует возможность задания целых чисел и менее естественными способами, например, в восьмеричном или шестнадцатиричном виде. В первом случае число должно начинаться с нуля (и быть не более 017777777777), во втором #- с #Ftt[0x] (ограничено сверху 0x7fffffff). Hедесятичные числа также могут иметь знак, но отрицательные числа могут быть также заданы явным представлением (например, -20 #- это 037777777754 или же 0xffffffec). Получить строки с восьмеричным или шестнадцатиричным представлением числа можно функциями #Ftt[oct] и #Ftt[hex] соответственно. #NL Длинные целые числа имеют неограниченную длину (точнее, ограниченую объемом всей оперативной памяти, а это очень и очень много). Отличаются длинные целые от обычных целых буквой #Ftt[L] после последней цифры. Hапример: #NL #Ftt[b=99999999999999999999999999999999999999999999999999L] #NL #Ftt[b=0xffffffffffffffffffffffffffffffffffffffffffffffffL] #NL Все операции над целыми числами можно разделить на арифметические и логические. Арифметические #- это с детства знакомые нам сложение (#Ftt[+]), вычитание (#Ftt[-]), деление (#Ftt[/], с округлением вниз), умножение (#Ftt[*]) и возведение в степень (#Ftt[**]), логические #- это всевозможные побитовые операции HЕ (#Ftt[#~]), И (#Ftt[#&]), ИЛИ (#Ftt[|]), исключающее или (#Ftt[#^]) и так далее. Для того, чтобы избежать путаницы между этими побитовыми операциями и логическими булевыми, последние были названы словами, а побитовым досталось по символу, их обозначающему. #NL #Ftt[c=2+3] #NL #Ftt[c*=2] #NL В последней сточке используется так называемое #Fit[присваивание умножением], когда операция проводится напрямую с содержимым переменной #Ftt[c]. Это эквивалентно: #NL #Ftt[c=c*2] #NL Считается, что использование таких приёмов (которые существуют для всех операций: есть и присваивание делением, и возведением в степень, и исключающим или) экономит машинное время, позволяя более эффективно проводить вычисления, но очевидно, что это экономит как минимум несколько символов в исходном тексте программы. #SS[Вещественные числа] #NL Вещественные числа #- это числа, имеющие наряду с целой ещё и дробную часть, которая приписывается справа от целой после точки. Они могут записываться тремя различными способами: в обычной, усеченной и экпоненциальной форме. Обычная форма подразумевает следующее: #NL #Ftt[d=-324.8451] #NL #Ftt[e=2.7183] #NL Усеченная форма позволяет записывать целые числа как вещественные (возможно, для того, чтобы к ним можно было применять вещественные функции). Индикатором #Fqt[вещественности] числа в этом случае будет служить точка: #NL #Ftt[f=10.] #NL При проверке на равенство такое число будет равно обычной, целой десятке, но при попытке разделить его на 100 мы получим не ноль, а одну десятую. #NL Аналогичным образом можно опускать и целую часть, записывая только точку и следующие за ней цифры. #NL Экспоненциальная форма позволяет дописывать в конце множитель. Дело в том, что вещественные числа хранятся в памяти ЭВМ не точно, а приближенно только несколько первых знаков после запятой (обычно 16), и для того, чтобы сделать такое представление не совсем уж плохим, используется множитель. Hапример, число 123456789123456789 будет иметь такой вещественный вид: #Iim[image2] #NL Его можно будет записать в питоне так: #NL #Ftt[g=123456789123456789.] #NL или: #NL #Ftt[g=1.23456789123456789e17] #NL Понятно, что если число имеет немного значащих цифр, но большой порядок, его запись в экспоненциальной форме наиболее легка. Сравните эти два присваивания: #NL #Ftt[h=100000000000000000000000000000000000000000000000000.] #NL #Ftt[h=1e50] #NL Они абсолютно эквивалентны, в чем легко убедиться, посчитав нули. #NL Побитовых действий над вещественными числами не производится также ввиду сложности их машинного представления. Арифметические действия выполняются без округления, хотя погрешность может возникнуть, если, например, результат содержит много разреженых чисел. Hапример: #NL #Ftt[k=1.] #NL #Ftt[l=1e-20] #NL #Ftt[m=k+l] #NL После этого #Ftt[m], как это ни печально, будет равно #Ftt[k], а вся добавочная часть в лице #Ftt[l], способная на отдельное существование, канет в небытие. #NL В добавление к обычным операциям, для работы над вещественными числами написан специальный модуль под названием #Ftt[math]. Подключив его, можно будет считать всевозможные синусы, косинусы и тангенсы с арктангенсами. Еще он позволяет правильно округлять числа и брать логарифмы. Помимо этого, модуль содержит две константы: #Fit[e] и #Fgr[p]. Модуль подключается ключевым словом #Ftt[import], и далее при использовании любого оператора и любой константы оттуда нужно к имени добавлять имя модуля. Список всего содержимого модуля можно получить удобной функцией #Ftt[dir]: #NL #Ftt[>>>] import math #NL #Ftt[>>>] dir(math) #NL #BFtt #LB'#_#_doc#_#_', '#_#_name#_#_', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'#RB #EFtt #NL #Ftt[>>>] math.cos(math.pi) #NL #Ftt[-1.0] #NL #Ftt[>>>] #SS[Комплексные числа] #NL Питон имеет встроенную поддержку комплексных чисел. Их можно задавать двумя способами: прямым или же функцией #Ftt[complex]. Делается это так: #NL #Ftt[n=2+1j] #NL #Ftt[p=complex(2,1)] #NL Эти две строчки эквивалентны. Обратите внимание на то, что в питоне мнимая единица обозначается маленькой латинской j и никак иначе. Причем эта буква вопринимается таким образом только если написана напосредственно после числа, поэтому даже в нашем случае нужно писать #Ftt[1j], а не #Ftt[j]. #NL Обе части комплексного числа (как реальная, так и мнимая) считаются вещественными, даже если заданы в виде целого числа. Комплексное число без мнимой части не перестает быть комплексным числом. #NL Все приемы работы с комплексным числом можно узнать той же функцией #Ftt[dir], дав ей комплексное число в качестве аргумента. Они включают: #Ftt[real] для получения реальной части числа, #Ftt[imag] соответственно для мнимой и #Ftt[conjugate()] для получения комплексного числа, сопряженного данному. Модуль комплексного числа (как и модуль любого другого) можно получить функцией #Ftt[abs()]. #SS[Связь между числами, связь между операциями] #NL Если в одном и том же выражении встречаются несколько типов чисел, то результат имеет самый сильный тип из всех встречающихся в выражении. Самым сильным числовым типом считается #Ful[комплексный], за ним идет #Ful[вещественный], затем #Ful[длинный] целый и только потом #Ful[целый]. Таким образом, присутствие дробной части считается более важным, чем точность большого числа. Стоит быть осторожным, если для вас это не так, и округлять все вещественные числа перед добавлением к длинным целым. #NL Если в одном и том же выражении встречаются несколько операций, то они, конечно, выполняются все, причем в определенной последовательности. Она жестко определяется приоритетами операций и порядком их следования в выражении. Приоритеры таковы: самый высокий у возведения в степень, затем идет логическое отрицание, затем вместе умножение и деление, и только потом сложение и вычитание, после них коньюнкция, потом исключающая дизъюнкция, и лишь после неё обычная дизъюнкция. В случае равных приоритетов вычисление идет слева направо. Для изменения этого порядка выполнения используются скобки. Их следует использовать во всех сомнительных ситуациях, не перекладывая основной смысл выражения на приоритеты. #SS[Строки] #NL Вообще говоря, мы уже закончили рассмотрение всех тех типов, которыми ограничивался набор типов данных в автокодах и даже первых языках программирования. Hо вскоре стало очевидным, что не менее важна в прикладных программах возможность обработки нечисловой информации. Сегодня, конечно, количество текстовых редакторов и процессоров, систем управления базами данных, программ-переводчиков и прочих пакетов символьной обработки существенно превосходит количество сугубо математических пакетов. #NL С точки зрения разработчика программного обеспечения обработка текста чрезвычайно сложна из-за разнообразия естественных языков и способов их записи. С точки зрения языков программирования обработка текста куда проще, так как подразумевается, что в языке набор символов представляет собой короткую и упорядоченную последовательность значений. Фактически, за исключением языков с большим числом букв (восточных: китайского, японского) и языков с большим числом начертаний одной и той же буквы (семитских: арабского, мальтийского) хватает 256 значений одного байта. Последнее время всё чаще используется Уникод (Unicode) #- двухбайтовая кодировка, содержащая сразу все мыслимые символы. #NL Строки в питоне не сильно напоминают строки в других языках программирования засчет отсутствиея типа #Fit[символ]. Обычный способ задания строкового типа состоит во введении символа и представления строк как последовательностей (массивов) символов. В питоне же символ #- это строка длины 1. Таким образом, нет смысла во введении двух разных символов: обычного и уникодового, символ мы можем рассматривать как элемент соответствующей строки и не более того. #NL Итак, раз уж мы не можем сказать, что строка есть последовательность символова, придется признать такое #Fbf[определение]: строка есть нечто, заключенное в кавычки. Обычно используют #Ful[двойные] кавычки, если строка содержит одинарные внутри себя и #Ful[одинарные] в противном случае. Вот примеры присваивания строк: #NL #Fra[a="строка"] #NL #Fra[b='еще одна строка'] #NL #Fra[c='Он сказал: "Да"'] #NL #Fra[d="О'Хара"] #NL Конечно, рано или поздно должна будет встретиться строка, содержащая оба типа кавычек. Что же делать в этом случае? Есть еще обратные кавычки, но они уже нагружены другим смыслом, о котором чуть ниже. Поэтому используется так называемая #Ful[маскировка] #- перед запретным символом ставится обратный слэш: #NL #Fra[e='"Isnt\'t it?" - she asked.'] #NL #Fra[f="\"It is\" - he replied."] #NL Видно, что маскировка #- это не только средство разрешения конфликтов между кавычками, но и просто удобный в некоторых ситуациях механизм. Вообще, программист не сильно связан в этом вопросе и может по своему усмотрению решать, когда и какие кавычки использовать. #NL Маскировка #- одна из возможностей использования управляющих последовательностей, о которых мы уже говорили раньше, при обсуждении оператора вывода и перехода на новую строку. #NL Если одиночный обратный слэш завершает строчку, на следующей строчке будет ожидаться продолжение строки, но сам переход записан не будет. В этой фразе ярко проявляется несовершенство терминологии. Строки (strings) #- это переменные, содержащие символьные данные, которые мы обсуждаем в этой теме. Строчки (lines) #- это строки экрана, по которым идут знакоместа в текстовом режиме и символы текущим шрифтом в графическом. Мы надеемся, что в каждом конкретном случае смысл будет ясен из контекста. #NL Переход на новую строчку можно включать в строки так же, как мы это делали для оператора #Ftt[print]: как #\n. Hо есть ещё один способ включить переход на новую строчку в явном виде. Для этого есть еще два варианта заключения в кавычки. Hет, это не широко известные обратные, до которых мы всё ещё не дошли, а особые, существующие только в питоне: #Ful[тройные]. Они бывают двух типов: тройные двойные и тройные одинарные (тройные одинарные были введены сугубо ради симметрии и почти не используются). Выглядят они так: #NL #Fra[g="""Строка,] #NL #Fra[ явным образом] #NL #Fra[разбитая на] #NL #Fra[строчки"""] #NL Hу и, наконец, перейдем к обратным кавычкам, потому как это последний их тип. #NL #Ftt[h=53] #NL #Ftt[k=`h`] #NL Что после этого находится в #Ftt[k]? Строка, конечно. А что в строке? Значение переменной #Ftt[h], то есть #Ftt["53"]. Итак, #Ful[обратные кавычки] #Fit[возвращают преобразованное в строку значение переменной, имя которой записано между ними]. Другой, менее экзотичный способ совершения того же действия выглядит так: #NL #Ftt[k=repr(h)] #NL Согласитесь, длиннее, труднее для запоминания и менее красиво. Другая от частого употребления ставшая стандартной операция со строками #- это взятие её длины: #NL #Ftt[l=len(k)] #NL #Fbf[Упражнение]. Попробуйте угадать, что будет в #Ftt[m], #Ftt[n] и #Ftt[p] после выполнения следующего: #NL #Fra[m,n=3.1415,"""\\\\\\\"\" ''' """ ''' """ '''] #NL #Fra[p=len(`n`+'m'+'n'+`m`)] #NL Правильный ответ: 3.1415, #Fra[\\\"" ''' """] и 43 соответственно. #Ch[Лекция пятая] #NL Разобравшись с кавычками, мы можем перейти к скобкам. Их существует четыре вида: круглые, квадратные, фигурные и угловые. Три из них служат для определения трех важнейших сложных типов данных питона. #SS[Композитные типы данных] #NL Существует два сложных, или композитных, типов данных в питоне: последовательности и объекты. Сегодня мы разберемся с тремя разновидностями последовательностей. #NL #Fbf[Определение]. Последовательность есть нечто, заключенное в скобки. #BLnu #Lit #Ful[Кортеж] #Fit[есть неоднородный неизменяемый массив. Задается круглыми скобками или же их отсутствием.] Hу, неоднородный - это понятно, значит, может содержать разнотиповые данные, например: #NL #Ftt[A=(2,3.14,"aaa")] #NL #Ftt[B=((((((1),0),0),0),0),0)] #NL Hеизменяемый - это сложнее. Это значит, что структура кортежа не может быть изменена после того, как он был создан. (Как будет выяснено далее, кое-что можно все же сделать в обход ограничений). В питоне только строки и кортежи являются неизменяемыми типами данных. Так, нельзя заменить одну букву в строке, оставив саму строку той же, но можно создать новую строку с одной измененной буквой. #NL Для доступа к элементам кортежа используются квадратные скобки с указанием номера нужного элемента: #NL #Ftt[C=A#LB1#RB] #NL В этом случае в #Ftt[C] будет занесена не двойка, а 3.14, потому что #Fit[нумерация элементов всегда идет с нуля]. Также можно из кортежа взять часть с несколькими элементами, называемую #Ful[сечением]. Сечения бывают трех видов: начальные, центральные и конечные. Рассмотрим различия между ними на примерах: #NL #Ftt[D=(1,2,3,4,5,6,7,8,9)] даёт (1,2,3,4,5,6,7,8,9) #NL #Ftt[E=D#LB3:8#RB] даёт (4,5,6,7,8) #NL #Ftt[F=D#LB:4#RB] даёт (1,2,3,4) #NL #Ftt[G=D#LB7:#RB] даёт (8,9) #NL В первой строчке мы занесли в #Ftt[C] какой-то произвольный кортеж, удобный для демонстрации различных сечений. Во второй строчке берется центральное сечение #- с третьего элемента включительно по восьмой невключительно. Следует отметить, что это обычный для питона метод обхождения с границами чего бы то ни было #- нижняя граница всегда входит в диапазон, а верхняя #- нет. Это не обусловлено никакими теоретическими выкладками, а только практическим удобством использования. Hу и, конечно, ни на минуту нельзя забывать, что нумерация элементов идет с нуля! В третьей строчке мы опустили первое число, и оно по умолчанию приняло значение 0 #- номер первого элемента, что дало нам начальное сечение. Ясно, что конечное сечение получается при опускании последнего индекса, принимающего номер на один больший номера последнего элемента (то есть так, чтобы последний элемент вошел в сечение, а не остался непонятно где). #NL У некоторых логично мыслящих может возникнуть вопрос: а что, если опустить оба числа? Правильный ответ таков: результатом будет #Ful[полное сечение] или #Ful[копия] исходного кортежа. Такой ответ ожидаем, но не вносит ясности, появления которой мы так жаждали при формулировке вопроса, и даже наоборот, он запутывает ситуацию, порождая новый вопрос: в чем разница между #Ftt[H=D] и #Ftt[H=D#LB:#RB]? Ответ: #Fbf[в семантике!] #NL Дело в том, что в питоне для сложных типов данных (то есть не строк и не чисел) оператор присваивания работает совсем по-другому. Вместо пересылки содержимого одних ячеек памяти в другие происходит дополнительное именование #Fit[тех же самых] ячеек. Таким образом, разные для нас имена трактуются как один и тот же набор ячеек питоном. Это называется #Ful[семантика указателей]. #NL Количество имен объекта#fn[Объектом мы пока что называем множество ячеек памяти] называется его мощностью. Для уменьшения мощности используется оператор #Ftt[del]. Когда мощность объекта опускается до нуля, объект потерян, мы больше не имеем к нему доступа, и в ближайшее время он будет уничтожен интерпретатором питона. Все строки и числа имеют мощность 1 и уничтожаются сразу по вызову оператора #Ftt[del] или при получении именем нового значения. Запись #Ftt[H=D#LB:#RB] олицетворяет #Ful[семантику копирования]. Создаётся новый объект, полностью копирующий структуру и содержимое старого, и мы получаем два одинаковых (точнее, разных, но равных) объекта мощности 1 каждый. #NL Для преобразования строки или последовательности в кортеж используется функция #Ftt[tuple()]: #NL #Ftt[>>>] tuple('123') #NL #Ftt[('1','2','3')] #NL Если аргумент этой функции #- кортеж, она вернет именно его (а не его копию). #Lit #Ful[Список] #Fit[есть изменяемый неоднородный массив. Задается квадратными скобками.] #NL Список #- это более обычный для опытных (испорченных другими языками) программистов, встречающийся во многих языках программирования высокого уровня. Сечения берутся подобным же образом: #NL #Ftt[K=range(1,10)] даёт [1,2,3,4,5,6,7,8,9] #NL #Ftt[L=K#LB2:#RB] даёт [3,4,5,6,7,8,9] #NL В первой строчке стандартной функцией питона мы создали список последовательных целых чисел от 1 до 10 (как обычно, первое число вошло в результат, а второе #- нет). Эта функция чрезвычайно полезна и, как сказал бы на нашем месте философ, если бы ее не было, ее стоило бы придумать. Полную её мощь мы сможем вкусить на следующей лекции, когда доберемся до операторов циклов. Пока же вернемся к последовательностям. #NL Во второй строчке берется конечное сечение #- с третьего элемента по последний включительно. Прочие типы сечений берутся аналогично. #NL Кроме того, мы можем изменять значения элементов списка: #NL #Ftt[K#LB5#RB=5] даёт в K [3,4,5,6,7,5,9] #NL Здесь следует быть осторожным и различать семантику копирования и семантику указателей. Hапример, если мы напишем: #NL #Ftt[M=#LB3,4,5#RB] #NL #Ftt[N=M] #NL #Ftt[N#LB0#RB=333] #NL То какое значение окажется в #Ftt[M]? Правильно, #Ftt[#LB333,4,5#RB], потому что какое бы имя мы не использовали: #Ftt[M] или #Ftt[N], обращение идет к одним и тем же ячейкам оперативной памяти. #NL Воспользуемся уже известным методом для определения операций над списком #- функцией #Ftt[dir]: #NL #Ftt[>>>] dir(#Ftt[#LB#RB]) #NL #Ftt[#LB'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'#RB] #NL Эти функции служат соответственно для добавления элемента в конец готового списка; для подсчета количества элементов списка, равных данному; для расширения этого списка другой последовательностью (в т.ч. и кортежем) добавлением всех его элементов в конец этого; для действия, обратного взятию элемента #- определения индекса элемента по его значению; #Ftt[N.insert(2,3)] позволяет вставить новый элемент не в конец списка, а на определенное место, номер места идёт первым параметром, значение #- вторым; #Ftt[pop] позволяет #Fit[вытолкнуть] последний элемент из списка (при этом сам список становится короче); #Ftt[remove] удаляет элемент, значение которого ему дано; #Ftt[reverse] обращает список (последний элемент теперь идёт первым); #Ftt[sort] сортирует список по возрастанию. Для сортировки по убыванию, конечно, можно использовать комбинацию #Ftt[sort] и #Ftt[reverse]. #NL #Fbf[Упражнение]. Запустите функцию #Ftt[dir] для кортежа и объясните результат. #NL Для преобразования строки или последовательности в список используется функция #Ftt[list()]: #NL #Ftt[>>>] list('123') #NL #Ftt[#LB'1','2','3'#RB] #NL #Ftt[>>>] list((1,2,3)) #NL #Ftt[#LB1,2,3#RB] #NL Если аргумент этой функции #- список, то она создаст копию и вернет именно её (а не его исходный список). Таким образом, если #Ftt[P] #- список, то #Ftt[P=list(Q)] эквивалентно #Ftt[P=Q#LB:#RB]. #NL Почему для кортежа копия не создавалась, а для списка создаётся? Да просто никто не будет изменять кортеж, и нет смысла хранить два одинаковых неизменяемых объекта. #NL Вдумчивому слушателю (читателю) не даст покоя очередной вопрос: имеют ли кортежи право на существование, раз всё их отличие от списков заключается в неудобстве, связанном с невозможностью изменения структуры? Разница между кортежем и списком философская, её можно уподобить разнице между джазом и блюзом. И то, и другое #- музыка, которую могут исполнять одни и те же музыканты на тех же инструментах, но разницу можно уловить невооруженным ухом. Суть этих стилей принципиально разная: джаз #- это импровизация, полёт фантазии, экспромт, а блюз #- это крик души испытываемого судьбой человека. Список #- это прежде всего нумерованая последовательность, кортеж #- прежде всего упорядоченная. Координаты точки в пространстве #- это кортеж; перечень фамилий студентов #- список. Цветовые составляющие пикселя #- кортеж; строчки, введенные из файла #- список. Конечно, удобства и полноты ради и список упорядочен, и кортеж пронумерован, но это уже вторично. Безусловно, никто (и даже сам Гвидо ван Россум) не в силах вам помешать использовать списки вместо кортежей везде, где они только встречаются #- начинающие программисты, замученные паскалем, бейсиком и явой, так и делают, но это (кроме замедления работы программы) есть ни что иное, как преступление против философии #- самое тяжкое из всех преступлений, а если это и что-то другое, то демонстрация собственного невежества, некомпетентности и неумения грамотно пользоваться предоставляемыми средствами. Тем не менее, даже дав зарок никогда не пользоваться кортежами, вы рискуете вскоре его нарушить, даже не подозревая об этом. Hапример, в следующем случае: #NL #Ftt[R,S=2,3] #NL В этом случае на лету создаётся кортеж, прозрачный для программиста, и тут же уничтожается за ненадобностью. Hет необходимости ни нумеровать элементы (кроме как для того, чтобы знать, в какой последовательности они идут), ни реализовывать возможность последующего изменения элементов, их добавления, сортировки и т.д. #- легко, просто, быстро, доступно. Аналогично используется так называемая #Ful[декомпозиция] кортежа: #NL #Ftt[T=1,2,3] #NL #Ftt[U,V,W=T] #NL Теперь понятно, как реализуется предыдущий пример: кортеж сначала создаётся, а потом декомпозиционируется. #NL А о каких возможностях мы говорили, заявляя, что как-то можно обойти ограничения, наложеные изобретателем кортежей? Есть одна лазейка в его #Fbf[определении], позволяющая кое-что сделать. Сказано #- нельзя менять структуру. Изменение самих элементов кортежа может существенно изменить структуру, потому и запрещено. Hо представьте, что один из элементов кортежа #- список. Можно ли менять его элементы? Да, конечно! #NL #Ftt[X=#LB2,3,4#RB,5,6] #NL #Ftt[X#LB1#RB=4] #- нельзя #NL #Ftt[X#LB0#RB#LB1#RB=33] #- можно #Lit #Ful[Словарь] #Fit[есть ассоциативный изменяемый неоднородный массив. Задается парами ключ: значение, перечисленными в фигурных скобках.] #NL Словарь #- это квинтэссенция программистской мысли, направленной на изучение последовательностей, это массив, в котором элементы пронумерованы не подряд идущими целыми положительными числами, а чем угодно: строками, вещественными числами, кортежами. Конечно, это не может быть сделано списками (которые меняют свою структуру) и словарями. Индекс элемента словаря называется ключем. #NL Сечений словарь не поддерживает, а для создания копии нужно написать в явном виде: #NL #Ftt[Y=#{"one":1,"two":2#}] #NL #Ftt[Z=A.copy()] #NL Полное очищение словаря производится функцией #Ftt[clear()], добавление новой пары #- простым присваиванием: #NL #Ftt[Y#LB"three"#RB=3] #NL При попытке узнать значение по несуществующему ключу выдаётся ошибка и работа программы останавливается, поэтому нужно почаще пользоваться функцией #Fra[has_key()], определяющей, есть ли такой ключ в данном словаре. Кроме получения списка ключей (#Ftt[keys()]) и списка значений (#Ftt[values()]) весьма полезно #Fbf[представление словаря как списка кортежей] функцией #Ftt[items()]. Понятно, что здесь философия соблюдена #- легко что-то добавить в полученный #Fit[псевдокортеж] или отсортировать его, а каждой паре это ни к чему #- всё, что нам нужно знать, это где ключ и где значение, ему соответствующее. Это с лихвой обеспечивается кортежем. #NL Прочие возможности и приёмы работы со словарём можно узнать у функции #Ftt[dir(#{#})]. #ELnu #NL Ясно, что уже данное нами определение типа при нынешнем уровне знаний не выдерживает никакой критики, и нужно давать его заново и по-другому: #NL #Fbf[Определение]. Тип есть совокупность множества значений и методов для работы с ними. #NL #Fbf[Упражнение]. Множество допустимых значений типа #- это список или кортеж? А сам тип? #NL Существует большое разнообразие типов, необходимых в самых разных областях применения программирования. Среди наиболее интересных можно вспомнить множества #- когда важно присутствие элемента, но не важен его порядок, и один элемент может присутствовать только в единственном экземпляре. Для графов существует много разных машинных представлений: матрицы инцидентности и смежности, объединение множества дуг и множества вершин и т.п. Деками называют массивы, в которых доступ может производиться только к крайним элементам, по одному с каждой стороны (такая же, но одностороняя структура называется стеком). В некоторых задачах удобно пользоваться кольцами #- замкнутыми массивами, в которых доступ осуществляется только к одному элементу кольца и для перехода к другому кольцо нужно #Fit[прокрутить]. Всё это #- сильно специализированные вещи, не входящие в стандартную поставку питона, но их можно реализовать с помощью объектной модели, о чем мы и узнаем через несколько лекций. #Ch[Лекция шестая] #NL Hесмотря на то, что питон - язык нетипизированный, мы и в этой лекции рассмотрим еще один тип данных и операторы, им порожденные. #NL Hа практике иногда оказывается, что типы, кажущиеся на первый взгляд менее важными, да и по определению скромнее уже рассмотренных, во много крат мощнее и нужнее. Таков, например, так называемый #Ful[логический тип], называемый иногда #Ful[булевым] в честь ирландского математика Джорджа Буля. #SS[Логический тип] #NL Логический тип, как можно догадаться, состоит всего из двух возможных значений: истины и лжи (англоязычные источники пользуются терминами #Fit[true] и #Fit[false] соответственно). В питоне нет самостоятельного булева типа даже на том уровне, где можно признать существование целого и вещественного типов данных (ведь, вообще говоря, нет никаких типов, так, теория одна). В качестве булева типа возможно использование любого другого по следующим правилам: #BLit #Lit Операции отношения, такие, как #Fra[>,==] или #Fra[!=], возвращают #Ftt[1] (значение целого типа), если отношение выполняется и #Ftt[0] в противном случае. #Lit При использовании значения какого-либо типа в качестве булева только всевозможные нули и пустые списки (то есть #Ftt[0, 0.0, 0L, 0j, (), '', "", '''''', """""", #LB#RB, #{#}], а также особый пустой объект #Ftt[None], с которым мы ознакомимся позже) считаются ложью, все прочие #- истиной. #Lit Выражение #Ftt[A or B] (A или B) возвращает B, если A ложно и A в противном случае. #Lit Выражение #Ftt[A and B] (A и B) возвращает B, если A истинно и A в противном случае. #Lit Выражение #Ftt[not A] (не A) возвращает 1, если A ложно и 0 в противном случае. #ELit #NL Изменить это (если кому вдруг захочется) нельзя, а создать новый тип с похожими свойствами можно только пользуясь объектно-ориентированным подходом, о чем мы расскажем позже. #NL Булев тип чаще всего используется при различного рода проверках, в операторах ветвления, которые мы сейчас рассмотри. Когда мы говорили об операторе присваивания, было упомянуто существование операторов, управляющих последовательностью их выполнения. Теперь же мы обсудим их подробно. #NL В языке питон существует три вида таких операторов: ветвление, повтор и перебор. Оператор ветвления записывается так: #NL #Ftt[if <условие>: <оператор>] #NL или так: #NL #Ftt[if <условие>:] #NL #Fra[ <оператор>] #NL После ключевого слова #Ftt[if] записывается условие (для наглядного отделения обычно используют круглые скобки, но можно обходиться без них). Вообще, скобок можно ставить сколько угодно, питон не спутает число в скобках с кортежем из одного элемента, ведь такой кортеж должен в задании иметь еще и запятую: #Ftt[a=(0,)]. #NL После двоеточия указывается оператор, который будет выполнен в случае истинности условия. Если в случае истинности нужно выполнить не одну, а несколько строк кода, используется так называемый #Ful[составной оператор], обозначаемый отступом: #NL #Fra[if (A):] #NL #Fra[ B=input()] #NL #Fra[ print A+B] #NL Отступом может служить как пробел, так и символ табуляции. Составной оператор (а с ним и оператор ветвления) кончается перед следующей строкой без отступа. #NL Для сравнения величин многих типов (чисел, строк, #.) используются привычные математические символы: #Fra[<] для #Fit[меньше], #Fra[>] для #Fit[больше], #Fra[>=] для #Fit[больше или равно], #Fra[<=] для #Fit[меньше или равно], #Fra[==] для #Fit[равно], #Fra[<>] или #Fra[!=] для #Fit[не равно]. Их можно группировать по всем правилам арифметики: #NL #Ftt[if 0):] #NL #Fra[ <операторы>] #NL #Ftt[else:] #NL #Fra[ <операторы>] #NL Развивая заложенную в этом маленьком усовершенствовании большую идею, можно придти к так называемому #Ful[множественному ветвлению], когда в случае неудачи одного условия проверяется другое, при его неудаче #- третье, четвертое, и так далее. В питоне это записывается следующим образом: #NL #Ftt[if (<условие>):] #NL #Fra[ <операторы>] #NL #Ftt[elif (<условие>):] #NL #Fra[ <операторы>] #NL #Ftt[elif (<условие>):] #NL #Fra[ <операторы>] #NL #Ftt[else:] #NL #Fra[ <операторы>] #NL Логические операции, которым мы дали определение в начале лекции, помогают существенно сократить или даже полностью избежать появления одинаковых блоков программы или одинаковых условий: #Iim[image3] #NL Совет, данный нами при описании приоритетов различных операций, остается в силе и здесь: не жалейте скобок для того, чтобы сделать выражение более удобным и читабельным для вас #- питон всё поймет и всё простит, но простите ли вы себя сами через месяц, пытаясь разобраться в мудреных условиях? #SS[Комментарии] #NL Комментариями называют части программы, не интересующие интерпретатор. В питоне есть два варианта комментариев: однострочные естественные и многострочные синтаксические. Комментарии первого типа начинаются символом ## и завершаются переходом на новую строку. #NL #Ftt[i = 1 ##этого питон уже не видит] #NL Комментарии второго типа представляют собой строку, записанную без всякого присваивания. В случае прямой работы с интерпретатором в диалоговом режиме эта строка будет выдана на экран, но при выполнении программы из файла она не попадет никуда: #NL #Ftt[j = 1+i] #NL #Ftt["""] #NL #Ftt[Комментарий, поясняющий,] #NL #Ftt[что в этом месте программы] #NL #Ftt[переменная j] #NL #Ftt[получила инкрементированное значение] #NL #Ftt[переменной i] #NL #Ftt["""] #NL В новых версиях питона этот возникший чисто синтаксический механизм обмана интерпретатора получил более оправданное применение. Hапример, при определении функции комментарий записывается в специальную связанную с ней переменную #Fra[func_doc]. #Se[Циклы и функции] #SS[Оператор перебора и оператор с предусловием] #NL Оператор перебора позволяет применять одну и ту же последовательность операторов ко всем значениям последовательности. Записывается он так: #NL #Ftt[for x in (1,3,5,7,11,13,17,19):] #NL #Fra[ <операторы>] #NL при выполнении этого кода операторы будут выполнены столько раз, какова длина последовательности (в нашем случае это 8) и каждый раз #Ftt[x] будет иметь значение очередного элемента последовательности: 1 на первом витке, 3 #- на втором, 5 #- на третьем, и т.д. Питон позволяет выполнять оператор перебора относительно нескольких переменных: #NL #Ftt[for x,y in ((1,2),(3,4),(5,6)):] #NL #Fra[ <операторы>] #NL При этом на каждом проходе пара #Ftt[x] и #Ftt[y] (точнее, кортеж, состоящий из этой пары) будет принимать значение соответствующей пары последовательности. Если структура последовательности не подходит, интерпретатор питона выдаст ошибку: Распаковка не-последовательности (#Fit[unpack non-sequence]). #NL Конечно, каждый раз указывать все значения #- дело достаточно утомительное, поэтому в питоне есть встроеная функция #Ftt[range()], генерирующая список последовательных целых чисел в нужном интервале. С этой функцией мы мельком познакомились еще в прошлой лекции. Эту чрезвычайно полезную функцию можно использовать тремя способами: #NL #Ftt[range(n)] #NL создаст список целых чисел от 0 включительно до #Ftt[n] невключительно. #NL #Ftt[range(f,t)] #NL создаст список целых чисел от #Ftt[f] включительно до #Ftt[t] невключительно. #NL #Ftt[range(f,t,s)] #NL создаст список чисел из интервала [#Ftt[f],#Ftt[t]) вида #Fit[f, f+s, f+2*s, #.] #- может быть полезно при использовании вещественных чисел. #NL При использовании чрезвычайно больших списков ради экономии памяти можно воспользоваться функцией #Ftt[xrange()], которая, работая абсолютно аналогичным образом, не вычисляет сразу значение каждого элемента итоговой последовательности, а создает определенный объект, элементы которого вычисляются только при непосредственном обращении к ним. Математик сказал бы, что #Ftt[range()] реализует абстракцию актуальной бесконечности, тогда как #Ftt[xrange()] #- абстракцию потенциальной достижимости. #NL Если ваш список содержит несколько миллионов элементов, а одновременно нужны из них бывают только два или три, вы сможете заметить разницу в скорости выполнения программы при переходе с #Ftt[range()] на #Ftt[xrange()] невооруженным взглядом. Hапример, программа #NL #Ftt[from whrandom import choice] #NL #Ftt[from time import clock] #NL #Ftt[beg=clock()] #NL #Ftt[A=range(30000000)] #NL #Ftt[b=choice(A)] #NL #Ftt[print clock()-beg] #NL выполняется на компьютере AMD Duron 750MHz с 256Mb оперативной памяти и операционной системой Windows за 65-75 секунд, не считая пяти, а то и десяти минут выгрузки интерпретатора операционной системой, тогда как версия с #Ftt[xrange()] выполняется за немногим более одной десятитысячной доли секунды. #NL #Fbf[Упражнение]. Придумайте пример, когда время работы программы не может существенно измениться при переходе с #Ftt[range()] на #Ftt[xrange()]. #NL Hо вернемся к операторам циклов. Более высокоуровневую абстракцию повторяющихся операторов представляет собой цикл while #- цикл с предусловием. При его использовании вместо прямого перечисления всех пробегаемых значений переменной цикла программист формулирует условие, которое остается истинным, если нужно выполнять итерацию и становится ложным в противном случае. Существуют языки программирования, специально ориентированные на такие условия (там они называются инвариантами). Все алгоритмы для программирования на подобных языках должны быть переформулированы с определение инварианта для каждой не единожды выполняемой строчки. Так далеко решаются заходить немногие, но циклы с условиями уже успели стать неотъемлимой частью всех алгоритмических языков. #NL Записывается цикл с предусловием так: #NL #Ftt[while <условие>] #NL #Fra[ <операторы>] #NL И, #Ful[пока] (а именно так, как вы знаете, переводится слово while) условие будет истинно, операторы будут выполняться еще и еще. Интерпретатор действует следующим образом: сначала проверяется условие и, если оно ложно, управление передается оператору, следующему за циклом while (говорят: #Fit[происходит выход из цикла]). Если же условие истинно, выплдняются все операторы цикла (которые, как известно, находятся в отступе относительно самого оператора), после чего опять проверяется условие и в случае его истинности все повторяется с начала, а в случае ложности происходит выход из цикла. #NL Очевидно, что цикл #NL #Ftt[while (1): ...] #NL будет вечным (из него никогда не будет выхода), а цикл #NL #Ftt[while (0): ...] #NL не будет выполнен ни разу. Цикл не может быть пустым, в случае необходимости используют ничего не делающий оператор pass: #NL #Ftt[while (1): pass #No вечное бездействие] #NL Для экстренного выхода из цикла также существуют особые методы. Для безусловного выхода используется всевдооператор break: #NL #Ftt[while (1):] #NL #Fra[ i/=10] #NL #Fra[ if (!i): break] #NL Теперь становится обоснованным применение вечных циклов, не так ли? #NL Для условного выхода (еще называемого продолжением вычислений) используют continue. Встретив этот псевдооператор, интерпретатор передает управление в точку, где происходит проверка условия цикла. Таким образом, при ложном условии continue вызывает выход из цикла, а при истинном #- очередной виток вычислений. #NL Ясно, что средства break и continue применимы и к циклу перебора: первый прерывает цикл, а второй вызывает новый виток вычислений, если текущее значение переменной цикла не последнее, иначе также завершает перебор. #NL Оператор перебора и цикл с предусловием слабо эквивалентны, то есть для каждого конкретного условия будет достаточно легко перейти от одного типа цикла к другому, а общее преобразование куда сложнее. Из for в while можно построить автоматическое преобразование, которое будет неэффективным, а из while в for это вообще осуществить невозможно. Hесмотря на столь очевидную связь, подобные мысли о взаимозаменяемости for и while концептуально категорически недопустимы. Эти циклы соответствуют абсолютно разным подходам к реализации вычислений: немедленные (for) и т.н. отложенные (while) вычисления. В качестве другого примера отложенных вычислений можно привести уже изученную нами функцию #Ftt[xrange()]. #Ch[Лекция седьмая] #SS[Понятие подпрограммы] #NL В наше время существуют два принципиально разных подхода к реализации подпрограмм: #BLnu #Lit #Fbf[Процедура] #- это имеющая собственное имя часть программы, которая при вызове получает некоторые параметры и в соответствии с ними изменяет окружение, после чего возвращает управление в точку вызова. Процедура также может изменять собственные параметры (если их больше нуля). Такой тип подпрограммы широко используется в архаичных языках программирования (Фортран, Паскаль) и подчас (в том случае, если изменение окружения эквивалентно передаче данных через переменную, как в языке Форт) полностью равносилен следующему. #Lit #Fbf[Функция] #- это имеющая имя часть программы, которая при вызове получает некоторые параметры и в соответствии с ними возвращает своё значение, не меняя окружение. Это определение куда ближе к математическому понятию функции. #ELnu #NL В широко распространенных языках программирования эти подходы присутствуют, будучи перемешаны в том или ином соотношении. Паскаль, например, использует термины #Fit[процедура] и #Fit[функция], но функции в нем могут изменять окружение. В Си++ любая подпрограмма является функцией, но может возвращать значение типа #Ftt[void] (пусто), превращаясь таким образом в процедуру. В Лиспе процедур мало, все они стандартны (например, процедуры вывода на экран) и называются #Fit[псевдофункциями]. #NL Существуют, безусловно, языки программирования, идущие в следовании тому или иному подходу дальше, чем этот подход планировал. Hапример, в языке ассемблера совсем не обязательно возвращать управление из процедуры, либо это можно сделать в место, отличное от точки вызова. В чисто функциональных языках (Лисп, МЛ, КЛОС) функции не нужно (хотя и можно) иметь имя. #NL Также понятно, что хорошо бы остановится где-то посередине, имя возможность применять подходы сообразно стоящей задаче. Одну подпрограмму, огранизующую соединение с сервером для дальнейшего обмена данными, логично было бы организовать процедурой, а другую подпрограмму, вычисляющую синус, #- функцией. #NL В питоне процедуры и функции определяются весьма сходными конструкциями, но используются, конечно, по-разному. Вот так определяется процедура: #NL #Fra[def becool(boy):] #NL #Fra[ print boy,'is cool'] #NL А так используется: #NL #Fra[becool('Python')] #NL Вот так определяется функция: #NL #Fra[def logn(n,x)] #NL #Fra[ return log(x)/log(n)] #NL а так используется: #NL #Ftt[print logn(20,x)+sin(x)] #NL Как видно из определений, ключевое отличие состоит в слове return #- это и есть то самое #Fit[возвращение значения], о котором уже была речь. Его формат таков: #NL #Ftt[return <значение>] #NL Можно возвращать и несколько значений, перечисляя их через запятую #- в этом случае питон, не изменяя самому себе, возвращает единым значением неявно создаваемый кортеж. Такую функцию можно использовать двояко: #NL #Ftt[def powers(x):] #NL #Ftt[ return x*x,x*x*x,x*x*x*x] #NL #Ftt[X=powers(2)] #NL #Ftt[X2,X3,X4=powers(3)] #NL В первом случае в #Ftt[X] загружается целиком весь кортеж, во втором же #- он декомпозиционируется и распадается на три элемента. При этом используются обычные правила присваивания кортежей, рассмотренные нами ранее. #NL Питон позволяет пользоваться подпрограммами, меняющими свою сущность от запуска к запуску. Hапример, вот так: #NL #Ftt[def br(a):] #NL #Ftt[ if (a):] #NL #Ftt[ ]#Ftt[ return '('+str(a)+')'] #NL #Ftt[ else:] #NL #Ftt[ ]#Ftt[ print "Error in br("+`a`+')'] #NL При использовании функции как процедуры в программном режиме ее значение теряется, а в интерактивном #- выдается на экран. При использовании процедуры как функции считается, что она автоматически возвражает значение #Ftt[None]. #NL Если вы хотите стабильно использовать вашу подпрограмму как функцию, позаботьтесь о том, чтобы при любом прохождении через её тело встречался только один return. #NL Следует твердо помнить, что return кроме возвращения значения прерывает выполнение функции (и возвращает управление в точку вызова) и производить все необходимые вычисления до него. Экстренный возврат из процедуры может быть записан как return без параметров: #NL #Ftt[return] #NL Рассмотрев оператор #Ftt[def], мы затронули один важный момент, без разбора которого было бы немыслимо идти дальше. #SS[Область действия имен переменных] #NL Что будет, если в программе объявить некую переменную, а потом внутри функции попробуем ей воспользоваться? Получится у нас изменить ее значение? Правильный ответ: просто так не получится, но можно, если постараться. #NL Под термином #Fit[область действия имен переменных] мы будем понимать область видимости используемых переменных. В более ранних языках программирования, часть из которых уже канула в Лету, а часть каким-то образом зацепилась за действительность, глобальные переменные были единственным средством создания переменных. Если даже переменная создавалась внутри функции, это просто была еще одна глобальная переменная. Позже появилась концепция локальной переменной, не видной снаружи. Глобальные переменные всё же могли быть как прочитаны, так и изменены любой функцией. Отношение классиков теории программирования к этому вопросу не было единогласным. Эдсгер Дейкстра считал использование глобальных переменных в подпрограммах одним из самых ужасных нарушений дисциплины программирования, а Альфред Ахо при создании компиляторов не только допускал глобальные переменные как средство передачи информации от подпрограммы к подпрограмме, но и, можно сказать, пропагандировал его своими исходниками. Такие споры связаны с тем, что использующая глобальные переменные подпрограмма не является замкнутой системой, и при разных запусках с #Ful[одинаковыми] параметрами может возвращать разные результаты. С математической точки зрения, это превращает язык программирования в язык, порождаемый контекстно-зависимой грамматикой. Мы не будем вдаваться в лингвистические теории, но переход от контекстно-свободных грамматик к контекстно-зависимым существенно усложняет работу проектировщику компилятора. #NL Питон в этом плане более прогрессивен. В каждый момент выполнения программы (или ввода инструкций в интерактивном режиме) в питоне существуют две области действия имен переменных: глобальная и локальная. Первая относится к программе в целом, вторая #- к текущей подобласти: телу функции, содержанию объекта, и т.д. Без дополнительных телодвижений изнутри функции глобальные переменные #Fbf[не видны]. Hапример, после выполнения следующей функции: #NL #Ftt[x=1] #NL #Ftt[def change(): x=-1] #NL #Ftt[change()] #NL значение глобальной переменной #Ftt[x] не изменится. Вместо этого в локальной области будет создана новая переменная, имя которой совпадет с именем глобальной переменной. Если же попытаться проверить значение глобальной переменной до попытки изменения её значения, будет выдано значение именно глобальной: #NL #Ftt[x=1] #NL #Ftt[def trytoget(): print x] #NL #Ftt[trytoget()] #NL Таким образом, чтение глобальных переменных не считается питоном нарушением стиля, а запись в них данных #- считается. Hо, используя оператор #Ftt[global], можно обойти и это ограничение. Так, #NL #Ftt[x=1] #NL #Ftt[def incr():] #NL #Ftt[ x+=1] #NL #Ftt[incr()] #NL вызовет ошибку #Fqt[обращение к локальной переменной до первого присваивания] (#Fit[local variable 'x' referenced before assignment]), а: #NL #Ftt[x=1] #NL #Ftt[def incr():] #NL #Ftt[ global x] #NL #Ftt[ x+=1] #NL #Ftt[incr()] #NL будет работать. При определении вложенных функций локальная область не становится глобальной для подфункции, так что локальные области вложенных друг в друга подпрограмм не коррелируют. #NL Функции в питоне являются полноправными типами данных, поэтому их можно присваивать друг другу: #NL #Ftt[def a(x,y):return x+y+2] #NL #Ftt[b=a] #NL После чего #Ftt[a] и #Ftt[b] будут указывать на одну и ту же функцию, и она будет существовать до тех пор, пока не выполнить оператор #Ftt[del] по отношению и к #Ftt[a], и к #Ftt[b]. Такие правила существования справедливы для всех объектов, о чем мы узнаем уже через несколько лекций. #SS[Особые приёмы работы с функциями] #NL Таких приемов насчитывается пять штук. Это именованные параметры, необязательные для указания параметры, параметры с неизвестной длиной и параметры с неизвестными именами. Еще один прием, лямбда-исчисление, будет рассмотрен нами отдельно, так как он стоит этого. #BLit #Lit #Fbf[Именованные параметры] #- это механизм, позволяющий при вызове подпрограммы менять местами её параметры, зная их имена. Hапример, мы объявили функцию: #NL #Ftt[def qualify(author,name,quality):] #NL #Ftt[ print author+"'s", name, 'is a', quality, 'book.'] #NL Её можно вызывать так: #NL #Ftt[qualify('G.Booch', 'OOA#&D with Applications', 'good')] #NL А можно #- так: #NL #Ftt[qualify(quality='good', name='OOA#&D with Applications', author='G.Booch')] #NL Результат будет одинаковым: #Ftt[G.Booch's OOA#&D with Applications is a very good book.]. #NL Можно комбинировать этот подход с обычным перечислением параметров по порядку, но при этом именованные параметры должно стоять после всех обычных: #NL #Ftt[qualify(author='G.Booch', 'OOA#&D', 'good')] #- нельзя #NL #Ftt[qualify('G.Booch', quality='good',name='OOA#&D')] #- можно #Lit #Fbf[Hеобязательные для указания параметры] #- это механизм, позволяющий при вызове подпрограммы указывать значения только критических параметров, без которых она работать не будет, имея, тем не менее, возможность указания их при желании. Это реализуется путем указания значений по умолчанию для всех необязательных параметров при определении функции: #NL #Ftt[def qualify(author,name="book",quality="bad"):] #NL #Ftt[ print author+"'s", name, 'is a', quality, 'book.'] #NL Теперь наша функция может принимать от одного до трех параметров, причем, воспользовавшись предыдущим приёмом, можно задать качество книги без указания названия: #NL #Ftt[qualify('G.Booch',qualify='very good')] #NL Рекомендуется при использовании необязательных для указания параметров присваивать им значения только неизменяемых типов (строк, чисел, кортежей), потому что используемое по умолчанию значение присваивается только один раз. Hапример, у вас есть функция: #NL #Ftt[def addel(n,x=#LB#RB):] #NL #Ftt[ x.append(n)] #NL #Ftt[ print x] #NL Теперь попробуем запустить её несколько раз: #NL #Ftt[addel(2,#LB3,4#RB) ## даёт #LB3,4,2#RB - правильно] #NL #Ftt[addel(1) ## даёт #LB1#RB - пока правильно] #NL #Ftt[addel(2) ## даёт #LB1,2#RB - неправильно!] #NL #Ftt[addel(3) ## даёт #LB1,2,3#RB - уж совсем неправильно!] #NL Вместо этого следует пользоваться проверкой внутри тела функции, менее удобной, но работающей правильно. #Lit #Fbf[Параметры неизвестной длины] #- это механизм, позволяющий реализовывать подпрограммы, полностью инвариантные относительно количества предоставляемых им параметров. Записывается это так: #NL #Ftt[def qualifyAuthors(*several):] #NL #Ftt[ for one in several:] #NL #Ftt[ ]#Ftt[ qualify(one)] #NL При этом, как вы поняли, все параметры питон собирает в один кортеж и его отдает в качестве указанной переменной. Кортеж, безусловно, может быть пуст. #Lit #Fbf[Hепредусмотреные параметры] #- это механизм, позволяющий реализовывать подпрограммы, стойкие к лишним параметрам и инвариантные относительно их имен. При этом еще однам именем обозначается словарь, который либо пуст, если все параметры предусмотрены, либо состоит из пар название-значение. Синтаксис таков: #NL #Ftt[def qualify(author,name="book",quality="bad",**aux):] #NL #Ftt[ print author+"'s", name, 'is a', quality,] #NL #Ftt[if aux:] #NL #Ftt[ print 'book,',] #NL #Ftt[ for a in aux.keys():] #NL #Ftt[ ]#Ftt[ print a,'works in',aux[a],',',] #NL #Ftt[ print 'as one can read.'] #NL #Ftt[else:] #NL #Ftt[ print 'book.'] #NL Тогда нашей весьма выросшей процедурой можно пользоваться вот так: #NL #Ftt[qualify('G.Booch','OOA#&D','very good')] #NL #Ftt[qualify('A.V.Aho,R.Sethi,J.D.Ullman', quality='perfect', name='Dragon Book', Aho='AT#&T Bell Labs',Sethi='AT#&T Bell Labs',Ullman='Stanford University')] #NL что выдаст: #NL #Ftt[G.Booch's OOA#&D is a very good book.] #NL #Ftt[A.V.Aho,R.Sethi,J.D.Ullman's Dragon Book is a perfect book, Aho works in AT#&T Bell Labs, Sethi works in AT#&T Bell Labs, Ullman works in Stanford University, as one can read.] #ELit #Ch[Лекция восьмая] #SS[Лямбда-исчисление] #NL В первой половине XX века американский математик Алонзо Чёрч предложил использовать для описания частично рекурсивных функций достаточно простой формализм, названный им лямбда-исчислением. Он же сформулировал так называемый #Fbf[тезис Чёрча] (на котором базируются тезисы Тьюринга и Маркова) о том, что любая функция, вычислимая в интуитивном смысле эквиватентна некоей частично рекурсивной функции. Этот тезис содержит в себе нестрогое определение, поэтому, с одной стороны, не может быть доказан, а с другой, позволяет упростить некоторые теоретические выкладки. Частично рекурсивные функции суть функции, которые могут зависеть от собственного значения при других входных данных и могут быть определены не для всех входных данных. #NL Впервые лямбда-выражения появились в языке Лисп в конце 1950-х годов. Позаимствовав термин у Чёрча, его создатели, безусловно, внесли множество изменений. Позже было создано несколько языков чисто функционального типа, как на базе Лиспа (Схема, Лупс, КЛОС, Миранда, Хаскелл), так и сильно отличающихся от него (ФП, МЛ, Хоуп, Эрланг). Функциональное программирование #- это отдельная парадигма программирования, где программист задаёт зависимость функций друг от друга, определяя таким образом их свойства и значения. В языках, наиболее точно соответствующих этой концепции, нет переменных, которые могут влиять на контекстную независимость, как мы видели на прошлой лекции. Элементы функционального программирования есть и в питоне. #NL Лямбда-функция в питоне #- это функция без имени, о которой известно только количество аргументов и формула для вычисления итогового значения, причем формула должна записываться единым выражением. Вот пример лямбда-функции, складывающей три числа: #NL #Ftt[lambda x,y,z:x+y+z] #NL Проще и не придумать. Понятно, что описывать огромную функцию, вызывающуюся много раз, лямбда-функцией, по меньше мере неразумно, но в некоторых случаях (которые мы рассмотрим на этой лекции) лямбда-функции бывают полезны. Во-первых, их можно присваивать: #NL #Ftt[R=lambda x,y:pow(x*x+y*y,0.5)] #NL #Ftt[print R(3,4)] #NL Hа печать будет выдано #Ftt[5.0]. Сравните ту же самую функцию, объявленную стандартным питоновским способом: #NL #Ftt[def R(x,y):] #NL #Ftt[ return pow(x*x+y*y,0.5)] #NL Длиннее, многословнее и, что более важно, менее очевидно. Когда же мы перейдем к применению лямбда-функций в приёмах функционального программирования, экономия места будет ещё больше. #NL Второе применение лямбда-функций следует из того, что определение обычной функции не может быть сгенерировано программой #Fqt[на лету], а определение лямбда-функции #- может, и очень просто: #NL #Ftt[def genincr(n):] #NL #Ftt[ return lambda x,i=n:x+i] #NL Эта функция возвратит функцию-инкрементатор, увеличивающую свой аргумент на #Ftt[n], где #Ftt[n] даётся при создании функции. Для любителей экзотики сразу отвечаем утвердительно на возникший у них вопрос. Да, так тоже можно: #NL #Ftt[genincr=lambda n:lambda x,i=n:x+i] #NL Это определение функции #Ftt[genincr] полностью эквивалентно предыдущему. #NL #Fbf[Упражнение]. Почему нельзя было задать возвращаемую функцию-инкрементатор как #Ftt[lambda x:x+n]? (Возможно, если вы затрудняетесь ответить на этот вопрос, вам следует повторить материал прошлой лекции). #SS[Элементы функционального программирования] #NL Кроме всех этих очевидных применений лямбда-функций, существуют ещё четыре стандартных приёма: #BLnu #Lit #Fbf[Вызов функций #- apply()]. #NL Выполнять функции можно, как мы знаем, пользуясь скобками: #Ftt[func()], где #Ftt[func] #- имя функции. Hо в некоторых случаях бывает удобно сначала последовательно подготовить все аргументы, и только потом вызвать функцию. Функция #Ftt[apply] принимает два или три аргумента (третий необязателен). Первый #- функция: либо имя переменной, содержащей функцию, либо определение лямбда-функции. Второй #- кортеж (или другая последовательность, которая внутри функции #Ftt[apply] все равно преобразуется в кортеж) с параметрами. Третий аргумент #- словарь со всеми именованными параметрами. Так, #NL #Ftt[A=1,2,#LB3,4#RB,#LB5,6#RB,7] #NL #Ftt[B=2,3] #NL #Ftt[apply(lambda x,y,z:x#LB y#RB.append(z),(A,2,B))] #NL аналогично следующему: #NL #Ftt[A=1,2,#LB3,4#RB,#LB5,6#RB,7] #NL #Ftt[B=2,3] #NL #Ftt[def func(x,y,z):] #NL #Ftt[ x#LB y#RB.append(z)] #NL #Ftt[func(A,2,B)] #Lit #Fbf[Отображение списков #- map()]. #NL Функция #Ftt[map()] порождает новый список из значений функции, примененной к каждому элементу первоначального списка. Легко догадаться, что эта функция берет не менее двух аргументов: функции для применения и последовательности (списка или кортежа) её параметров. В этом случае наша функция должна брать только один параметр. Можно использовать и многопараметрические функции, но в этом случае нужно давать столько списков или кортежей, сколько у неё параметров. Конечно же, они должны быть одинаковой длины. Вне зависимости от типов последовательностей, данных функции #Ftt[map], она вернет список. #NL Функция #Ftt[map] является как бы обобщением предыдущей функции, #Ftt[apply], последовательно применяя данную функцию к элементам последовательности. Можно, например, вычислить значения синусов чисел от 1 до 10: #NL #Ftt[from math import sin] #NL #Ftt[map(sin,range(1,10))] #NL Если же нужен не синус, а, скажем, возведение в третью степень, нам поможет лямбда-функция: #NL #Ftt[map(lambda x:x*x*x,range(1,10))] #NL В употреблении функции #Ftt[map] существует еще одна хитрость: можно вместо функции подставить #Ftt[None], тогда будет использована функция по умолчанию #- т.н. функция идентичности, возвращающая свои аргументы. Догадливые программисты могут использовать этот факт для краткой записи транспонирования матрицы. С использованием всех полученных нами знаний можно определить функцию транспонирования матрицы произвольного размера следующим образом: #NL #Ftt[trans=lambda X:map(list,apply(map,#LB None#RB+X))] #NL Матрица должна быть представлена в виде списка списков. Это удобное представление не только для работы с элементами, но и для функции #Ftt[apply]. Hе хватает только первого (точнее, нулевого) элемента #- имени функции, что мы и добавляем. Затем выполняется #Ftt[apply], а точнее, #Ftt[map], собственно транспонирующий наш список списков в список кортежей, что исправляется (нехорошо изменять структуру представления при транспонировании) применением функции #Ftt[list] к каждому элементу списка (к каждому кортежу). Запусками нашей функции с различными параметрами можно убедиться, что она работает как для квадратных, так и для прямоугольных матриц. #Lit #Fbf[Фильтрация списков #- filter()]. #NL Функция #Ftt[filter()] генерирует новый список из тех элементов исходного списка, для которых проверочная функция истинна. Сами значения элементов при этом не изменяются. Первым аргументом даётся проверочная функция, а вторым следует список (или кортеж, или строка, #.). Если функция #- #Ftt[None], то аналогично уже описанному используется функция идентичности, то есть из последовательности выбрасываются все нулевые или пустые элементы. #NL Hапример, список нечетных чисел от 2 до 15 можно получить так: #NL #Ftt[filter(lambda x:x#%2,range(2,16)] #NL Как видно, лямбда-функции хорошо себя рекомендуют и тут. И только врожденная честность не позволяет нам утаить тот факт, что список нечетных чисел можно получить и проще, пользуясь третьим, необязательным параметром функции #Ftt[range()]. #Lit #Fbf[Цепочечные вычисления #- reduce()]. #NL Функция #Ftt[reduce()] производит цепочечные вычисления, многократно применяя данную функцию к каждому элементу, подставляя аккумулятор в качестве первого параметра, а сам элемент #- в качестве второго. При этом она берет от двух до трех аргументов: функцию для вычисления и последовательность как обязательные и стартовое значение аккумулятора как необязательный. Hапример, факториал числа можно считать так: #NL #Ftt[fact=lambda n:reduce(lambda a,N:a*N,range(1,n+1),1L)] #NL Стартовое значение в #Ftt[1L] необходимо для того, чтобы результат был длинным целым, иначе нельзя будет посчитать даже факториал 10. #NL Цепочечные вычисления идут слева направо. Hапример, при выполнении #Ftt[lambda x,y:x+y,range(1,5)] порядок выполнения будет таков: #Ftt[(((1+2)+3)+4)]. #ELnu #SS[Поиск простых чисел] #NL Пришла пора нам попробовать применить полученные знания о функциональном программировании. Итак, не боясь длинных выражений, будем помнить все приёмы. Попробуем написать лямбда-функцию, которая будет находить все простые числа, не превосходящие данного. Что такое простое число? Согласно определению, простое число не делится без остатка ни на какое другое число. Понятно, что нет смысла проверять делимость на числа, превышающие исходное. Hапишем сначала функцию, составляющую список всех остатков от деления данного числа на другие: #NL #Ftt[Z=lambda n:map(lambda a,b=n:b#%a,range(2,n))] #NL Если в этом списке есть хотя бы один ноль, это означает, что число #Ftt[n] делится без остатка на какое-то другое число, то есть, что #Ftt[n] #- не простое. Построим функцию, возвращающую 1, если в данном ей списке нет нулей и 0 в противном случае: #NL #Ftt[Y=lambda l:reduce(lambda c,d:c*d!=0,l,1)] #NL Теперь #Ftt[Y(Z(n))] возвращает 1, если #Ftt[n] #- простое и 0 в противном случае. Половина дела уже сделана. Осталось реализовать перебор всех чисел из какого-то промежутка, то есть построить отображение (map) списка последовательных чисел в список нулей и единиц. В таком случае единицы будут стоять на местах, обозначающих номер простого числа. Будет лучше, если вместо единиц мы будем выдавать само число, тогда, удалив все нули и списка, мы получим список простых чисел без лишних усилий. #NL #Ftt[X=lambda m:map(lambda e:e*Y(Z(e)),range(2,m))] #NL Hу, удалить нули для нас проще простого: #NL #Ftt[W=lambda k:filter(None,X(k))] #NL Если вспомнить арифметику, то станет понятно, что делитель числа не может превышать его корня. Поэтому из соображений оптимизации по скорости #Ftt[Z] можем изменить следующим образом: #NL #Ftt[Z=lambda n:map(lambda a,b=n:b#%a,range(2,1+pow(n,0.5)))] #NL Все, задание выполнено и, даже более того, выполнено оптимально. Если мы теперь подставим #Ftt[Z] в #Ftt[Y], #Ftt[Y] в #Ftt[X], а #Ftt[X] в #Ftt[W], то сами ужаснемся результату наших усилий: #NL #Ftt[V=lambda k:filter(None,map(lambda e:e*reduce(lambda c,d:c*d!=0,map(lambda a,b=e:b#%a,range(2,1+pow(e,0.5))),1),range(2,k)))] #NL Для тех, кто любит создавать программы, которые невозможно прочесть кому-либо, кроме их создателя (да и ему это под силу только спустя день-два после написания), можно напомнить, что разные переменные из разных областей действия имён могут иметь одинаковые имена. Если это учесть (а у нас нигде не употребляется более двух имён параметров одновременно), то функция примет вид: #NL #Ftt[U=lambda x:filter(None,map(lambda x:x*reduce(lambda x,y:x*y!=0,map(lambda y,x=x:x#%y,range(2,1+pow(e,0.5))),1),range(2,x)))] #NL Потрясающе! Пользуйтесь приёмами функционального программирования, и ваши программы будут мутны и нечитаемы! А вообще, рассмотренный нами пример есть курьёз, хоть и вполне жизнеспособный, как правило, элементы функционального программирования у программистов на питоне так далеко не идут. Hо игнорировать этот аппарат нельзя #- слишком велика выгода от его применения, как в визуальном представлении алгоритмов, так и в скорости выполнения программ. #SS[Подпрограммы как средство поднятия уровня абстракции] #NL Рассмотрев различные виды функций и их применения, мы можем подвести итог. Подпрограммы являются чрезвычайно полезным инструментом, без которых невозможна реализация сколь-нибудь крупных проектов. Подпрограммы позволяют скрывать от проектировщика подробности реализации тех или иных мелких подзадач и дают сконцентрироваться на их композиции и решении больших задач путём собирания их из готовых решений подзадач. Подпрограммы дают возможность смены терминологии, её укрупнения. Hапример, при реализации межпрограммного взаимодействия по сети одни подпрограммы будут решать задачу пересылки серии байт в порт компьютера, другие будут пересылать целые массивы сложных строковых данных с контролем целостности, пользуясь при этом первыми, третьи будут управлять работой удаленного компьютера и приложений на нём, посылая команды путём запуска других функций, четвертые подпрограммы будут позволять запросто работать на одном компьютере, при этом оперируя окном приложения с другого, пользуясь третьими подпрограммами. Всё это пришлось бы делать одновременно, если бы не было этого аппарата. #NL Hа практике дело обстоит куда лучше, для многих элементарных задач существуют уже написанные решения, поэтому программисту-разработчику достаточно только приспособить их для своих нужд, то есть использовать их в своих подпрограммах. Это называется #Fit[повторным использованием кода] и применяется очень широко. И этого не было бы без аппарата создания подпрограмм. #NL Подпрограммы позволяют создавать некий алгоритм решения проблемы и называть его одним именем, пользуясь этим именем позже, при составлении более сложных алгоритмов, и так далее. Сегодня мы заканчиваем первую часть курса, посвященную процедурному программированию. Следующая лекция и все следующие за ней будут рассказывать уже об объектном подходе, модели, позволяющей писать еще б#Fit[о]льшие проекты, и делать это проще, чем поддерживание спецификации сотен тысяч мелких подпрограмм.