Текст
                    БУКВАРЬ
ПРОГРАММИСТА
на русском и английском языках
Python
ОСНОВЫ,ООП,
WINDOWS ПРИЛОЖЕНИЯ
АНДРЕЙ САЖЕНЮК

Contents Слово к читателю........................................................... 7 Часть 1. Основы..............................................................9 Урок 1. Введение...........................................................9 Загрузка софта...........................................................9 Ваша первая программа на Питоне.........................................10 Работа над ошибками.....................................................10 IDE — FAQ (Frequently Asked Questions, часто задаваемые вопросы,).......12 Урок 2. Операторы.........................................................12 Калькулятор.............................................................13 Урок 3. Переменные, литералы, типы и оператор присваивания .............. 15 Переменные и литералы — демо............................................17 Кастинг.................................................................18 Урок 4 Операции...........................................................19 Арифметические операции ................................................19 Операции сравнения......................................................21 Логические операции.....................................................24 Конкатенация (слияние)..................................................25 Урок 5. Выражения.........................................................26 Урок 6. Ввод/вывод........................................................28 Вывод...................................................................28 Ввод....................................................................29 Урок 7. Оператор i f......................................................29 Одноблочный if..........................................................30 Двухблочный if..........................................................31 0
Многоблочный if.........................................................32 Урок 8. Циклы.............................................................33 Цикл for................................................................34 Цикл while..............................................................35 Преждевременный выход из цикла..........................................36 Где do-while однако?....................................................38 Урок 9. Исключения........................................................38 Урок 10. Функции ......................................................... 44 Основные термины........................................................44 Параметры и аргументы...................................................46 Преждевременный выход...................................................4 7 Возвращаемые значения....................... . .. ......... 48 Локальные и глобальные переменные.......................................49 Модули..................................................................50 Пользовательские функции, библиотечные функции и обработчики событий....50 Урок 11. Контейнеры данных................................................51 Списки (англ, lists).................................................... 52 Словари (англ, dictionaries)............................................53 Кортежи (англ, tuples)..................................................54 Операция звездочка......................................................54 Урок 12. Контейнеры и циклы...............................................55 Итераторы...............................................................57 Урок 13. Строки...........................................................58 Урок 14 Двумерные списки.................................................. 60 Урок 15. Файлы и потоки...................................................62 1
Внешняя память............................................................62 Чтение....................................................................65 Чтение/Запись.............................................................68 Часть 2. Обьектно-ориентированное программирование (ООП)......................70 Урок 1. Введение............................................................70 Основные термины..........................................................70 Первый раз в первый класс!................................................71 Урок 2. Разработка методов..................................................74 Урок 3. Статические члены класса............................................75 Статические переменные....................................................75 Статические методы........................................................76 Урок 4. Сокрытие данных (инкапсуляция)......................................78 Геттеры и сеттеры.........................................................79 Свойства..................................................................80 Урок 5. Композиция..........................................................81 Урок 6. Наследование........................................................84 Урок 7. Полиморфизм.........................................................86 Урок 8. Еще раз о строках, контейнерах и потоках............................87 Часть 3. Фреймворк tkinter....................................................89 Урок 1. Хэлло, tkinter!.....................................................89 Урок 2. Color Mixer (миксер цвета) с обьектами класса Label.................91 Урок 3. Обьектно-ориентированный Color Mixer с метками......................94 Урок 4. Кнопки (Button), фреймы (frame), обработчики событий................96 Фокус ввода...............................................................97 Урок 5. Флажки (Checkbutton) и ассоциированные переменные..................100 2
Урок 6. Радио кнопки (Radiobutton).................................................102 Урок 7. Списки вариантов (Listbox) и привязка событий............................ 106 Урок 8. Полоса прокурутки (Scrol 1 bar)............................................109 Урок 9. Поля ввода (Entry) и табличное размещение (Grid Layout)....................112 Part 1 Fundamentals.................................................................. 9 Lesson 1. Getting Started............................................................9 Downloading Software...............................................................9 Your First Python Program.........................................................10 Fixing Errors.....................................................................10 IDE - FAQ.........................................................................12 Lesson 2. Statements................................................................12 Calculator........................................................................13 Lesson 3. Variables, Literals, Types and Assignments................................15 Variables and Literals Demo.......................................................17 Casting ..........................................................................18 Lesson 4. Operations................................................................19 Arithmetic Operations............................................................ 19 Comparison (Relational) Operations................................................21 Logical Operations................................................................24 Concatenation.....................................................................25 Lesson 5. Expressions...............................................................26 Lesson 6. Input/Output................................................................28 Output............................................................................28 Input.............................................................................29 Lesson 7. if Statement..............................................................29 3
One Case if..........................................................................30 Two Cases if.........................................................................31 Multiple Cases if....................................................................32 Lesson 8. Loops........................................................................33 for Loop ............................................................................34 while Loop...........................................................................35 Finishing Loops Ahead of Time........................................................36 Where Is do-while though?............................................................38 Lesson 9. Exceptions...................................................................38 Lesson 10. Functions...................................................................44 Basic Terms .........................................................................44 Parameters and Arguments.............................................................46 Premature Exit.......................................................................47 Returned Values......................................................................48 Global and Local Variables...........................................................49 Modules..............................................................................50 User Defined Functions, Library Functions and Event Handlers.........................50 Lesson 11. Data Containers............................................................ 51 Lists................................................................................52 Dictionaries.........................................................................53 Tuples...............................................................................54 Asterisk Operator....................................................................54 Lesson 12. Containers and Loops........................................................55 Iterators............................................................................57 Lesson 13. Strings.....................................................................58 4
Lesson 14. Two-Dimensional Lists.....................................................60 Lesson 15. Files and Streams.........................................................62 Permanent Storage..................................................................62 Reading............................................................................65 Reading/Writing....................................................................68 Part 2. Object Oriented Programming (OOP)..............................................70 Lesson 1. Introduction...............................................................70 Essential Terms....................................................................70 First Class........................................................................71 Lesson 2 Developing Methods..........................................................74 Lesson 3. Static Class Members.......................................................75 Static Variables...................................................................75 Static Methods.....................................................................76 Lesson 4. Data Hiding (Encapsulation)................................................78 Getters and Setters................................................................79 Properties.........................................................................80 Lesson 5. Composition................................................................81 Lesson 6. Inheritance................................................................84 Lesson 7. Polymorphism...............................................................86 Lesson 8. Strings, Containers and Streams Revisited..................................87 Part3 Framework tkinter................................................................89 Lesson 1. Hello, tkinter!............................................................89 Lesson 2. Color Mixer with Labels....................................................91 Lesson 3. Object-oriented Color Mixer with Labels....................................94 Lesson 4. Buttons, Frames, Event Handlers.......................................... 96 5
Keyboard Focus,......................................................................97 Lesson 5. Checkbuttons and Associated Variables.......................................300 Lesson 6, Radiobuttons................................................................102 Lesson 7. Listboxes and Events Binding................................................106 Lesson 8. Scrollbar...................................................................109 Lesson 9. Entry Fields and Grid Layout................................................112 6
Слово к читателю «Трудно дело птицелова, Заучи повадки птичьи, Помни время перелетов, Разным посвистом свисти...» <Э. Багрицкий, «Юго-запад»> В начале 80-х, в бытность сначала студентом Новосибирского университета, а потом и младшим программистом в НИИ электронных приборов, я увлекался работами Ершова, Кнута, Дейкстры, Хоара, Даля. То было славное время романтиков, эстетов от программирования. Что-то из того, чему они учили, может показаться наивным с позиций сегодняшнего дня, но главный их посыл актуален и сейчас, а именно, — они прививали вдумчивость, системность, умение «зреть з корень». Уже тогда А. П Ершов точно подметил, что существуют две категории разработчиков "штабные" и "боевые". «Штабное» программирование сегодня трансформировалось в целую науку - software engineering and design (прог рамная инженерия и проектирование). Дабы отделить идеи алгоритмоь от их имплементаций, «увидеть лес за деревьями», Дональд Кнут в «Искусстве программирования» даже придумал свой собственный виртуальный компьютер и язык к нему. Прошло время, и в течении более чем сорока лет практического программирования и преподавания программирования, в течении сорока лет «свистения» всеми мыслимыми «посвистами», мне становилось все более очевидным сущностное сходство всех языков программирования и универсальность заложенных в них концепций, каковыми с моей точки зрения являются: • Операторы • Переменные и примитивные типы • Операции • Выбор • Циклы • Исключения • Функции • Контейнеры данных • Файлы • Классы Тогда возникла мысль обобщить свой опыт и написать универсальный учебник по программированию, проиллюстрировав вышеперечисленные концепции примерами на разных языках. Идея была сама по себе увлекательной, я даже написал первые главы, но затем спросил себя. «Была бы такая книга интересна начинающим?» и понял что вряд ли. Ведь начинающий программист хочет (причем быстро!) научиться писать на каком-то языке, а не на всех сразу. Тогда первоначальная идея единого учебника трансформировалась в проект серии пусть и разных, но единых с точки зрения методики учебников («букварей»). 7
Важной составляющей всех букварей является последний раздел, где теоретические, на первый взгляд абстрактные концепции начинают «работать» в контексте определенной платформы (т. н. «фреймворка»). Поскольку недостаточно обьяснить, из каких частей состоит рубанок, дрель или отвертка, как эти части называются, и г- чем их назначение, нужно показать, как подготавливается доска, как сверлятся отверстия, заворачиваются шурупы ит. д Любой инструмент ценен не сам по себе, а как помощник человека при решении конкретных задач. Питон стал первым букварем в этой серии. Приятного прочтения! Андрей Саженюк, ноябрь 2025 8
Часть 1. Основы Урок 1. Введение Загрузка софта Рис. 1-1-1. Python Software Foundation (програмное обеспечения для Пигона) — официальный веб сайг. Зайдите на сайт python. org, скачайте и уствновите Питон IDLE для Windows, Мас ОС, или Linux (многие сборки Linux уже включают Питон). Определение.: IDE (or IDLE) расшифровывается как Integrated Development Environment (ИСР, интегрированная среда разработки) или Integrated Development and Learning Environment (интегрированная среда разработки и обучения). Говоря просто, IDE - это инструментарий программиста. Существуют много популярных IDE: MS Visual Studio, MS Visual Studio Code, PyCharm, Android Studio, Xcode, JetBrains и т д. Некоторые IDE созданы под определенный язык программирования, некоторые более универсальны и могут быть использованы для разработки на разных языках. Типичная IDE состоит как минимум из 3-х компонент: * Редактор кода • Компилятор • Тестер и отладчик То есть, вы можете печать и править код, компилировать (преобразовывать код в машинные команды), тестировать программу и (если есть ошибки или баги) отлаживать ее. Более продвинутые IDE могут включать в себя встроенный графический редактор для дизайна GUI (рус. - графический интерфейс пользователя) чиджетов, генератор кода (готовые для использования шаблоны, позволяющие ускорить разработку), средства управления проектами и нерсиями и т. д. 9
Ваша первая программа на Питоне 1 Запустите IDE. 2. Напечатайте следующий код в редакторе (вы можете подставить свое имя и город) print("Hi Python!") print ("I am Andrei.") print("I live in Toronto.") 3. Выберите File->Save команду меню (или нажмите на клавиатуре комбинацию CTRL+S). Сохраните вашу первую программу под любым именем и в любой папке на ваше усмотрение Все файлы программ на Питоне имеют расширение .ру. 4. Выберите Run->Run Modul е команду меню (или нажмите F5). 5. Вы увиде новое окошко (оно называется консоль или оболочка} и в нем три строчки текста: 1) хэлло, 2) ваше имя, 3) ваш город. Если получилось, поздравляем1 Как вы видите, IDE Питона работает в двух окнах, редактора (для набора текста) и консоли (для тестирования программы). Вы можете расположить эти окна на свое усмотрение, например, слева — редактор, справа — консоль, либо максимизировать оба и переключаться между ними (ALT+TAB). * £4Е5Ы«1НЛ Г-Й (44 СИМ CWi Л -fit t.tt.THNG Сс а f thui P jficnCtU Ila.eadjrfcc fci*$ - HJ 1 print ('ЧН ’: ?yth n") 2 print("1 am Andrei") 3 print("1 live in Toronto") 4 Python 3.11.5 (tags/v3.11.5:cce6ba9, Aug 24 2023, 14:38:34) [MSC v.1936 64 bit (AMDb4)] on Win32 Type "help", "copyright", "credits" or "lice nse()" tor more information. = RESTART: G:\EVERYTHING\Courses\Python\Pyth onBook\partl\lessun01 _intro\first.py Hello Python I am Andrei I live in Toronto Рис. 1-1 -2. Моя первая iipoi рамма — редактор слева и консоль справа. Работа над ошибками Что если я сделал ошибку? Например, забыл напечатать закрывающуюся скобку в первом print () ? Компилятор Питона выделит красным цветом то место кода, где приблизительно случилась ошибка, и покажет сообщение с коротким обьяснением ошибки 10
A firstpy • EAEVERYTHlNG\Coune$\Python'’ PythorBcwk\part1\le«on01 jntro\rir$tpy (3,1.,. — □ X , File Edit Format Run Options Window Help print|"Hello Python" print("I am Andrei") print("I live in Toronto") Л SyntaxEnof X (Jjl T wes nwer closed I °* I Ln.1 СоЮ] Рис. 1-1-3. Ошибка синтаксиса обнаружена. Иногда Питон не видит проблему сразу, но находит ее позже, во чремя запуска программы. Вообразим, что мы напечатали prin () вместо print (). Тогда мы увидим следующее во время запуска программы: г<« (Б* СЧЬиа Python 3.13.2 (tags/v3.13.2.4f9bb39, Feb 4 2025, 15: 23:48) [MSC v 1942 64 bit (AMD64)] on Win32 Type "help”, "copyright", "credits" or "license ()" fo r more information. >» = RESTART E:\EVERYTHlNG\Coursc-s\Python\PythonBook\pa rtl\lesson03 _intro\first py Traceback (most recent call last). File "E \EVERYTHING\Courses\Python\PythonBook\partl \lesson0L_intro\first.py", line 1, in <module> prin("Hello Python") NameError name 'prin' is not defined Did you mean: 'print'? »> Рис. 1-1-4. Синтаксические ошибки могут быть обнаружены во время выполнения программы. Красным цветом печатается обьяснение: где примерно (номер строки) и почему случилась ошибка Существуют дна типа ошибок: • Синтаксические • Семантические (смысловые) Синтаксические ошибки носят формальный характер: например, вы просто не соблюли формат определенных операторов или ключевых слов, пропустили запятую, не закрыли правильно скобки и т. д. Такие ошибки легко обнаруживаются компьютером. Смысловые ошибки найти сложнее, — ваша программа была неправильно спроектирована и работает не так, как вы ожидали. Только человек может идентифицировать и исправлять такие ошибки. 11
По окончанию урока закройте оба окошка. Если вам понадобится эта программа позже, вы сможете ее найти используя команду Fi le->Recent Files или через Fi 1е->0реп... IDE — FAQ (Frequently Asked Questions, часто задаваемые вопросы,) Как изменить размер шрифта ь редакторе и в консоли? Выберите команду меню Options->Conf igure IDLE, вы увидете диалог Settings с шестью закладками, выберите закладку Fonts, откройте список Size, выберите размер, который вас устраивает Я запускал программу несколько раз, в результате я вижу в окне консоли слишком много текста; как его почистить? Закройте консоль, не закрывайте редактор; запустите программу опять (F5). Когда я запускаю IDE, я вижу консоль, а не редактор. Как я могу изменить установки, чтобы по умолчанию запускался редактор? В диалоговом окне Settings выберите закладку Windows; чы увидите два At Startup варианта: Open Edit Window или Open Shell Window; выберите Open Edit Window. Мне больше нравятся темные цвета во время кодирования. Как переключиться со светлой темы на темную? В диалоговом окне Settings выберите Highlights закладку; откройте IDLE Classic список и поменяйте тему на IDLE Dark. Как включить в редакторе номера строк? Выберите команду меню Opt ions->Show Line Numbers. Урок 2. Операторы Определение: оператор — запрос программиста компьютеру на выполнении определенной работы. Определение: компьютерная программа (или скрипт) — последовательность операторов. Иными словами, компьютерная программа похожа на серию глаголов в повелительном наклонении Получи! Сложи! Вычти! Сравни! Напечатай! Перейди на такой-то шаг! То есть, максимально упрощенно, работа программиста состоит в написании операторов Компьютер выполняет операторы, как они написаны, сверху вниз. В первом уроке мы написали очень простую программу, состоящую из трех операторов Существует два типа операторов: • Простые • Составные 12
Простые операторы занимают одну строку кода и не могут содержать другие операторы Например: • Оператор присваивания • Оператор вызова функции * Операторы прерываний (return, break, conti пие ит.д) Наша первая программа состояла из трех вызовов библиотечной функции print () (мы обсудим функции в части 1 уроке 10). Составные (или управляющие) операторы могут занимать много строк и содержать внутри себя простые операторы. Например: ♦ оператор выбора if-else • оператор цикла for * оператор цикла while • оператор исключений try-except. Составные операторы могут быть вложенными. Калькулятор Разберем простую программу, которая запрашивает у польз» -вателя два числа и символ математической операции (+, -,*,/). Затем программа вычисляет сумму, разность, произведение и частное этих чисел. Просто напечатайте код и запустите программу. От вас не требуется сейчас понимание деталей; смысл программы в том, чтобы проиллюстрировать понятие оператор import sys # Часть 1 - переменные и присвоения numberl = 0.0 operation = number2 = 0.0 result = 0.0 buffer = "" # Часть 2 - ввод данных, вызовы ЬиОлиотечных функций print("Type the first number:") # первое число buffer = input () numberl = float (buffer) print("Type the second number:") # второе число buffer = input () number2 = float(buffer) printCType the operation ( + , -, /, *):") # арифметическая операция operation = input() 13
# Часть 3 - начало оператора if-else if operation == " + resul t = numberl 4- number2 print("Will add...") # будет сложение... elif operation == : result = numberl - number2 print("Will subtract...")# будет вычитание... elif operation == result = numberl * number2 print("Will multiply. ")# будет умножение... elif operation == "/": result = numberl / nurnber2 print ("Will divide...")# будет деление... else: print("Wrong operation!") # неверная операция sys.exit() # конец оператора if-else # Часть 4 - печать результатов print(numberl, operation, number2, " = ", result) Рис. 1-2-1. Калькулятор —код. Строки с хэш-тэг ом — комментарии, они пишутся для людей и обьясняют логику программы. Комментарии игнорируются интерпретатором Питона. Строка import sys не есть выполняемый оператор, это директива подключения к программе библиотечного модуля. Библиотечные модули позволяют нам экономить время, поскольку в них запрограммированы многие часто используемые, стандартные задачи. Анализ Программа состоит из четырех частей: 1. Вначале мы имеем пять деклараций переменных и пять операторов присваивания 2. Затем мы спрашиваем пользователя предоставить нам два числа и математическую операцию, Для этого мы вызываем три библиотечные функции - print (), input (), и float (). Мы можем в одном операторе совместить вызов фикции и присваивание. Во второй части программы 8 простых операторов 3. Составной оператор if-else занимает 15 строк. Вы видите что внутри составною оператора используются простые операторы: присвоения и вызовы функций Простые операторы внутри составного набираются (и это важно!) с отступом Мы будем называть группу операторов с отступом — блок 4. Печать результата 14
Type Lhe first number: 12 Type the second number: 13 Type the operation (+, /, *): Will subtract... 12.0 - 13.0 = -1.0 »> Рис. 1-2-2. Калькулятор — тест. Иногда простые операторы не помещаются в одной строке. Используйте обратную косую черту чтобы разбить длинный простой оператор на две или более строке movieName = "The Assassination of Jesse James " + \ "by the Coward Robert Ford" (Название фильма — «Как трусливый Роберт Форд убил Джесси Джеймса») Урок 3. Переменные, литералы, типы и оператор присваивания Будучи программистом, вы должны обрабатывать различные типы данных Для этого требуются переменные и литералы. Если операторы это елементарные единицы действия, то переменные и литералы это элементарные единицы данных. Определение: переменная — поименонэная область памяти компьютера (англ. RAM, Random Access Memory), Вы можете представить себе RAM как таблицу с двумя колонками Ячейки памяти (или байты) пронумерованы, то есть имеют адреса; по каждому адресу записано определенное значение. Программирование в конкретных адресах — сложный, утомительный и чреватый ошибками процесс. Переменные с осмысленными именами облегчают и упрощают доступ программиста к RAM Адреса Значения — myVar —< — ... ... 1012 99 1013 -100 1014 0 1015 55 ... ... Рис. 1-3-1. Память (RAM) и переменная myVar. 15
Любая переменная имеет: • имя • значение • тип Имена переменных придумывает программист. Имя это идентификатор переменной, значение — это текущее содержание («наполнение») переменной, и тип — это формат хранимых в переменной данных. Имя переменной не меняется в процессе выполнения программы, однако и значение и тип могут меняться. Имя переменной может состоять как из букв так и из цифр, но первый символ не должен быть цифрой. Пробелы в именах использовать нельзя, и если имя переменной состоит из нескольких слов, вы можете разделять их знаком подчеркивания или посредством заглавных букв: my_lona_variable_name myLongVariableName. Придерживайтесь стиля, который нам нравится, но старайтесь не смешивать стили в одном проекте. Имена переменных чувствительны к регистру клавиатуры. Например, price и Price — две разных переменных. В Питоне существуют четыре основных типа переменных Мы называем их примитивные (или встроенные) типы. • целый (int) * с плавающей точкой (float) • строковый (str) • булевый (bool) int переменные используются для хранения челых чисел, float — для дробных чисел, str variables — для текста, и bool — для True/False (истина/ложь) значений. Определение: литерал это явно выраженное, неизменяемое данное. Литералы не имеют имен как переменные, но у них есть и значения и типы Примеры: 5 — литерал типа int 9 9.8 — литерал типа float "abed" — литерал типа str True/FaIse — литералы типа bool. 16
Определение: опрератор присваивания (=) присваивает переменной новое значение. Слева от знака равенства указывется переменная, справа — но^ое значение. Переменные и литералы — демо # 5 переменных определены и инициализированы firstName ="John" # имя lastName = "Brown" # фамилия age =25 # возраст averagePythonMark = 88.5# средняя оценка по Питону honorAward = True # 5 выводов на экран print(firstName) print(lastName) print(age; print(averagePythonMark) print(honorAward) Рис. 1-3-2. Переменные и литералы — код. Как вы ьидите, мы определили пять переменных и присвоили им начальные значения, Присвоение начальных значений переменным называется инициализацией. То есть мы имеем пять оператором присваивания, где слева от знака «равно» переменные, справа — литералы. Если поле одного присваивания происходит второе, старое значение переменной теряется и не может быть восстановлено. Целые литералы состоят только из цифр, литералы с плавающей точкой имеют целую часть, точку и дробную часть, строковые литералы содержат текст в двойных или одинарных кавычках, булены литералы представлены ключевами словами True/False. После присваиваний мы печатаем значения переменных используя библиотечную функцию print (). Эта функция может печатать одну переменную/литерал или несколько переменных/литералов через запятую. Определение: ключевое слово (зарезервированное слово) в языке программирования имеет раз и навсегда определенное значение. Количество ключевых слов ограничено. Программисты не могут использовать ключевые слова для имен своих переменных, функций (часть 1, урок ] 0) или классов (часть 2, урок 1). Примеры ключевых слов: if, else, while, import, except, True, False. John Brown 25 88.5 True Рис. 1-3-3. Переменные и литералы — тест. 17
Мы можем присваивать литерал переменной, мы можем присваивать одну переменную друюй переменной, но невозможно ничего присвоить литералу, поскольку литералы неизменяемы по определению. Мы иногда назваем литералы в программировании r-values («правые» значения), они могут появляться только справа от знака «равно». Переменные с другой стороны являются l-values («левые значения»), они могут появляться и слева и справа от знака «равно» Следующие операторы присваивания не имеют смысла. 33.3 = 44.4 "abc" = anyVariable Попытка запустить такую программу вызовет сообщения об ошибках. Следующая программа демонстирирует, что переменная может менять свой тип (библиотечная функция type () была использована для вывода на экран типа переменной). testVar = 99 print(type(testVar)) testVar = 99.99 print(type(testVar)) testVar = "99.99" print(type(testVar)) testVar = False print(type(testVar)) Рис. 1-3-4. Изменение типа — код. <class <class Cclass <class ’ int'> 'float'> 'str'> 'bool1> Рис. 1-3-5. Изменение гина — тест. Кастинг Мы можем переопределять типы переменных, используя четыре библиотечные функции: int (), float (), str (), и bool (). Мы называем эти функции функции кастинга. Например, следующие 2 оператора преобразуют число с плавающей точкой в строку (иными словами, 99.99 будет перформатироиано как пять символов "99.99" . myVar = 99.99 myVar = str(myVar) Следующие 2 оператора преобразуют переменную с плавющей точкой к булевому формату (то есть 99.99 превратится в True, поскольку только число 0 . О интерпретируется как False): myVar = 99.99 myVar = bool(myVar) 38
Кастинг может приводить к неожиданным результатам, будьте внимательны. Например, кастинг чисел в строки безопасен, но кастинг строк в числа может привести к аварийному завершению программы, поскольку не всякая строка представляет из себя число Урок 4. Операции Определение: операции — специальные символы, дающие возможность программисту манипулировать данными. Обычно, операция — это один (+, *, /) или два специальных символа (<=, ==) (не печатайте пробел между ними!); также операции могут быть представлены ключевыми словами языка (not, and, or). Используя операции, программисты могут слагать, вычитать, сравнивать и т д. Основные операции в Питоне делятся на три группы: • Арифметические • Сравнения • Логические Определение: операнды это переменные или литералы, к которым относится операция. Определение: бинарная операция относится к двум операндам. Определение: унарная операция относится к одному операнду. Арифметические операции • сложение (+) англ, sum • вычитание (-) англ, difference • умножение (х) англ multiplication • деление(/) англ, division • целое деление (//) англ, integer division • остаток (%) англ remainder • степень (**) англ, power • минус (-) англ, minus • инкремент (+=) англ, increment • декремент (-=) англ decrement Арифметические операции работают на int или float операндах и производят int или float результаты. Символ в Питоне перегружен, то есть имеет два значения в завсисмости от контекста: если есть только один операнд справа, это минус, если есть два операнда слева и справа, это вычитание. 19
numl = 7 num2 = 2 result = numl + num2 print(numl, "+", num2, "=", result) result = numl - num2 print(numl, num2, "=", result) result = numl * num2 print (numl, "*", num2, result) result = numl / r.um2 print(numl, "/", num2, "=", result) result = numl // num2 print(numl, "II", num2, result) result = numl % num2 print(numl, "%", num2, result) result = numl ** num2 print(numl, и**", num2, "=", result) result = -numl print("-", numl, "=", result) Рис. 1-4-1. Арифметические ииераюры — код. 7 + 2 = 9 7-2 = 5 7 ♦ 2 = 14 7 / 2 = 3.5 7 // 2 = 3 7 % 2 = 1 7 ** 2 = 49 - 7 = -7 »> Рис. 1-4-2. Арифметические операторы — тест. 2П
Имя Символ(ы) Тип операндов Число операндов Тип результата Сложение + int/float 2 int/float Вычитание — int/float 2 int/float Умножение ★ int/float 2 int/float Деление / int/float 2 int/float Целое деление // int/float 2 int/float Остаток % int/float 2 int/float Степень ★ ★ int/float 2 int/float Минус — int/float 1 int/float Инкремент += int/float 2 int/float Декремент — = int/float 2 int/float Рис. 1-4-3. Сводная таблица арифметических операторов. Пример оператора инкремента. myVar = 5 myVar += 10 # тсже самое что myVar = myVar + 10 Этот код увеличивает значение переменной на 10, то есть значение становится 15 Заметим, что операции увеличения и уменьшения созданы просто для вашего удобства, -<ы не обязаны ими пользоваться. Вы можете использовать классический синтаксис (это показано в комментарии) Заметим также, арифметические операции не меняют значения операндов, исключение — операции инкремента и декремента; они оба меняют значение левого операнда. Операции сравнения • равно (==) • не равно (!=) • больше (>) * больше или равно (>=) • меньше (<) • меньше или равно (<=) англ, equal англ not equal англ greater than англ, greater than or equal to англ, less англ, less than or equal to 21
Операции сравнения — бинарные и работают на всех типах данных, однако сравниваемые операнды должны быть одинакового типа. Операторы сравнения дают булевый результат (True/False). numl = 22 num2 = 20 result = (numl == num2) print("Is 22 equal to 20?", result) result = (numl != num2) print("Is 22 not equal to 20?", result) result = (numl > num2) print("Is 22 greater than 20?", result) result = (numl >= num2) print("Is 22 greater than or equal to 20?", result) result = (numl < num2) print("Is 22 less than 20?”, result) result = (numl <= num2) print("Is 22 less than or equal to 20?", result) Рис. 1-4-4. Операторы сравнения — код. Is 22 is equal to 20? False Is 22 is not equal to 20? True Ts 22 greater than 20? True Is 22 greater than or equal to 20? True Is 22 less than 20? False Ts 22 Jess than or equal to 20? False Is 'abc' less than ’ABC? False >» Рис. 1-4-5. Операторы сравнения — тест. 22
Имя Символ(ы) Тип операндов Число операндов Тип результата Равно любой 2 bool Не равно 1 = любой 2 bool Больше > любой 2 bool Больше или равно любой 2 bool Меньше < любой 2 bool Меньше или равно любой 2 bool Рис. 1-4-6. Сводная таблица операторов сравнения. Операции сравнения интенсивно используются в операторах if (часть 1 урок 7) и операторах циклов (часть 1 урок 8) Сравнение строк Строки обсуждаются в части 1 уроке 13, Но. говоря просто. — строка это набор символов Вы можете адресовать символы в строке, используя квадратные скобки и порядковый номер символа (первый символ имеет номер 0, второй — 1 и так далее). Строки сравниваются, согласно кодам символов. Каждый символ имеет так называаемый ASCII код. ASCII расшифровывается как американский стандартный код обмена информации. Скажем, нам нужно сравнить str 1 и s tr2. Сначала сравниваются два первых символа, если str 1 [0] меньше чем str2|0], вся строка strl считается меньше чем str2 вне зависимости от их длины. Если первою символы идентичны, сравниваются вторые символы, весь процесс заканчивается когда достигнут конец самой короткой строки из двух. Строка "abc" меньше чем строка "abed". Заглавные буквы имеют меньшие коды чем прописные. Например строка "а" больше чем строка "А". Пустая строка ("") меньше любой непустой строки 23
Dec Hex Name Char (trichar Dec Hex Char Dec Hex Char Dec Hex Char 0 0 Null NUL CTRL-0 12“ Space 64 40 96 60 1 1 Stan of head-no SOH CTRL-A 33 21 I 65 41 A 97 61 a 2 2 Start cr text STX CTRL-8 34 22 «* 66 42 В 96 62 b 3 3 End of tert ETX CTRL-C 35 23 • 67 43 C 99 63 c 4 4 End of xrnit EOT CTRl-D 36 24 $ 68 44 D 100 64 d 5 5 Enqur> ENQ CTRl-E 37 25 % 69 45 E 101 65 • 6 6 да no ACK CTRL-F 38 26 a 70 46 F 102 66 f 7 7 Be<i BEL CTRL-G 39 27 71 47 G 103 67 0 8 8 Backspace B$ CTRl-H 40 28 ( 72 46 H 104 68 и 9 9 HorijontaT tab HT CTRL-l *1 29 ) 73 49 1 105 69 i 10 од Linefeed lf CTRL-J 42 2A • 74 4A J 106 6A J 11 06 Vertical tab VT CTRL-iC 43 2B + 75 48 К 107 6B k 12 ОС Form feed FF CTRL-L 44 2C 9 76 4C L 108 6C 1 13 ОО Carn age feed CR CTRL-M 45 2D 9 77 40 M 109 6D m 14 06 Shift out SO CTRl-N 46 2E 18 4E N 11C bE n 15 OF Shftm St CTRlO 47 2F / 79 4F 0 111 6F 0 16 10 Data Ime escape OLE CTRL-P 48 30 0 80 50 P 112 70 P 17 11 Device control 1 DC1 CTPL-Q 49 31 1 81 51 Q 113 71 Q 18 12 Device control 2 DC2 CTRL-R 50 32 2 82 52 R 114 72 r 19 13 Device control 3 DC3 CTRL S 51 33 3 83 53 S 115 73 s 20 14 Device control 4 DC4 CTRL-T К 34 4 84 54 T 116 74 t 21 15 Neg acknowledge NAK CTRL-U 53 35 5 85 55 и 117 75 u 22 16 Synchronous ic»e SYN CTRL-V 54 36 6 86 56 V 118 76 V 23 17 End of xm»t Mock Elfe CTRl-w 55 37 7 87 5? w 119 77 V» 24 18 Cancel :an CTRL-X 56 38 8 66 58 X 120 ?8 X 25 19 End of medum EM CTRL-Y 57 39 9 89 59 V 121 79 Y 26 1А Substitute SUB CTRL-2 58 ЗД • 90 5A 2 122 7A z 27 18 Escape ESC ctrl-[ 59 38 9 91 SB t 123 7B < 28 1С File separator FS CTRL-\ 60 X < 92 X \ 124 7C 1 29 1D Group separator GS CTRL-] 61 3D 93 3D 1 125 7D 30 16 Record seca'atcv RS CTRL-*4 62 ЭЕ > 94 5E 126 7E 31 U unit separator US CTRL-. 63 3F 95 SF 127 7F DEL Рис. 1-4-7. Таблица ASCII кодов. Логические операции В Питоне существуют три логических операции: • and (рус. и) • or (рус. или) • not (рус. не) Они работают с булевыми операндами и производят булевый результат согласно хорошо известным правилам булевой алгебры. True and True = True True and False = False False and True = False False and False = False True or True = True True or False = True False or True = True False or Fal se = False 24
not False = True not True = False Операция not является унарной и относится к операнду справа. Ы = True Ь2 = False print("True and False is", Ы and b2) print("True or False is", bl or Ь2) print("Not True is", not bl) Рис. 1-4-8. Логические операции — код. True and False is False True or False is True Not True is False »> Рис. 1-4-9. Логические операции — тест. Имя Символы Тип операндов Число операндов Тип результата И and bool 2 bool Или or bool 2 bool Не not bool 1 bool Рис. 1-4-10. Логические операции — сводная габлипа. Конкатенация (слияние) Операция "+" перегружена в Питоне, она делает две разные вещи суммирует числа и обьединяет (конкатенирует) строки, Значение операции зависит от контекста: если операнды числовые, это сумма; если операнды строковые, это конкатенация Вы не можете конкатенировать строку и число, число должно быть сначала преобразовано в строку, используя кастинг firstName = "John " # имя lastName = "Brown" # фамилия age = 25 print("Full name is", firstName + lastName) print(firstName + lastName + " is " + str (age) + " years old") Рис. 1-4-11. Конкатенация — код. 25
Full name is John Brown John Brown is 25 years old »> Рис. i-4-12. Конкатенация —тест. Имя Символ Тип операндов Число операндов Тип результата Конкатенация + string 2 string Рис. 1-4-13. Конкатенация — сводная таблица. Операции - важные факты для запоминания: • операции делятся на три группы: арифметические, сравнения и логические * операции представлены специальными символами или ключевами словами (+, <=, not, and и т. д.). • операции могут быть бинарными (с двумя операндами) или унарными (с одним операндом) • все операции разные, запоминайте какого типа операнды им нужны и какого типа результат они производят. • операции не меняют операндов за исключением операции увеличения и уменьшения (+=и-=). • операция перегружена, то есть имеет два значения: сложение и конкатенация. ♦ операция ’ также перегружена, она имеет два значения: вычитание (если есть два операнда) и минус (если есть только один). Урок 5. Выражения Идея, лежащая в основе выражений, проста мы можем нанизывать операторы друг на друга. Выражения в программировании поистине вездесущи Мы используем их постоянно. Возьмем простой пример Мы купили две вещи, и хотим посчитать цену на кассе (включая налог и скидку) Мы могли бы это сделать в три шага, прямолинейно sumOfTwo = itemlPrice + item2Price # сумма priceWithTax = sumOfTwo * tax # налог total = priceWithTax - 15.0 # скидка 15 p. Существует, однако, более элегантное решение проблемы в одной строке: total = (iteml_price + item2_price) * tax - 15.0 26
Мы не только сократили код, мы также избавились от двух ненужных переменных (sumOfTwo и pr iceWithTax). То есть выражения экономят программистам массу времени В вышеупомянутом операторе прис ваивания выражение находится справа от знака равенства (оно выделено серым цветом). Определение выражение этс комбинация операций. Очень важно, что операции в ->ыражениях могут быть вложены результат одной операции может служить операндом для следующей операции Глубина вложения операций в выражениях не ограничена. Выражения вычисляются слева направо. Но внутри выражений различные операции имеют различный приоритет или силу. Например, операции * и / сильнее чем операции + и то есть Питон будет выполнять их первыми, если вы не проинструктируете его изменить порядок вычисления, используете круглые скобки. Круглые скобки в выражениях используются, чтобы указать правильный порядок 1ычисления Операции сравнения сильней, чем логические операции. Следовательно, вы не обязаны печатать: (а < b) and (с < d) Вы можете упростить: а < b and с < d В справочной документации к Питону вы можете найти детальные таблицы, где указаны все операции и их приоритеты Эти таблицы полезно знать, но не обязательно запоминать. Используйте круглые скобки, если вы не уверены в приоритетах и хотите обеспечить правильный порядок оценки выражения. Выражения — важные факты: • переменная является выражением, • литерал является выражением, • операция вместе с ее операндами является выражением, • выражения могут быть вложены, • любое выражение имеет результат, • оценка выражения это процесс вычисления его результата, • любое выражение имеет тип (это тип результата выражения), • круглые скобки используются в выражении чтобы контролировать процесс оценки выражения Выражения — более крупные элементы языка, чем переменные, литералы и операции, но они менылие элементы языка чем операторы (выражения используются внутри операторов). 27
Оператор Выражения Переменные/литералы Операции Рис. 1-5-1. Взаимосвязь между операторами, выражениями, переменнымн/литералами и операциями. Урок 6. Ввод/вывод Библиотечная функция input ()требуется для пользовательского ввода данных с клавиатуры, print () для вывода данных на экран. Вы видели применение обеих функций в наших коротких демо программах. Обсудим детали. Вывод Формат библиотечной функции print (): print (выражение [, выражение 2] [, выражение 3] ...) Мы используем квадратные скобки чтобы показать необязательные параметры. Выражения могут быть любого типа, но когда они печатаются, они автоматически преобразуются в строки (происходит кастинг). userAge = 28 # возраст пользователя print(userAge, " years is equal to ", userAge * 12,\ " months or ", userAge * 12 * 365, " days ", sep="") Рис. 1-6-1: print () — код. 28 years is equal to 336 months or 12264U days »> Рис. 1-6-2: print () — тест («28 лет это 336 месяцев или 122640 дней»). По умолчанию, после печати оператор print () автоматически делает переход на новую строку, однако иногда нам нужно, чтобы несколько еыгодов были на одной и той же строке Этого можно достичь, используя специальный параметр end и присвоив ему пустую строку, например print("Hello", end = "") print("John") Таким образом, оба слова будут напечатаны на одной строке 28
Также по умолчанию print () добавляет пробел между печатаемыми выражениями. Если вы этого не хотите, вы можете использовать sep = "" parameter. Таким образом пробелы не добавляются Ввод Библиотечная функция input О приостанавливает выполнение программы, ожидает пользовательского ввода с клавиатуры, и затем (после того как пользователь нажал клавишу ENTER} сохраняет напечатанное в строковой переменной. print("Your name?") # Ваше имя? name = input () # ввод print("Hi ", name, "!", sep = "") print("Your age?") # Ваш возраст? age = int (input ()) # ввод и кастинг print("You are", age, "years old.") Рис. 1-6-3: input () —код. Your name? Andrei Hi Andrei! Your age? 33 You are 33 years old. Рис. 1-6-4: input () —тест. Обратите внимание на строку, где вы вводите возраст (аде). Если --ам требуется на выходе int или float значения, делайте into или float () кастинг. Однако, не всякая строка конвертируется е число, В таком случае пргорамма может аварийно завершится (будет сгенерировано так называемое исключение, исключения обьясняются в части 1, урок 9) То есть перед кастингом, чтобы избежать сбоя, вы можете проверить строку на корректность, например, используя библиотечную функцию Питона isdigit (), которая проверяет является ли символ цифрой. Урок 7. Оператор if if является в Питоне оператором принятия решений В зависимости от разных условий, компьютер может выполнять разные задачи, двигаться по разным веткам выполнения программы. Существуют три вида оператора if: * с одним блоком • с двумя блоками • многоблочный 29
Одноблочный if Формат: i f булево_выражение: оператор-1 оператор 2 оператор N оператор после Обычно мы называем булево_выражение в операторе if условием. Компьютер оценивает условие и если они истинно (True), выполняет все операторы напечатанные с отступом от 1 до N Если булево_выражение ложно (False), все операторы с отступом пропускаются и выполнение переходит непосредственно к оператор после. Как правиле в операторе if используются операторы сравнения, поскольку они производят булеьы результаты (True/False). Группа операторов с отступом называется блок. Наиболее частые ошибки, который совершают начинающие «пайтонисты»: • зыбывается двоеточие после условия. • забываются отступы в блоке (вы можете печатать отступ клавишей TAB), age =18 if age >= 18: print("You are an adult") print("You are allowed to print("1st if completed") age = 9 if age >« 18: print("You are an adult") print("You are allowed to print("2nd if completed") # возраст - 18 # "вы взрослый" vote")# "вы можете голосовать" # 1-й if закончен # возраст - 8 vote") # 2-й if закончен Рис. 1-7-1: идноблочный xf — код. You are an adult You are allowed to vote 1st if completed 2nd if completed »> Рис. 1-7-2: одноблочный if — тест. Как вы ьидите, первО|й блок состоящий из двух операторов print () был выполнен, поскольку пользователь старше 18-ти, но второй блок из двух операторов print () был пропущен, поскольку пользователю только 8 лет. Очень часто в операторе if используются логические операции and, or, not. Таким образом мы можем комбинировать условия. 30
income = 60000.0 # годовой доход if income > 53359.0 and income <= 106717.0: # если доход в определенных # пределах print("Your tax brackets are 20.5%") # то налог 20.5% print("You will pay from income, " income income * 0.205,\ " taxes", sep="") print("if completed") Hue. 1-7-3: одноблочный оператор if с двумя условиями - код. Your tax brackets are 20.5% You will pay from $60000.0 income $12300.0 taxes if completed »> Pic. 1-7-4: одноблочный if с двумя условиями —тест ("вы заплатите с 60 тыс. дохода 12300 налогов''). Сошласно терминологии из второго урока, if — составной оператор Он может содержать внутри себя другие операторы, в частности, другой if. income = 60000.0 if income > 53359.0: if income <= 106717.0: print("Your tax brackets are 20.5%") print("You will pay from $", income, " income $",\ income * 0.205, " taxes", sep="") print("if completed") Рис. 1-7-5: вложенный оператор if — код. Эта версия работает в точности так же, как и предыдущая, но вероятно предыдущая предпочтительней, — она короче и проще для понимания Двухблочный if Формат: if булево_выражение: оператор 1 оператор_2 оператор_М else: оператор NM оператор N+2 onepaTop_N tM оператор_после 31
Компьютер оценивает булево_выражение и, если оно истинно, выполняет все операторы с отступом от 1 до N Второй блок операторов после else игнорируется и мы переходим к оператор_после. Если значение выражения ложно, первый блок операторов игнорируется, второй блок после else выполняется и мы переходим к оператор после. Если i f с одним блоком может оказаться пустым (компьютер не делает ничего), двухблочный if никогда не пуст, поскольку один из двух блоков всегда выполнится. Заметьте однако, что они никогда не могут выполниться вместе. income = 55000.0 if income > 53359.0 and income <= 106717.0: print("Your taxes are 20.5 ") print("You will pay", income * 0.205) else: print("Your taxes are not 20.5%") Рис. 1-7-6: двухблочный if — код. Когда вы вызовете эту программу, два первых сообщения печатаются, третье пропускается. Многоблочный if Формат: i f булево выражение 1: блок операторов 1 elif булево выражение 2: блок операторов 2 elif булево выражение 3: else : блок_операторов N опера тор_после Многоблочный оператор if иногда называют if-elseлестницей. Сначала вычисляется булево выражение 1, и если результат истина, выполняется первый блок операторов и осуществляется переход к оператор_после. Если булево_выражение 1 ложь, мы спускаемся на шаг вниз и оцениваем булево_выражение_2. Если оно истинно, то второй блок выполняется и компьютер перепрыгивает на оператор после. Наконец, если ни одно из булевых выражений не равно истине, выполняется else блок. То есть проверяя условия одно за другим, мы спускаемся шаг за шагом вниз, отсюда образное сравнение с лестницей. 32
Рассмотрим пример («альфой» в английском иногда называет буквенное, а не процентное выражение оценки): alpha = "" print("Type the grade:") # введите оценку в процентах grade = float(input ()) if grade >= 80.0: alpha = "A" elif grade >= 70.0: alpha = "R" elif grade >= 60.0: alpha = "C" elif grade >= 50.0: alpha = "D" else: alpha - "F" print("The alpha:", alpha) Рис. 1-7-7: многоблочный if — код. Type the grade: 51 The alpha: D »> Pile. 1-7-8: Многоблочный if — test. Блок else в лестинце опционален (не обязателен). Если его нет, вся лестница может оказаться пустой, мы как бы «проваливаемся» (англ, falling through}. Другими словами, alpha остается пустой строкой, если пользователь печатает процент меньше 50. alpha = "" print("Type the grade:") grade = float(input ()) if grade >= 85.0: alpha = "A" elif grade >= 70.0: alpha = "B" elif grade >= 60.0: alpha = ”C" elif grade >= 50.0: alpha = "D" print("The alpha:", alpha) Рис. 1-7-9: Mhoi облочный if без else — код. Урок 8. Циклы Цикл в программировании означает инструкцию компьютеру повторять ту же или почти ту же работу много раз. Один шаг цикла называется итерацией. Существуют два типа цикло*- в Питоне: • for • while 33
Цикл for Цикл for требуется чтобы повторить работу определенное количество раз; в Питоне цикл for использует библиотечную функцию range (мы пройдем функции в 1-й части, уроке 10). Функция range () дает нам набор целых чисел, она может иметь один параметр (stop), два параметра (start и stop), или три параметра (start, stop и step): • range(stop) • range(start, stop) • range(start, scop, step) range () (рус. — набор) с одним параметром включает челые числа от 0 до stop - 1. Набор с двумя параметрами включает челые числа от start до stop - 1. Набор с тремя параметрами включает челые числа от start до stop - 1, но увеличение происходит не на единицу, а на step. range () — важные факты для запоминания: • start включен и набор, но stop исключен, * набор может быть пуст, например, range (0) не содержит чисел. Цикл for формат for целая_переменная in range (...) : блок операторов Мы называем строку с ключевым елевом for — зоголоеок цикло. Блок операторов — тело цикло. Блок операторов будет повторен для каждого числа в наборе. Если набор пуст, цикл не делает ничего. print("*** range(5) ***") for i in range (5) : print (i) print("*** range(1, 5) ***") for i in ranged, 5): print(i) print("*** ranged, 5, 2) ***") for i in range (1, 5, 2) : print(i) print("*** ranged, 0, -1) ***") for i in ranged, 0, -1): print (i) print("*** range(O) ***") for i in range(0): print (i) Рис. 1-8-1: цикл for — код. 34
о 1 2 3 4 *** range(1, 5) *** 1 2 3 4 * ** range(1, 5, 2) *** 1 3 *** range (5, 0, -1) *** 5 4 3 2 1 *** range(0) *** »> Рис. 1'8-2: цикл for — тест. Цикл while Цикл while более унивесален нежели цикл for; он может быть использован, когда число итераций неизвестно заранее Цикл while формат, while булево_ьыражение: блок операторов Блок операторов выполняется пока булево_выражение истинно Как только булево выражение становится ложным, оператор цикла заканчивается и управление передается следующему оператору после цикла. i = 1 while i <= 10: print(i) i = i + 1 print("Loop finished") Рис. 1-8-3: цикл while — код. 35
1 2 3 4 5 6 7 8 9 10 Loop finished Рис. 1-8-4: никл while— тест. Если булево выражение ложно сразу, то есть уже в начале работы цикла, то оператор while оказывается пустым. Мы «проваливаемся» на следующий оператор. Если булево выражение истинно (цикл работает), но оно не меняется в ходе итераций, случается очень распространенная ошибка в программировании — бесконечный цикл. Вы видите, что ваш компьютер как бы «замерз» (англ, frozen), то есть не реагируетет на ваши действия Это означает, что он делает снова и снова одну и ту же работу в цикле. Например, если в предыдущем демо вы забыли оператор увеличения (i = i + 1), вы войдете в бесконечный цикл. Преждевременный выход из цикла В силу определенных обстоятельств иногда требуется завершить цикл раньше времени Два оператора Питона помогут вам в этом • break • continue Если оператор break завершает цикл и передает управления на следующий оператор после цикла, оператор continue завершает только текущую итерацию цикла, но не завершает весь цикл. Оба оператора используются внутри оператора if: при определенном условии весь цикл (или только одна его итерация) завершаются. 1 = 1 while i <= 10: print ("**********") print("Number:", i) # число: print("Square, i * i) # квадрат: if i == 5: break print("Cube, i * i * i) # куб: i = i + 1 print("Loop finished") # цикл завершился Рис. 1-8-5: break — код. 36
********** Number: 1 Square: 1 Cube: 1 Number: 2 Square: 4 Cube: 8 Number: 3 Square: 9 Cube: 27 -^kk-k-kic-kic-k-k Number: 4 Square: 16 Cube: 64 Number: 5 Square: 25 Loop finished >» Рис. 1 -8-6: break — тест. Как вы [.идите, цикл спланированный на десять шаюв, отработал только пять for i in range(5): print ("--------") print("Number, i) print("Square:", i * i) if i == 3: continue print("Cube, i * i * i) print("Loop finished") Рис. 1-8-7: continue — код. Number: 0 Square: 0 Cube: 0 Numbe r: i Square: Cube: 1 1 Number: 2 Square: Cube: 8 4 Number: 3 Square: 9 Number: 4 Square: 16 Cube: 64 Loop finished Рис. 1-8-8: continue — тест. 37
Как вы видите, третья итерация была прервана, и число 3 в кубе не было напечатано, однако цикл продолжался. Где do-while однако? Читатели, уже знакомые с другими языками программирования могут недоумевать, куда делся в Питоне оператор do-while. Дело в том, что иногда в цикле возникает потребность сначала делать итерацию, а потом уже проверять нужно ли продолжать Для этого практически во всех языках программирования существует оператор do-while. Если цикл while может оказаться пуст, цикл do-while работает как минимум один раз. Приведем такой пример: нам нужно сначала взять число от пользователя, потом посчитать его квадрат, а уже потом решить, нужно ли продолжать программу. Мы можем съэмулирвать в Питоне оператор цикла do-while, используя цикл while True и оператор break. # do - while эмулятор num = О yesNo = "" while True: print("Type a number:") # введите число num = int(input ()) print("The square of", num, "is", num * num) # печать квадрата print("Continue? (y/n)") # продолжить? yesNo = input() if yesNo == "n": break Рис. 1-8-9: do-while эмулятор. Урок 9. Исключения Воьзмем для примера суперлростую программу (Adder), которая суммирует два числа (англ add — складывать) nl = О п2 = О пЗ = О chars = "" print("*** Welcome to Adder Арр! ***") # ДоОро пожаловать print ("nl: ", end="") chars = input() nl = int(chars) print ("n2: ", end="") chars = input () n2 = int(chars) n3 = nl + n2 print (nl, " + n2, n3) Рис. 1-9-1: Adder версия 1 — код. 38
*** Welcome to Adder App! *** nl: -99 n2: 100 -99 + 100 = 1 »> Рис. 1-9-2: \dder версия 1 — тест. И все бы хорошо... но что если пользонатель совершит ошибку и введет не цифровой символ? *** Welcome to Adder Арр' *** nl: а Traceback (most recent call last): File "E:\EVERYTHING\Courses\Python\PythonBook\partl\l esson09 exceptions\adderO.py”, line S, in <module> nl = int(chars) ValueError: invalid literal for int() with base 10: 'a’ »>l Рис. 1-9-3: Adder версия 1 —аварийное завершение. Так выглядят исключения. Вы видите что программа стартовала нормально, но аварийно завершилась на строке 8, из-за неверного параметра для библиотечной функции int () (посмотрите тему «Кастинг» в уроке 3). Все исключения в Питоне имеют стандартные имена, в данном случае это ValueError. Определение: исключения — экстремальные ситуации, которые могут произойти во время работы программы. Если исключение не предусмотрено и не обработано программистом, программа завершается аврийно Говоря об исключениях мы используем терминологию «бросать-ловить» (англ, throw-catch). Если исключение случилось, оно выброшено или возбуждено (англ, raised) системой выполнения Питона; если онс обработано программистом, оно поймано. Исключения — достаточно обьемная тема, этотурок обьяснит самые основные концепции 39
SQL PYTHON JAVA PHP HOWTO W3.CSS С C+* C# BOOTSTRAP REACT Built-in Exceptions The table below shows built-m exceptions that are usually raised in Python: Exception Description Arithmetic Error Raised when an enor occurs in numeric calculations Ааай’учТчр' Raised when an assert statement fails AttcibuteErair Raised when attnbute reference or assignment fails Exception Base class for all exceptions EOF Г rror Raised when the input*) method hits an “end of file" condition (ЕОГ) FloatingPomtError Raised when a floating point calculation fails GeneialoiExil Raised when a generaloi is closed (with the dose() method) Impprtt rrgi Raised when an imported module does not exist Indents tjsmEfipr Raised when indentation is not correct InaexErroi Raised when an index of a sequence does not exist Ke/I no' Raised when a key does not exist in a dictionary Keyboardinterrupt Raised when the user presses Ctrl+c, Ctil+z or Delete LookupError Raised when errois raised . ant be found Рис. 1-9-4. Начало списка имен исключений Итона на сайте M3schools.com Исключения названы встроенными (англ built-in) в этой таблице, поскольку прграммисты могут тоже создавать свои собственные исключения. Для обработки исключений мы используем составной оператор try-except; он состоит из двух блоков 1) потенциально опасный («риске ванный») блок, котороый может выбросить исключение, 2) блок, который ловит, то есть обрабатывает исключение, Формат оператора try-except: try: "рискованный" блок except [имя_ исключения] : блок обработки исключения Итак, если исключение выбрасывается, работает блок except, где мы его лев.тм; если ничего плохого не происходит, блок except пропускается и мы переходит на строчку после оператора try-except, Имя исключения не обязательно, если мы его не указываем, мы реагируем на все исключения, если указываем, только на это конкретно взятое исключение 40
Логика оператора try-except похожа на логику оператора if. Но логика if проще: мы проверяем условие и действуем соответственно, try-except логика сложнее, поскольку мы не знаем точно, которая строка в блоке вызвала проблему (если, конечно, в блоке более одной строки). Если мы вкладываем операторы try-except логика еще более усложняется. Рис. 1-9-5. Логика оператоар try-except Исправим Adder и сделаем его более надежным. п1 = О п2 = О пЗ = О chars = "" print ("*** Welcome to Adder Арр! ***") while True: print ("nl: ", end="") chars = input() try: nl = int(chars) # "рискованная" строка break. except: print("Bad number! Try again...") # некорректное число while True: print("n2: ", end="") chars = input () try: n2 = int (chars) # "рискованная" строка break except: print ("Bad number! Try again...") n3 = nl + n2 print (nl, " + n2, " = ", n3) Рис. 1-9-6. Adder версия 2 — код. 41
Мы заранее понимаем, что конвертация строки в число рискованна и может обрушить программу, поэтому вы помещаем функцию int () внутрь блока try. Если конвертация прошла хорошо, мы вываливаемся из цикла; если нет, мы не доходим до оператора break, мы перепрыгиваем из блока try в блок except, печатем сообщение и повторяем итерацию цикла еще раз, спрашивая пользователя ввести число *** Welcome to Adder Арр’ *** First number: abodef Bad number1 Try again... First number: 12 Second number: -0-0-0- Bad number! Try again Second number: 13 12 + 13 = 25 »> Рис. 1-9-7. Adder версия 2 - test. Обратите внимание, что строка except могла бы быть более конкретной- except ValueError: ValueError — имя исключения, которое может быть выброшено во время конвертации в число. Это значит, что мы ловим только исключения конвертации и игнорируем остальные. Иногда нам нужна альтернативная нетвь выполнения для "хорошего" сценария. Мы можем добавить к оператору try-except блок else; оператор try-except-else с остоит из трех блоков 1) потенциально опасный (рискованный) блок, который может выбросить исключение, 2) "плохой" сценарий, исключение поймано, 3) "хороший" сценарий, исключения не случилось. Формат оператора try-except-else: try: "рискованный" блок except [exception name]: блок плохого сценария else: блок хорошего сценария Как »ы е идите, логика оператора try-except-else похожа на логику оператора if-else. Блоки except и else являются взаимно исключающими. 42
Рис. 1-9-8. Логика оператора try-except-else. Перепишем Adder используя оператор try-except-else. # variables nl = О n2 = О пЗ = О chars = "" print("*** Welcome to Adder Арр! ***") while True: print("First number: ") chars = input() try: nl = int(chars) # "рискованная" строка except ValueError: print("Bad number! Try again...") # некорректный ввод else: break while True: print("Second number: ") chars = input () try: n2 = jnt (chars) # "рискованная" строка except ValueError: print("Bad number! Try again...") else: break n3 = nl + n2 print (nl, " + n2, n3) Рис. 1-9-9. Adder версия 3 - код. Мы можем несколько сократить программу посредством обьединения двух циклов и двух операторов обработки исключений. 43
# variables nl = О n2 = О n3 = О chars = "" print("*** Welcome to Adder Арр! ***") while True: try: print("nl: H, end="") chars = inputO nl = inc(chars) # "рискованная" строка print("n2: ", end="") chars = inputO n2 = int(chars) # "рискованная" строка except VaiueError: print("Bad number! Try again...") # некорректный ввод else: break n3 = nl + n2 print (nl, " + n2, " = ", n3) Рис. 1-9-10. Adder версия 4 - код. Однако заметим, что в этой версии есть один недостаток: если первый ввод был корректен, а гторой нет, то нам все равно нужно опять вводить оба числа Мы также можем добавить блок finally к оператору try-except-else. То есть оператор try-except-else-f inally состоит из четырех блоков: 1) потенциально опасный ("рискованный") блок, который может выбросить исключение, 2) плохой сценарий, исключение выброшено и поймано, 3) хороший сценарий, исключения не было, 4) блок, выполняющийся после 2-го и 3-го -<не завсимости оттого, было выброшено исключение или нет. Урок 10. Функции Основные термины Определение: функция — поименованный набор операторов многоразового применения. Функции создаются программистами под обработку конкретных задач Поскольку одна и та же функция может применяться много раз, функции делают код короче, более структурированным, более легким для понимания и модификации. Функции необходимы для сведения решения сложной задачи к решению более простых задач. Рассмотрим пример (англ, greeting — приветствие). 44
1 # определение функции(строки 2-5) 2 def 3 4 5 greeting(): # заголовок функции print("************") # тело функции (строки 3-5} print("Hello John Brown'") print("*****★***★**") 6 7 greeting!) # 1-й вызов функции greeting() # 2-й вызов функции 9 greeting() # 3-й вызов функции 10 print("Main program is completed") 11 # Порядок выполнения: 734583459345 10 Рис. 1-10-1. Пример функции — код. ************ Hello John Brown! ****** ****** ************ Hello John Brown! ************ ************ Hello John Brown! ************ Main program is completed »> Рис. 1-10-2. Пример функции — тест. Важно понимать, что функция сначала должна быть подготовлена. Этот шаг называется определение (англ, definition) функции. Затем мы можем использовать функцию. Этот шаг называется вызов (англ, coll) функции. Определение функции состоит из заголовка и тела. Заголовок определяет имя функции, тело определяет, что собственно функция должна делать Все операторы в теле печатаются с отступом Определение функции должно быть сделано перед ее первым вызовом Вышеприведенная программа выполняется в следующем порядке • строка 7 (начало работы программы, поскольку определение фунции пропускается, 1-й вызов функции) • строки 3,4, 5 (функция отрабатывает, три сообщения печатаются) • строка 8 (2-й вызов функции) • строки 3,4, 5 (функция отрабатывает, три сообщения печатаются) • строка 9 (3-й вызов функции) • строки 3,4, 5 (функция отрабатывает, три сообщения печатаются) • строка 10 (завершение головной программы) Перед любым вызовом функции компьютер запоминает текущую точку выполнения программы. После отработки функции происходит возврат на следующий оператор после вызова функции Внутри одной функции вполне может быть вызов другой функции То есть в реальных приложениях мы имеем большое количество связанных функций, вызывающих друг друга. 45
Для упрощения дальнейшего обсуждения будем придерживаться следующей терминологии • вызывающая функция -родительская функция, • вызываемая функция - дочерняя функция. В вышеприведенном примере головная программа является родительской функцией, greeting () — дочерней. В некоторых языках программирования, например, в C/C++ или Яве, головная программа должна быть формально определена как функция. Питон этого не требует, но оставляет на усмотрение программиста. Параметры и аргументы Довольно часто родители не только просят детей выполнить какое-то задание, но и объясняют дополнительно некоторые детали. Мы можем передавать в функцию дополнительную информацию через параметра!. Таким образом функции могут быть более гибкими и мощными Рассмотрим пример. def greeting(firstName, lastName): print (’’************’’) print("Hello ", firstName, " ", lastName, "!", sep="") greeting("John", "Lennon") # Члены группы "Битлз" greeting("Paul", "McCartney") greeting("George", "Harrison") greeting("Pingo", "Starr") Рис. 1-10-3: Функция с двумя параметрами — код. ************ Hello Lennon John! ************ Hello Paul McCartney! k+t-k-k-k-k'k-k-k'k-k-k Hello George Harrison! ************ Hello Ringo Starr! Рис. 1-10-4: Функция с двумя параметрами —тест. firstName (имя) и lastName (фамилия) в определении функции — это параметры, параметры являются «заполнителями» (англ placeholder) в том смысле что позже, когда функция будтет вызвана, они будут заменены (или «заполнены») аргументами. Аргументы это переменные, литералы или выражения которые будут скопированы в параметры, когда функция будет вызвана. То есть, имена и фамилии четырех знаменитых музыкантов — аргументы. Вы видите, что функция стала более гибкой, она может печатать любые имена и фамилии, не толькоЧобп Brown" как раньше 46
Важно понимать разницу между формальными параметрами и фактическими аргументами. Параметр это всегда переменная, он не может быть литералом или сложным выражением, но аргумент — вполне и очень часто Например, первый аргумент здесь — выражение, второй — строковый литерал: greeting("Dear " + "John", "Lennon") Аргументы с ключевыми словами и аргументы по умолчанию При вызове функции можно предварять аргументы именами параметров со знаком "=". Например: greeting(firstName="John", lastName="Lennon") Да, код немного длинней, нс зато более понятный. Интересно, что используя этот механизм (аргументы с ключевыми словами), можно передавать аргументы в произвольном порядке, и они все равно будут правильно обработаны. greeting(lastName="Lennon", firstName="John") Вы также можете определять для функции аргументы по умолчанию, то есть когда аргумент опущен, подставляется значение по умолчанию. def greeting (firstName="???", last.Narne="???") : print ("**************) print("Hello ", firstName, " ", lastName, "1", sep="") greeting() # по умолчанию печатаются вопросительные знаки Рис. 1-10-5. Функция с параметрами по умолчанию — код. Hello ??? ???» »> | Рис. 1-10-6: Функция с параметрами по умрлчанию —тест. И аргументы с ключевыми словами и аргументы по умолчанию интенсивно используются при программировании GUI (графический пользовательский интерфейс) приложений с библиотекой tkinter (часть 3) Аргументы с ключевыми словами делают код понятней. Аргументы по умолчанию позволяют значительно сокращать вызовы библиотечных функций. Преждевременный выход Довольно часто в силу определеннных обстоятельств продолжать работу функции нет смысла, нам нужно «вывалиться» раньше времени. В таком случае нам нужен оператор return. Он немедленно завершает выполение функции, и мы переходим к следующему за вызовом функции оператору Например допустим, что мы хотим печатать приветствие только тогда, когда аргументы не пусты. 47
def greeting(firstName, lastName): print ("************") if firstName == print ("No first name provided...") # нет имени print("Function terminated") # функция завершается return if lastName == print("No last name provided!") # нет фамилии print("Function terminated") # функция завершается return print("Hello ", firstName, " ", lastName, "!", sep="") greetingC", "Lennon") greeting("Paul", "") greeting("George", "Harrison") greeting("Ringo", "Starr") Рис. 1-10-7. return демо— код. ************ No first name provided... Function terminated No last name provided! Function terminated *1*****^Иг**1г Hello George Harrison! ±±***±±***** Hello Ringo Starr! Рис. 1-10-8. return демо — тест. Возвращаемые значения Если родителю разрешено говорить, почему ребенку нельзя ответить? Аргументы служат для передачи данных в функцию, возвращаемые значения — для передачи данных из функции. Функция может вернуть в вызывающую функцию: число, строку, булевую переменную, во1ражение. Это достигается через оператор return. Рассмотрим пример: def average(numl, num2): sumOfTwo = numl + num2 averageOfTwo = sumOfTwo / 2.0 return averageOfTwo print(average(5.0, 3.5)) markl =90.5 # global variable mark.2 = 67.5 # global variable averageMark = average(markl, mark2) print(averageMark) # два параметра # локальная переменная sumOfTwo # локальная переменная averageOfTwo # возвращаемое значение Рис. 1-10-9. Возвращаемое значение— код. 48
4.25 79.0 Рис. 1-10-10. Возвращаемое значение — тест. Как ры видите, если функция возвращает результат своей работы, «ы можете использовать ее вызов с правой стороны оператора присваивания. То есть родитель сохранил результаты работы ребенка: пЗ = average(nl, п2) Вы также можете использовать возвращаемое значение внутри выражений: print(average(nl, n2) * 10G.0 + "%") Аргументы ► Возвращаемое значение + Ребенок Рис. 1-10-11. Коммуникация между вызывающей (родитель) и вызываемой (ребенок) функциями. Как мы продемонстрировали, родительская функция может передать несколько аргументов, но ребенок возвращает только одно значение. Это ограничение, однако, можно преодолеть используя контейнеры (тема следующего урока). Оба типа коммуникации (от родителя к ребенку и наоборот) являются опциональными. Да, функции могут иметь параметры и возвращаемые значения, но не обязаны Локальные и глобальные переменные Еще одно нажное замечание о программе выше Переменные определенные внутри функций называются локальными, переменные определенные вне функций называются глобальными. Локальные переменные атоматически получают память при запуске функции, и атоматически удаляются из памяти при завершении функции. Есть смысл ссылаться к ним только внутри функции, где они определены. Не пытайтесь адресовать локальные переменные извне функции! Определение: стек — динамическая область памяти. Организована по принципу LIFO (англ, «last in, first out», «последний пришел, первый вышел»). Стек используется для хранения локальных переменных Также когда функция вызывается, адрес следующего за ней оператора запоминается на стеке, когда функция завершается, родительская функция извлекает этот адрес со стека и продолжает корректно выполняться. 49
Модули Функции — мощный инструментарий для деления кода на логические единицы. Однако важно уметь делить код не только логически, но и физически Решение проблемы — сохранение функций f отдельных файлах. Мы называем такие файлы модули Если этого не делать, то в реальных приложениях голодная программа рано или поздно иырасгет до астрономических размеров соген или тысяч строк. В следующем примере мы сохранили три функции в файле helper .ру. Головная программа подключает модули через директиву import Обратите внимание, теперь для вызова функции нужно имя модуля как префикс, затем точка и имя функции. def addition(numl, num2): return numl + num2 def product(numl, num2): return numl * num2 def average(numl, num2): return (numl + num2) / 2.0 Рис. 1-10-12. Модуль helpers .ру состоит из трех функций. import helpers print(helpers.addition(1, 2) ) print(helpers.product(1, 2)) print(helpers.average(1, 2)) Рис. 1-10-13. Головная протрамма — код. 3 2 1.5 »> Рис. 1-10-14. Головная протрамма — тесг. Если печатание имени модуля как префикса становится утомительным, то есть упрощение Используйте директиву import со звездочкой. from helpers import * Таким образом при вызове функций имя модуля как префикс не понадобится. Пользовательские функции, библиотечные функции и обработчики событий Под пользовательскими функциями мы понимаем функции созданные непосредственно вами Однако множество полезных функций Питона уже написаны для вас, чтобы сэкономить ваше драгоценное время. Вам нужно просто знать, как их правильно вызывать. Мы называем их библиотечные функции 50
Примеры: • print () • input () • open () Многие библиотечные функции сгруппированы в системные модули То есть для их подключения к проекту нужна директива import. Тогда для их вызова может понадобиться как префикс имя модуля, например: sys. exi t (). Определение; обработчик события — функция определяемая программистом, но вызываемая Операционной Системой (ОС) в случае возникновения определенных событий. Нам не требуются обработчики событий при написании простых консольных приложений Но как только вы начинаете писать реальные программы с графическими пользовательскими интерфейсами (англ. GUI), обработчики событий становятся суперважными. Например, вы можете определить обработчик события для кнопки. Если пользователь щелкнул, операционка вызывает обработчик, и вы можете туда вложить нужный код связанный со щелчком мыши Обработчики событий будут рассмотрены в третьей части книги Тип Кто написал? Кто вызвал? Пользовательская Вы Вы Библиотечная «Кто-то» Вы Обработчик события Вы «Кто-то» Рис. 1-10-15. Три типа функций. Урок 11. Контейнеры данных Набор стандартных операторов в Питоне сильно ограничен. Функции обьединяют операторы, и, в каком-то смысле, функции являются вашими собственными супер-операторами, которые создаются под конкретные задачи. Аналогично, мы имеем в Питоне только четыре примитивных типа переменных. Кроме того, переменные имеют очень маленькую вместимость Контейнеры позволяют обьединять переменные, являясь своего рода супер- переменными. Разберем основные контейнеры в Питоне: • Список (англ, list) • Кортеж (англ, tuple) • Словарь (англ, dictionary) 51
Списки (англ, lists) Определение.: список — набор пронумерованных переменных. Все переменные "шерят" одно и то же имя, а доступ к различным переменным осуществляется через их индексы в квадратных скобках. Индексы нумеруются с нуля. Частая ошибка начинающих — полагать, что если в списке, например, 10 элементов, то и номер последнего будет 10. Это неверно, номер последнего будет 9! numbers = [1, -2, 3] print("item 0:", numbers[0]) print("item 1:", numbers LI]) print("item 2:", numbers[2]) print("How many items?", len(numbers)) Рис. 1-11-1. Список демо— код. item 0: i item 1: -2 item 2: 3 How many items? 3 Рис. 1-11-2. Список демо — тест. В первой строке вы видите как создать и проининциализировать список. Популярная библиотечная функция len () в качестве аргумента берет имя списка и возвращает количество элементов в списке. Если список пуст, возвращается ноль. Посмотрите, как можно создать и инициализировать нулем большой список- numbers = [0] * 100 print("item 0:", numbers|0]) print("item 1:", numbers[1]) print("item 99:", numbers[99]) print("Hew many items?", len(numbers)) # сколько элементов? Рис. 1-11-3. Большой список — код. item 0: 0 item 1: 0 item 99: 0 How many items? 100 Рис. 1-11-4. Большой список — тест. Если вам неизвестен заранее размер и содержимое списка, вы можете создать пустой список и добавлять (англ — append) элементы в конец списка динамически, во время работы программы. Также возможно динамическое удаление (англ — remove). 52
numbers = [] print("Before append: ", end="") # до добавления print(numbers) for i in range(1, 11): # цикл от 1 до 10 numbers.append(i) print("After append: ", end="") # после добавления print(numbers) numbers.remove(5) print("After remove: ", end-"") print(numbers) I’nc. 1-11-5. Динамическое добавление и удаление элементов списка — код. Before append: After append: After remove: [J [1, [1, 2, 3, 2, 3, 4, 4, 5, 6, 7, 6, 7, 8, 8, 9, 10] 9, 10] Рис. 1-11-6. Динамическое добавление и удаление элементов списка — тест. Обратите внимание, библиотечная функция remove () удаляет из списка указанный как параметр элемент, если в списке несколько одинаковых элементов, удаляется первое вхождение. Если такового элемета не оказалось, выбрасывается исключение ValueError (см часть 1 урок 9) Другая полезная функция называется pop () и она удаляет елемент на определенном индексе. Как вы видите, функция print О довольная гибкая, и может печатать не только переменные, литералы и выражения, но также списки. Элементы списка не обязаны быть одного типа mixed = [0, "0", False] # смешанный список Питон располагает разнообоазными методами манипуляции списков: append (), extend (),insert (),count(), remove(), clear(), copy(), index (), pop(), remove (), reverse (), sort () и т. д. Методы — это более высокоуровневые функции и будут разобраны во 2-й части книги (ООП). Словари (англ, dictionaries) Если в списке элементы пронумерованы я словаре — поименованы Словарь это набор пар ключ-значение. Ключи это всегда строки, но значения могут быть любого типа. 3 следующем примере имена стран — это ключи, численности населений — значения. 53
populations = {"Canada": 37000000, "UK": 66000000,\ "USA": 327C00000} print("Canada -", populations["Canada"]) print("UK , populations["UK"]) print("USA populations["USA"]) populaticns2 = {} # пустой словарь создан populations2["Canada"] = 37000000 # добавление элементов populations2["UK"] = 66000000 populacions2["USA"J = 327000000 print("Canada populations2{"Canada"]) print("UK populations2["UK"]) print ("USA populations2["USA"]) Рис. 1-11-7. Словарь — код. Canada - 38000000 UK - 67000000 USA - 329000000 Canada - 37000000 UK - 66000000 USA - 327000000 Рис. 1-11-8. Словарь — тест. Как мы видим, можно создать и сразу же инициализировать словарь, либо (альтернативный вариант) можно создать пустой словарь и динамически добавлять в него пары ключ- значение. Все ключи в словаре должны быть уникальны. Кортежи (англ, tuples) Кортежи похожи на списки, но они не могут быть изменены после создания Кортежи обрабатываются быстрее чем списки, они более эффективны. marks = (66.5, 70.0, print(marks[0 J) print (marks[1|) print(marks[2 J) 88.3) Рис. 1-11-9. Кортежи — код. 66.5 70.0 88.3 »> Рис. 1-11-1U. Кортежи — тест. Операция звездочка Иногда нам нужен контейнер как целое, иногда только значения из этого контейнера. Во втором случае мы можем использовать с контейнером операцию * (звездочка); она как бы очищает оболочку контейнера и дает нам только его содержимое. 54
container = (1, 2, 3) print(container) print(‘container) »> (1, 2, 3) 12 3 Рис. 1-11-11. Операция звездочка — код. Рис. 1-11-12. Операция звездочка — тест. Контейнеры — важные факты для запоминания: • Списки создаются, с использованием квадратных скобок, • словари — фигурных, • кортежи — круглых. Урок 12. Контейнеры и циклы Контейнеры в реальном программировании могут состоять из сотен или даже тысяч элементов, чтобы сканировать и обрабатывать списки, словари или кортежи нам нужны циклы. Обратите внимание однако, что операторы for and whi 1е универсальны и могут использоваться для любых повторяющихся задач, не только для обработки контейнеров Следующая программа показывает как обрабатывать списки используя цикл for: myGrades = [60.0, 70.0, 80.0, 90.0] # мои оценки print(myGrades) print() howMany = len(myGrades) # сколько? for i in range(howMany): # №1 сканированиепо индексам print (myGrades[i]) print() for grade in myGrades: # №2 сканирование по значениям print(grade) print () for i in range(howMany): # №3 модификация по индексам myGrades[i] += 5.0 print(myGrades) for grade in myGrades: # №4 модификация по значениям (не работает!) grade += 5.0 print(myGrades) Рис. 1-12-1. Обработка списка в цикле for — код. 55
Мы показываем здесь два способа обработки списка первый через индексы, второй через значения (игнорируя индексы), Первый метод наиболее универсальный, потому что вы можете и считывать и модифицировать элементы списка Маленькое неудобство в том, что нужно подсчитать сначала общее количество элементов. Второй метод проще, не нужно подсчитывать, но он более ограничен, вы имеете доступ только на чтение, нет возможности модифициорвать элементы списка. В четвертом цикле вы модифицируете переменную grade, но не сам элемент списка. [60.0, 70.0, 80.0, 90.0] 60.0 70.0 80.0 90.0 60.0 70.0 80.0 90.0 [65.0, 75.0, 85.0, 95.0] [65.0, 75.0, 85.0, 95 0] »> Рис. 1-12-2. Обработка списка в цикле for — test. Вы можете конечно использовать со списками и цикл whi 1 е, но тогда придется самому устанавливать и обновлять индекс i Возможно цикл for для списков более естественен. myGrades = [60.0, 70.0, 80.0, 90.0] howMany = len(myGrades) i = О while i < howMany: myGrades[i] += 1 i +- 1 Рис. 1-12-3. Обработка списка в цикле while. Следующий пример показывает обработку словаря в цикле Словарь myGrades хранит оценки студента по трем разным предметам программиросания myGrades = {"C++":60.0, "Java":70.0, "Python":80.О} print(myGrades) for lang in myGrades: # цикл по ключам myGrades[lang] +=5.0 # и обновление значений print(myGrades) Рис. 1-12-4. Обработка словаря в цикле for — код. {'C++': 60.0, 'Java': 70.0, 'Python': 80.0} {'C++': 65.0, 'Java': 75.0, 'Python': 85.0} »> Рис. 1-12-5. Обработка словаря в цикле for —тест. 56
Итераторы Для доступа к элементам в контейнере мы конечно же используем их индексы (число — для списков и кортежей, строка — для словарей). Тем не менее, существует альтернатива Иногда имеет смысл получить указатель на первый элемент, обработать первый, затем сдвинуть указатель на торой, обработать второй, ипять сдвинуть, опять обработать. . и т. д. То есть мы получаем доступ к данным в контейнере через динамический указатель, он называется итератор. Продемонстрируем использование итераторов. myLisc = [1, 2, 3, 4, 99] iterator = iter(myList) try: while True: print(next(iterator) ) except Stopiteration: print ('‘LIST DONE!\n”) myDict = {"First": 1, "Second": 2, "Third": 3, "Fourth": 4, "Fifth": 99} iterator = iter(myDict) try: while True: key = next(iterator) print("Key:", key, "| Value:", myDict[str(key)]) except Stopiteration: print("DICTIONARY DONE!") Рис. 1-12-6. И<ераторы — код. Сначала мы создали список. Используя библиотечную функцию Питона iter () мы инициализировали итератор. Библиотечная функция next () делает две вещи • возвращает указатель на первый елемент, • сдвигает указатель на второй элемент. То есть, используя next () в цикле, мы можем просканировать весь контейнер Когда мы достигаем конца, выбрасывется исключение Stop!teraion (см урок 9), что означает обработка закончена. Затем мы создали словарь В этом случае next () дает нам не следующее значение, а следующий ключ. Мы вытаскиваем следующее значение через кастинг ключа в строку, и индексирование 57
>» 1 2 3 4 99 LIST Key: Key: Key: Key: Key: DONE' First | Second Third | Fourth Fifth | Value: 1 I Value: 2 Value: 3 | Value: 4 Value: 99 DICTIONARY DONE' Рис. 1-12-7. Итераторы —тест. Урок 13. Строки Определение: строка — набор символов («кусок» текста). Все символы в строке проиндексированы. В некотором смысле строки тоже контейнеры Строки имеют сходство со списками или кортежами Например, первый символ строки myString — myString [С]; библиотечная функция len () также применима к строкам Питон не имеет специального типа данных для отдельных символов, как C/C++/C# или Ява Это значит, что тип myString [N] тоже str; то есть это строка, состоящая из одного символа. Важно знать что строки immutable (рус. - неизменяемы), то есть они не могут изменятся после того как созданы. Например, этот оператор ошибочен: myString|0] = "а" Однако следующее корректно myString = "аааа" myString = "obtobbb" Как такое возможно, если строки неизменяемы? Ответ состоит в том, что перед вторым присваиванием Питон удаляет из памяти строку "аааа", затем распределяет память под совершенно новую строку "bbbbbb", и сохраняет адрес этой новой строки в той же перемнной myString. 58
Покажем, как пройти строку в цикле символ за символом. Первый раз делаем это через индексы, второй раз через значения str = "abed" howManyChars = Len(str) # сколько символов? for i in range (0, howManyChars): print (str[i]) for ch in str: print(ch) Рис. 1-13-1. Обработка строки в цикле for — код. а Ь с d а Ь с d »> Рис. 1-13-2. Обработка строки в цикле for — тест. Наиболее распространенные операции на строках (см. урок 4): • Конкатенация (к) • Сравнение (>, >=, с, <=, ==, ! =) Питон имеет также богатый набор библиотечных функций, которые работают со строками: расчленение, поиск, преобразование к нижнему или верхнему регистру и много других. Мы называем эти функции методами, и используем с символом точка: имяСтроки. имяМетода (...) Методы — это более продвинутые {обьекто-ориентированные) функции. Мы обсуждаем обьектно-ориентрованное программироваине (ООП) во второй части книги. ft HTML CSS JAVASCRIPT SQL PYTHON PHP BOOTSTRAP HOWTO W3XSS JAVA JQUERY C« Method Description MySQ. Кип Python MongoDB MongoDB Get Started MongoDB Create Database MongoDB Create Cotecbon MongoDB -ввел MongoDB ftnd MongoDB Query MongoDB Sort MongoDB Delete MongoDB Drop Collection MongoDB Update MongoDB Unit caoitahzeti Converts the first character to upper case easefoldf) Converts suing into lower case centerp Returns a centered suing ИшдйО Returns the number of twnes a specified vatoe occurs in a suing encoded Returns an encoded version of the String ends wit h() Returns true if the string ends with the specified value excandtabsO Sets the tab size of the stnng ftndQ Searches the suing for a specified value and returns the position of where it was found format/) Formats specified values m a suing Python Reference Python Overwcw Python В uJt-in Functions formaCmapO Formats specified values in a suing ndext 1 Searches the stnng for a specified value and returns the position of where it was found Python Stnng Methods is a I non7) Returns True it all characters <i the string are alphanumeric Рис. 1-13-3. Список методов Питона работающих со строками на сайте M3chuoh.com 59
Урок 14. Двумерные списки Любой элемент контейнера может быть в свою очередь контейнером Возможность вкладывать контейнеры делает этот инструментарий действительно мощными Вы можете представлять себе список как линейную последовательность переменных, где каждая переменная имеет уникальный позицию, смешение, индекс. Рис. 1-14-1. Одномерный список. Индекс Однако очень часто нам нужно обрабатывать табличные структуры, где каждая переменная имеет номер строки и номер колонки Колонка Строка 99 -5 10 2 ... 12 0 3 1 ... 0 -1 10 12 ... Рис. 1-14-2. Двумерный список Здесь нам могут помочь двумерные (англ 2D, «ту-ди») списки. 2D список в Питоне имплементирован как «список списков», другими словами, как одномерный список одномерных списков. my2DList = [ [1, 1, [2, 2, [3, 4, [11, 12, print(my2DList) print(my2DList [1]) print(my2DList [3] [2]) 1] , \ 2] , \ 5] ,\ 13] ] Рис. 1-14-3. 2D список — код. [[1, 1, И, [2, 2, 2] 13 [2, 2, 2], [3, 4, 5], [11, 12, 13]] Рис. 1-14-4. 21) список— тест. 60
Как видите, мы создали и проинициализировали 2D список. Верхняя строка имеет нулевой номер и номера строк увеличиваются, если двигаться вниз, самая левая колонка имеет нулевой номер, и номера колонок увеличиваются, если двигаться ьправо. Мы можем использовать имя двумерного списка без квадратных скобок, тогда мы ссылаемся к 2D списку целиком, мы можем использовать имя 2D списка с одним индексом в квадратных скобках, тогда мы ссылаемся к одной строке, которая является одномерным списком, или (наиболее часто) мы используем имя 2D списка с двумя индексами в квадратных скобках, тогда мы адресуем только один элемент в определенной строке и определенной колонке. Обрабатывая двумерные списки, используйте сначала номер строки, потом — номер колонки! Если мы можем двигаться через одномерный список используя циклы for или while, то для того чтобы пройти двумерный список, нам понадобится вложенные циклы for или while. my2DList = [[1, 2, 3, 4],\ [21, 22, 23, 24],\ [31, 32, 33, 34]\ ] print("Before, my2DList) # перед rows = len(my2DList) # сколько строк? cols = len(my2DList[0]) # сколько колонок? for r in range(rows): for c in range(cols): my2DList[r][c] += 1 print("After:", my2DList) # после Рис. 1-14-5. 2D список во вложенном цикле — код. Before: [[1, 2, 3, 4], [21, 22, 23, 24], [31, 32, 33, 34]] After: [[2, 3, 4, 5], [22, 23, 24, 25], [32, 33, 34, 35]] »> Рис. 1-14-6. 2D список во вложенном цикле — тест. Вы можете создать и инициализирсвать 2D список определенным значением используя символ «звездочка»: myBig2DList = 1(0.1] * 10] * 10 2D список может быть не обязательно «прямоугольным», когда все строчки имеют одну и ту же длину. Он может быть т.н. «неровным», «с выступами» (англ, "jagged"). 61
Column Row 99 -5 10 2 12 0 3 0 -1 10 12 15 Рис. 1-14-7. «Неровным» 2D список. jaggedList = [[1, 1], [2J, [3, 3, 3], [4, 4, 4, 4, 4, 4]] print(jaggedList) rows = len(jaggedList) # сколько строк? for r in range (r _>ws) : cols = len(jaggedListLi]) # сколько колонок в строке? for с in range(cols): jaggedList[r][c] += 1 print(jaggedList) Pnc. 1-14-8. Обрабо1ка «неровною» 2D списка — код. [[1, 1], [2], [3, 3, 3] , [4, 4, 4, 4, 4, 4]] [[2, 2], [3], [4, 4, 4], [5, 5, 5, 5, 5, 5]] »> Рис. 1 -14-9. Обработка «неровною» 2D списка — тест. Урок 15. Файлы и потоки Внешняя память Все типы данных, которые мы обсудили на данный момент (переменные и контейнеры) хранятся в оперативной памяти (RAM). Оперативная память работает чрезвычайно быстро, но имеет очевидный недостаток — она функционирует пока компьютер подключен к источнику питания. Вторая проблема — вместимость, размер современного софта, мултимедиа файлов, баз данных огромен, они часто просто не помещаются в «оперативку». Поэтому компьютеры имеют различные устройства внешней памяти встроенный жесткий диск и внешние жесткие диски, флэшки, SD-карты, оптические диски. Помимо этого, вы можете использовать через локальную сеть внешнюю память других компьютеров. В операционной системе Windows устройствам внешней памяти назначаются буквы алфавита: например, жесткий диск это с:, оптический диск — d:, флэшка — е: и т. д. Mac OS и Linux вместо букв используют логические, более осмысленные имена, например. "Macintosh HD" (жесткий диск) или "USB_DATA" (флэшка). 62
Поскольку устройства внешней памяти очень большие в смысле обьема хранимой там информации, данные на них организованы в папках (или каталогах) и файлах. Папки это нечто вроде ящиков, где хранятся файлы; папки не содержат непосредственно данных; папки могут иметь подпапки (подкаталоги), а эти подпапки в свою очередь могут иметь другие подпапки и т. д. Непосредственно данные хранятся в файлах. Любая папка и файл имеют имя В файлах могут храниться разные типы данных фото, видео, музыка, документы, таблицы и т. д. Поэтому имя файла может иметь расширение: 3 или 4 буквы после точки, говорящие нам какой тип данных сохранен в данном файле. Например, фото обычно имеют расширение . jpeg, вордонские докумены — . docx, текстовые файлы — . txtnr д. Дерево папок на устройстве внешней памяти может быть огромным Многие папки и файлы созданы ОС для различных приложений Чтобы отделить данные программ от пользовательских данных, в системе существуют несколько стандартных, заранее созданных папок для индивидульного пользования: Documents, Pictures, Music и т д. Если программисту требуется определить точно местоположение файла на внешнем устройстве, нужен т.н. путь: последовательность подпапок которые выводят нас на папку, где файл находится. Путь может быть: • абсолютным • относительным Абсолютный путь начинается ст н. корневой папки Корневая папка — это имя устройства внешней памяти. Формат абсолютного пути; {drive name}It older1/folder2/folderN Например, резюме, сохраненное на жестком диске, может иметь такой абсолютный путь: с:/Users/John/Documents/my resume.docx. Относительный путь начинается с текущей папки. Текущая папка это папка, где мы сохранили нашу программу на Питоне Например, другая прогамма на Питоне, сохраненная где-то в другой папке, могла бы иметь такой относительный путь. ./../chapter2/lessonl0/another_python program.ру. Когда мы определяем относительный путь, мы используем символы точка (текущая папка) и двойная точка (родительская папка). Сточки зрения программиста файлы могут быть • текстовыми, * бинарными. 63
Главным образом, текстовые файлы служат для людей, они состоят из строк, которые состоят их понятных людям символов, это не означает, однако, что компьютер не может обрабатывать текстовые файлы. Бинарные файлы нельзя «читать», они состоят из байт, Бинарные файлы обрабатываются компьютерами и программистами, создающими компьютерные программы; они содержат «сырые» данные. Если вы попытатесь открыть бинарный файл в простом редакторе текста (Notepad или Notepad++), вы увидите «мусор». I. EC Jf Notepad •• — □ X Кй Sewch View Erxodwvg Tod* M«t» ftan Мирен WiiMow ? ♦ ♦ X „(не . .a *O'B» aV»«iaein• ваше* 1 |ID 1ЯЖ1 а 1няишр15та55гагпя1 |1э№!19ГС№(Вг!! $&) , .1368; =0BEGJLOQTWY\Aadfikr kilafclWal j§. 1ЯШЙ1 _______ XjAu...ldO ДЛэГ-ЕП J^fHia^E>/{&+4iMiE>-YBBc • 1»М!Ынй4 gqF-&ranra, rUVrl ШэП >»«fitd ^oiryy\£U 85JW йР1Цм}щ*.?1ДИН>П1< Ъь Q ГЯЯЯПИ,05 АО А ЕЛЯ #\ ;х°ЕЙЯ5)5ё('0Я рВБ r -lUfiaГ®6£(&№ СВЗ OAwajg/RxJEOR ( >1ДИ Я5! 1ё1ЯЗУуйё УУ ДНИ Х*ЙИЯ1Р7 rAAaf,tA, ЯПЯ А4>в„А^вй (Р2€Кав/с AEoelSa 1 'Ату(XfHSl ''Ьоу2ЙПЯ 155’^®И д9„@&ЯИП1ЯЛк0ЙЕ i-X.SSU Ц ЙЙза>ЗЁ-?&®а,и-е?„дУ2 BY + U* Х -Д] hCyl ШШАШ 1ЯЗ‘Аа ЙПЙ’бААИИ ЧОТИЯЯЖПЮТЯ 1Я5Я ...@ha< 9* 11ЫЙ11 s 1 <в 1 -®< .4-Eg) bHFI чйП33тО j taa Q4Ee6d±TT»bXW« NcrenaitdlWa 902.814 taw 7,917 Ire 1 Cd 1 ₽M I MK>d«h(CRJ ANS INS Рис. 1-15-1. Бинарный файл откры) в редакторе NotepadT+. Обратите внимание, что кордовские документы — бинарные файлы, поскольку помимо текста они содержат специальные символы форматирования. Программисты обрабатывают файлы в 3 шага. 1) открытие, 2) чтение/запись, 3) закрытие Начнем с терминологии, Файлы редко считываются или записываются в один прием, поскольку размер оперативной памяти ограничен. Вместо этого мы распределяем в оперативной памяти относительно небольшую область, куда мы можем читать или откуда мы можем записывать данные порциями. Эта область называется буфер. Помимо буфера нам нужно отслеживать текущую позицию чтения/записи в теле файла. То есть во время чтения/записи исполняющая система Питона создает динамическую структуру для поддержки процесса. Некоторые детали этой структуры сокрыты от программиста, некоторые видимы. Эта структура называется поток (англ, stream). 64
Потоки — не файлы. Файлы скорее статичны. Они существуют на жестком диске, даже если компьютер выключен. С другой стороны потоки динамичны, они создаются, после того как файл открыт, они меняются во время чтения/записи, и они перестают существовать после того, как файл закрыт. Можно сказать, что потоки — это файлы в процессе обработки. Рис. 1-15-2. Файлы и потоки. Чтение Допустим, что у нас есть текстовый файл nums . txt расположенный в той же папке, что и наша прграмма на Питоне. Ц —ох На I* 5аан* Vw* Тмк Мам 1м» ГМ*га *чй» » Т X * . oU з с а". fi“a Ы-Л ПВО 71 1 - «ю Hl—w м 51_______________________________________________ 1 1111111111 2 22222222 3 33333333 И 4444 5 б 5555555555555555 ! 7 >—амМ —м ^Я1Л ЦТ, В «в Рис. 1-15-3. Текстовый файл nums . txt открытый в Notepad+t. 65
Наше первая про1рамма чтения файла: import sys try: stream = openCnums.txt") except. lOError : print("Opening error!") sys.exit () content = stream.read() for ch in content: print(ord(ch), "/", end="", sep="") if ord(ch) ==10: # перевод строки (LF) print() stream.close() Pic. 1-15-4. Счи тывание файла в один прием — код. 49/49/49/49/49/49/49/49/49/49/10/ 50/50/50/50/50/50/50/50/10/ 51/51/51/51/51/51/51/51/10/ 52/52/52/52/10/ Ю/ 53/53/53/53/53/53/53/53/53/53/53/53/53/53/53/53/10/ »>| Рис. 1-15-5. Счшывание файла в один прием — тест. Формат функции open (): newStream = open([path]fileName) Если открытие прошло успешно, ссылка на нсеый поток сохраняется в переменной newStream. Эта ссылка понадобится позже для обработки файла, путь (path) опционален. Если мы его опускаем, система подразумевает, что файл находится в той же папке, что и и наща программа на Питоне. Открытие — это попытка идентифицировать физический сектор на жестком диске, где находится начало файла. Если файл не найден, выбрасывается исключение lOError (см. урок 9) В таком случае функция sys . exit () завершает выполнение программы. Библиотечная функция read () считывает весь файл в строковую переменную content. Билиотечная функция ord () дает нам ASCII код символа (см. урок 4). Например ASCII код числа "1" — 49, числа ‘'2" — 50 и т. д LF (перевод строки) символ завершает строки в текстовом файле; ею код — 10 Нс будьте внимательны, некоторые текстовые файлы имеют два символа е конце каждой строки: CR (возврат каретки, код 13) и LF. Если файл находится где-то (не в текущей папке), нам нужно указать абсолютный путь 66
import sys try: stream = open ("C:/Users/Compu/OneDrive/Desktop/nums.txt") except lOError: print("Opening error!") sys .exit () content = stream.read() for ch in content: print (ord(ch), "/", end="", sep="") if ord(ch) ==10: # Line Feed (LF) print() stream.close() Рис. 1-15-6. Считывание текстового файла в один прием используя абсолютный путь. Если файл nums . txt расположен рядом с текущей папкой, мы должны подняться сначала вверх в родительскую папку, затем вниз import sys try: stream = open("./../Iessonl3 strings/nums.txt") except lOError: print("Opening error!") sys.exi t () content = stream.read() for ch in content: print, (ord (ch) , end»"", sep»"") if ord(ch) ==10: # Line Feed (LF) print() stream.close () Рис. 1-15-7. Чтение текстовою файла через относительный путь. Если текстовый файл невелик, мы можем попробовать считать его в один прием, однако гораздо более общепринятый подход — читать строку за строкой import sys oneLine = "" # reading buffer try: stream = openCnums.txt") except lOError: print("Opening error!") sys.exit() while True: oneLine = stream.readline() if len(oneLine) == 0: break print(oneLine, end="") stream.close () Рис. 1-15-8. Построчное чтение текстовою файла. Е>иблиотечная функция readline () возвращает одну строку из файла или пустой набор символов, если достигнут конец файла. 67
Чтение/Запись Простой формат функции open () с одним парамаетром, который мы обсудили, подходит для чтения текстовых файлов, однако, более универсальный формат с двумя параметрами позволяет обрабатывать и текстовые и бинарные файла как на чтение так и на запись Функция open () с двумя параметрами- newStream = open([patn]name, mode) Символ mode определяет тип (моду) обработки. Существует несколько типов обработки, но наиболее употребимые- "г" — чтение, "w" — запись а новый файл, "а" — добавление к существующий файл. Вы также можете добавлять к симолу типа обработки символ типа файла "t" (текстовый) или "Ь* (бинарный). Например, "wb" означает открыть новый бинарный файл на запись. Следующая программа создает дупликат текстового файла. Обратите внимание, если файл nums сору. txt уже существует, он будет вновь создан, т.е. старая версия удалится. import sys inputstream = None outputstream = None oneLine = "" # reading buffer try: inputstream = open("nums.txt") except lOError: print("Opening error!") # ошибка открытия sys.exi t () try: outputstream = open("nums copy.txt", "w") except lOError: print("Creating error!") # ошибка создания sys.exit() while True: oneLine = inpucStream.readline() if len(oneLine) == 0: break outputstream.write(oneLine) inputstream.close() outputstream.close() Нис. 1-15-9. Копирование текстового файла. 68
Ключевое слово None означает отсутствие значения. Потоки не принадлежат к примитивным типам, поэтому мы инициализируем их используя None На самом деле, потоки — это объекты (тема следующего урока). Следующая программа копирует бинарный файл; функция read () может иметь параметр: количество байт, которое считывается, в нашем случае — один. Функция write () имеет параметром буфер, содержимое которого сбрасывается в новый файл. import sys # переменные inputstream = None outputStream = None oneByte = "" # reading buffer try: inputstream = open("benefits.jpg", "rb") except lOError: print("Opening error!") sys.exit () try: outputstream = open("benefits copy.jpg", "wb") except lOError: print("Creating error!") sys.exi t() while True: oneByte = inputstream.read(l) if len(oneByte) == 0: break outputstream.write(oneByte) inputStream.close() ourputStream.close() Рис. 1-15-10. Копирование бинарного файла. 69
Часть 2. Обьектно-ориентированное программирование (ООП) Урок 1. Введение Обьекто-ориентированное программирование — это современная, ставшая де-факто стандартом парадигма разработчиков програмного обеспечения, когда переменные и функции объединяются в новые синтаксические и смысловые блоки, называемые классами и обьектами. Фактически, классы и объекты являются конгломератами данных и кода (выполняемых инструкций). ООП начало обретать популярность в середине восьмидесятых годов прошлого столетия Классическая (без обьектов) парадигма называется процедурным программированием. Питон не принуждает разработчиков использовать ООП, как это сделано в Яве или Си#. Эыбор остается за вами, вы можете использовать в своих проектах только классический подход, только современный или комбинировать оба. Основные термины Определение: класс — набор связанных переменных и функций, созданных для решения определенной программистской задачи Определение: переменная экземпляра/свойство — переменная, которая принадлежит классу. Определение: метод — функция, которая принадлежит классу. Определение: обьект— экземпляр (англ, instance), представитель класса. Определение: инстанцирование — процесс создания обьектов Определение: конструктор/инициализатор — специальный метод вызываемый автоматически, когда обьект инстанцируется. Звучит абстрактно? Не волнуйтесь, как только мы начнем практиковаться, все частички головоломки быстр" сойдутся вместе Предположим, что мы создаем приложение базы данных для колледжа и мы решили создать новый тип данных (класс) для обработки персональной информации о студентах. 70
В таком случае алы делаем два шага 1. Мы создаем класс Student, где определяем нее свойства студента (например, имя, адрес, текущие оценки, курс и т. п ) и методы обработки (например, зарегистрировать (register ()), поставить оценку (grade ()), перевести на следующий курс (transfer ()) и т. д. 2. Мы инстанцируем конкретных студентов как обьекты, так что каждый имеет свое имя, свой адрес, свои оценки. Мы манипулируем студентами через методы, определенные в классе Student. Все студенты «шерят» одни и те же методы, но метод должен знать, к какому конкретно студенту он прилагается. Поэтому методы вызываются не как обычные функции, то есть по имени; сначала нужен т н «квалификатор» (имя обьекта и точка), и только затем — имя метода. Например studentl. transfer (...) или: student? .grade (...) Итак, класс — это чертеж (план, заготовка), по которому будут позже создаваться и обрабатываться обьекты класса Student. Свойства у разных студентов разные, но все студенты имеют похожее поведение, поскольку «шерят» методы Получается, что все студенты похожи, и все студенты различны. Используя ООП, мы моделируем в приложениях обьекты реальною мира, создавая новые, определенные программистом, типы данных. Первый раз в первый класс! Мы создадим ноьый тип данных Rectangle (рус. - прямоугольник) с тремя переменными экземпляра width (рус. - ширина), height (рус. - высота) и outline (рус. - контур). Как мы увидим скоро (Часть 2 урок 4) есть некоторая разница между терминами «переменная экземпляра» и «свойство». Но пока это неважно, мы будем использовать оба. Мы определим три метода, чтобы работать с прямоугольниками grow () (рус. - расти), shrink () (рус. - сьеживаться) and draw () ((рус. - нарисовать)). Свойства width and the height понятны, свойство outline — это просто символ, который будет использоваться при отрисовке прямоугольника в окне консоли Методы grow () and shrink () должны увеличивать или уменьшать width and height соответственно. Наберите следующий код и сохраните в файле rectangle_class. ру: 71
class Rectangle: def init (self, w, h, o) : # конструктор self.width = w self.height = h self.outline = о # 3 переменные экземпляра def grow(self): self.width += 1 self.height += 1 # метод def shrink(self): self.width -= 1 self.height -= 1 # метод Рис. 2-1-1. Класс Rectangle — определение. Обычно мы делаем первую букву класса заглавной, но это не является требованием языка Питон. Как вы видите в классе определено три метода init_(), grow (), и shrink (). Имя конструктора в Питоне стандартно и состоит из двух знаков подчеркивания, ключевого слова init и еще двух знаков подчеркивания, Но где же переменные экземпляра? Они определяются и инициализируются г теле конструктора с префиксом self. При использовании переменных экземпляра внутри методов, префикс self обязателен Таким образом мы четко дифференцируем переменные экземпляра и локальные переменные, потому что у локальных переменных нет префиксов. Опускание префикса self при работе с переменными экземпляра — очень распространенная ошибка начинающих. Не забывайте, что ООП разработка всегда подразумевает два шага 1) определение класса, 2) инстанцирование (создание обьектов). Первый шаг сделан, перейдем ко второму: откройте еще один файл, сохраните его как rectangl е test. ру и напечатайте следующий код (англ, before — перед, after — после) from rectangle_class import * rl = Rectangle(11, 11, "-") # инстанцирование r2 = Rectangle (20, 2, "I") print("Defore grow():", rl.width, rl.height, r2.width, r2.height) rl.grow() r2.grow () printCAfter grow():", rl.width, rl.height, r2.width, r2.height) print("Before shrink():", rl.width, rl.height, r2.width, r2.height) rl.shrink() r2. shrink () printCAfter shrink() rl.width, rl.height, r2.width, r2.height) Рис. 2-1-2. Класс Rectangle — инстанцирование. 72
Как ьы видите, сначала мы подключили к главной программе определенние класса через директиву import. Затем мы инстанцировали два прямоугольника, используя имя класса и три аргумента. Это означает, что дважды был неявно вызван конструктор _init_() . Заметьте, что конструктор имеет четыре параметра в определении класса, но только три аргумента, когда он вызывается Что происходит внутри конструктора? Посмотрим на определение класса. Первым делом, конструктор запрашивает ОС распределить новую память под две целых переменных и одну строку. Полученный адрес памяти сохраняется в переменной seif. Через self все свойства инициализируются. Наконец, self (адрес "новорожденного" обьекта) возвращается в головную программу и сохраняется в переменной rl. Тоже самое происходите переменной г2. Таким образом работа конструктора состоит в том, чтобы: • распределить память под свойства, • инициализировать свойства, • вернуть адрес распределенной памяти в вызывающую программу, так что новый обьект можно использовать Когда вы используете свойства обекта, вам нужен квалификатор (имя обьекта), точка и имя свойства. Квалификатор необходим, поскольку разные прямоугольники могут иметь разные размеры и контуры. Методы grow () и shrink () имеют три параметра, но только два аргумента. Когда мы вызываем grow () с rl,адрес rl копируется в self. Когда мы вызываем grow () с г2, адрес г2 копируется в self. То есть один и тот же метод может обрабатывать разные прямоугольники. Определение: ключевое слово self в определении метода означает адрес того обьекта, с которым метод используется. Конструкторы возвращают self Обычные методы получают self. Мы сейчас имем два файла в нашем проекте Какой запускать? Мы должны запускать файл rectangle_test.ру. Before grow () : 11 11 20 2 After grow(): 12 12 21 3 Before shrink(): 12 12 21 3 After shrink(): 11 11 20 2 »> Рис. 2-1-3. Класс Rectangle инстанцирование - reci. 73
Урок 2. Разработка методов Новый метод draw () должен визуализировать (рисовать) прямоугольники Отрисуем прямоугольник в три шага riepx (англ, - top), середина (англ. - middle) и низ (англ. - bottom). class Rectangle: def inir (self, w, h, o): self.width = w self.height = h self.outline = о def grow(self): self.width += 1 self.height += 1 def shrink(seif): self.width -= 1 self.height -= 1 def drawTop(self) : for column in range(self.width): print(self.outline, end="") print() def drawMiddle(self): for row in range (self.height - 2): print (self.outline, end="") for column in range (self.width - 2): print(" ", end="") print(self.outline) def drawBottom(self): self.drawTop() def draw(self): self.drawTop() self.drawMiddle() self.drawBottom() Рис. 2-2-1. Класс Rectangle с методом draw () — код. Код методой отрисовки достаточно очевиден, чтобы отрисовать середину, требуется вложенный цикл. Обратите внимание, что когда методы вызываются внутри других методов требуется префикс self. from rectangle class import * r = Rectangle (5, 6, "I") r.draw() Рис. 2-2-2. Класс Rectangle с методом draw() — инстанцирование. 74
»> Рис. 2-2-3. Класс Rectangle с методом draw() — тест. Урок 3. Статические члены класса Статические переменные Существует некоторая проблема с классом Rectangle, которую нам нужно адресовать; метод draw () не отрисовывает правильно очень маленькие (меньше 3) и очень большие (выходящие за размеры консоли) прямоугольники; нам ничто не мешает даже пытаться создавать прямоугольники с отрицательными размерами. С целью сделать поведение обьектов более предсказуемым, надежным и менее подверженным ошибкам, мы можем установить минимумы и максимумы для размеров прямоугольников: 3 — минимум для ширины и высоты, 60 — максимум для ширины и 20 - максимум для высоты. Определение; статические (или классовые) переменные являются общими, то есть «шерятся» между всеми обьектами класса. Для того чтобы использовать статические переменные, нет нужды инстанцировать никакие обьекты: используйте имя класса как квалификатор. class Rectangle: MIN_W = 3 MIN H = 3 MAX _W = 60 MAX_H = 20 def __init_(self, w, h, o) : if w < Rectangle.MIN W: self.width = Rectangle.MIN_W elif w > Rectangle.MAX W: self.width = Rectangle.MAX W else: self.width = w if h < Rectangle.MIN_H: self.height = Rectangle.MIN H elif h > Rectangle.MAX H: self.height = Rectangle.MAX H else: self.height = h self.outline = о 75
def grow(self): if self.width < Rectangle.MAX_W and \ self.height < Rectangle.MAX H: self.width += 1 self.height += 1 def shrink(seif): if self.width > Rectangle.MIN W and \ self.height > Rectangle.MIN H: self.width -= 1 self.height -= 1 я 4 метода отрисовки - без изменений... Рис. 2-3-1. Класс Rectangle с проверкой Четыре статических переменные были продекларированы в начале класса, мы используем только заглавные в их именах чтобы показать, что это константы (переменные, которые не изменяются). Использование заглавных букв для констант является распространенной практикой в программировании Везде, где мы их использовали, мы добавляли имя класса (Rectangle) как квалификатор. Таким образом наш конструктор и методы grow () / shrink () проверяют размеры и не позволяют им выйти за отведенные для них пределы. from rectangle class import * г = Rectangle(-100, -100, "|") r.draw () print(Rectangle.MIN W, Rectangle.MIN H, Rectangle.MAX W, \ Rectangle.MAX H) Рис. 2-3-2. Класс Rectangle co статическими членами — использование. I I I I I I I I 3 3 60 20 Рис. 2-3-3. Класс Rectangle co статическими членами —тест. Как ьы видите, некорректные размеры были заменены на правильные значения Статические методы Статистические методы не имеют доступа к переменным экземпляра, это означает, что нет смысла указывать имя обьекта, когда вы их вызываете Вместо этого, мы используем имя класса как квалификатор Чтобы продемонстрировать идею, мы создадим в классе Rectangle статический метод help (). 76
class Rectangle: MIN W = 3 MIN Ji = 3 MAX W = 60 MAX H = 20 @staticmethod def help(): print("Rectangle class is to create, manipulate & draw" "rectangles") print("Creating rectangles requires 3 arguments: width, height" " and outline") print ("Methods: grow(), shrink (), draw()") print("Rectangles can be from:", Rectangle-MIN fl, "x", \ Rectangle.MIN H) print("Rectangles can be up to:", Rectangle.MAX_W, "x", \ Rectangle.MAX H) #далее без изменений... Рис. 2-3-4. Статический метод help () from rectangle class import * Rectangle.help() Рис. 2-3-5. Вызываем статический метод help (). Rectangle class is to create, manipulate & draw rectangles Creating rectangles requires 3 arguments: width, height and outline Methods: growO, shrink(), draw() Rectangles can be from: 3x3 Rectangles can be up to: 60 x 20 »> Рис. 2-3-6. Статический метод help () — тест. Перевод сообщения с английского «Класс Rectangle создан для создания, обработки и отрисовки прямоугольников Для создания прямоугольников требуется 3 аргумента, ширина, высота и контур Методы grow(), shrink(), draw() Прямоугольники могут быть от: 3 х 3 Прямоугольники могут быть до: 60 х 20» Просуммируем. Мы можем классифицировать переменные определенные в классе как: • Экземпляра ("обычные") • Статические Мы можем также классифицировать методы определенные в классе как: • Экземпляра (' обычные") • Статические 77
Важные фактыу1ля запоминания: * Обычные методы должны иметь параметр sei f. * Обычные методы имеют доступ как к обычным так и к статическим переменным • Статические методы не имеют параметра self. • Статические методы не имеют доступа к переменным экземпляра. • Статические методы имеют доступ только к статическим переменным. • Статические методы должны иметь директиву @staticmethod перед их определением Рис. 2-3-7. Взаимосвязь методов и переменных. Урок 4. Сокрытие данных (инкапсуляция) Определив ограничения для размеров прямоугольников мы сделали класс Rectangle надежней. Когда прямоугольники инстанцируются или обрабытынаются посредством grow () и shrink (), их размеры проверяются. Однако ничто не мешает пользователю класса, изменить ширину и высоту после инстанцирования. Например г = Rectangle(11, 10, "х") г.width = -99999 # ??? Определение: Сокрытие данных (или инкапсуляция) — это концепция ООП, состоящая и том, что следует «прятать» переменные экземпляра, так что пользователь класса не может их адресовать непосредственно и по ошибке «повредить». Более безопасный доступ к переменным экземпляра предоставляется через специальные методы, — «сеттеры» и «геттеры» (англ, setters and getters). 78
МЕТОДЫ МЕТОДЫ Рис. 2-4-1. Сокрытие данных (инкапсуляция). Из этой картинки хорошо видно, почему мы использем термин инкапсуляция Данные гложены -= некое подобие капсулы или оболочки, и эта оболочка состоит из методой Методы гарантируют безопасный доступ к данным Геттеры и сеттеры Определение: геттер (англ, getter) — специальный метод, позволяющий считывать переменные экземпляра. При вызове у геттера нет аргументов, он возвращает значения переменной экземпляра Определение; сеттер (англ, setter) — специальный метод, позволяющий безопасно менять переменные экземпляра. При вызове у сеттера один аргумент, он устанавливает новое значение переменной экуземпляра Инкапсуляция не является "железным" правилом, это просто рекомендация, базирующая на здравом смысле Геттеры и сеттеры могут замедлить обработку обьектов Если вы полагаете, что открытый доступ к переменным экземпляра не сделает ваш класс более опасным и «ранимым», поступайте так, как считаете нужным, выбор за вами. Программирование — это математическое моделирование сложных реальных процессов, поэтому здесь не существует легких рецептов на все случаи жизни. Двайте создадим более безопасную и надежную е ерсию класса Rectangle. Первым делом мы заменим везде в классе Rectangle width на width и height на height (добавим префикс из д^ух символок подчеркивания). Таким образом, мы скрываем переменные экземпляра от пользователя класса. В то же время мы добавим два геттера и два сеттера (остальные методы класса остаются без изменений). 79
def getWidth(self) : return self._______width def setwidth(self, w): if w < Rectangle.MIN-W: self, width = Rectangle.MIN W elif w > Rectangle.MAX_W: self. _width = Rectangle.MAX_W else: self._ width = w def getHeight(self): return self._______height def setHeight(self, h): if h < Rectangle.MIN H: self._ height = Rectangle.MTN_H elif h > Rectangle.MAX H: self. _height = Rectangle.MAX H else: self. height = h Рис. 2-4-2. Геттеры и сеттеры. from rectangle_class import * r = Rectangle (10, 10, "*") r.setWidth (99) print(r.getWidth()) Рис. 2-4-3. Геттеры и сеттеры — использование. 60 »> Рис. 2-4-4. Гетт еры и сет теры — тест. Как вы видите, попытка сохранить в переменной экземпляра некорректное значение (99) была предотвращена и безопасный максимум (60) был назначен. Свойства Предложенный нами подход сокрытия данных — вполне рабочий, но частое использование префиксов get и set может показаться утомительным; кроме того, надо все время печатать круглые скобки, вызывая геттеры и сеттеры. Механизм свойств позволяют вызывать геттеры и сетттеры, как если бы это были переменные экземпляра. То есть свойства выглядят для пользователя класса как переменные, для создателя класса — как методы. Свойства - «умные» переменные экземпляра. Посмотрите, как это программируется (вместо геттеров и сеттеров мы создали свойства используя специальные директивы со знаком @). 8<1
@property def width(self): return self._______width @width.setter def width(self, w): if w < Rectangle.MIK_W: self.__width = Rectangle.MIN_W elif w > Rectangle.MAX W: self. width = Rectangle.MAX W else: self.__width = w ^property def height(seif): return self._______height @height.setter def height(self): if h < Rectangle.MIN H; self, height = Rectangle.MTN_H elif h > Rectangle.MAX_H: self. height = Rectangle.MAX H else: self._______height = h Рис. 2-4-5. Свойства. from rectangle_class import * r = Rectangle(10, 10, "*") # инстанцирование r.width =99 # неявный вызов сеттера print(r.width) # неявный вызов геттера Рис. 2-4-6. Свойства — использование. Сравните эту версию с предыдущей (Рис. 2-4-3), и согласитесь, что она выглядит более натурально 60 Рис. 2-4-7. Свойства — тест. Урок 5. Композиция Основополагающий вопрос обьектно-ориентированного проектирования: • Как строить но^ые, более продвинутые классы, базируяюсь на уже существующих? Всякий знакомый с этой темой, подскажет: наследование1. Это, конечно, так, и мы обсудим наследование в следующем уроке Но есть, возможно, более простой способ повторного использования классов. Он называется композиция; проанализируем сначала его. 81
Определение: композиция — это практика включения в новый класс существующих обьектов как переменных экземпляра. Построим новый класс Box (рус. - коробка, ящик). Каждая "коробка" строится из шести прямоугольников- передний (англ. - front), задний (англ. - back), левый (англ. - left), правый (англ. - right), верхний (англ. - top), и нижний (англ. - bottom). Класс Box имеет два похожих на класс Rectangle метода: grow (), shrink (), метод draw () заменен на метод _str () — о нем чуть позже. При инстанцировании "коробок" требуется три параметра, ширина, высота и глубина. Rectangle grow() shrinkf) draw() grow() shrink() _str_() Рис. 2-5-1. Новый класс (Box) использует существующий класс (Rectangle). from rectangle class import * class Box: def init (self, w, h, d): self.rcFront = Rectangle(w, h, "I") self.rcBack = Rectangle(w, h, "I") self.rcLeft = Rectangle(d, h, "|") self.rcRight = Rectangle(d, h, "I") self.rcTop = Rectangle(w, d, "I") self.rcBottom = Rectangle(w, d, "I") def grow(self): self. rcE'ront. grow () self.rcBack.grow() self.rcRight.grow() self.rcLeft.grow() self.rcTop.grow() self.rcBottom.grow() 82
def shrink(self): self.reFront.shrink() self.rcBack.shrink() self.rcRight.shrink() self.rcLeft.shrink() self.rcTop.shrink() self.rcBottom.shrink() def str (self): return "Width: " + str(self.reFront.width) + \ ", height: " + str(self.reFront.height) + ", depth: " + \ str(self.rcRight.width) Рис. 2-5-2. Класс Box. В самой первой строке мы импортировали класс Rectangle Как видите, класс Box включает в себя 6 обьектов класса Rectangle, они инстанцируются внутри конструктора. Вы можете нключить в любой класс специальный метод str_ () Такой метод возвращает строку, текстовое описание обьекта. Если такой метод есть, вы можете печатать представителей этого класса, используя библиотечную функцию print (). from box class import * box = Box(4, 5, 6) print("A box instantiated!") print(box) print("Front rectangle:") box.rcFrcnt.draw() print("Left rectangle:") box.rcLeft.draw() Рис. 2-5-3. Класс Box — использование. A box instantiated’ Width: 4, height: 5, depth: 6 Front rectangle: Left rectangle: »> Рис. 2-5-4. Класс Box - тест. 83
Как ны видели для доступа к методам и свойствам вложенных обьектов требуются вложенные квалификаторы. Если вы вкладывете обьекты слишком глубоко, вы можете получить сложные для понимания выражения с многими квалификаторами obj1.obj2.obj 3.....objN.property Урок 6. Наследование Определение: наследование — основополагающая практика ООП, когда существующий класс улучшается/расширяется посредством добавления к нему новых переменных экземпляра и методов. Наследование однако значительно отличается от композиции. При композиции вы включаете в определение класса обьекты существующих классов. Это как если бы вы скопировали в нсвый вордовский документ уже существующие документы. При наследовании новое определение класса базируется на определении существующего классаю. Это похоже на создании нового Word документа на базе определенного шаблона. На данный момент, наши прямоугольники имеют контур, но «пусты» внутри. Попробуем создать другой клас FilledRectangle, который будет иметь неюлько ширину, высоту, контур, но и заполнение. Оба класса будут иметь три метода grow (), shrink () и draw (). Rectangle FilledRectangle grow() shrink() draw() grow() shrink() draw() Рис. 2-6-1. Новым класс (FilledRectangle) использует существующий класс (Rectangle). from rectangle_class import * class FilledRectangle(Rectangle): # конструктор получает ширину, высоту, контур и заполнение def _ init (self, w, h, о, f): super().__init___(w, h, о) # родительский конструктор вызван self.fill = f # дополнительные переменные # проиниииализированы Рис. 2-6-2. класс FilledRectangle. 84
Мы называем существующий класс родительским, и ноный класс — дочерним. К сожалению, терминология непоследовательна для разных языков. Например, в C++ мы называем эти классы базовым и производным, в Яве — суперклассом и субклассом, но это не меняет идеи. Как е<ы ь-идите, при определениии дочернего класса мы должны включать имя родительского класса в круглых скобках. Затем в первом же операторе конструктора мы должны получить ссылку на родительский экземпляр и проинициализировать его. Функция super () дает нам такую ссылку на родительский экземпляр. Используя эту ссылку, мы вызываем конструктор родительского класса, поскольку дочерний экземпляр может быть построен только на базе родительского экзэмпляра. Затем мы инициализируем тс переменные дочернего экземпляра, которых родительский класс не имет, в нашем случае это fill. from filled rectangle class import * r = FilledRectangle(4, 4, "-*•) r.draw () r.shrink() r.draw () Рис. 2-6-3. FilledRectangle —демо. * + + * + k k * til* k ★ + k k k k k »> Figure 2-6-4. FilledRectangle - тест. Самое удивительное здесь то, что мы ценой всего нескольких строчек кода смогли получить полную функциональность родительского класса практически «задаром», не вникая даже в то, как родитель сделан. Прямоугольник в заполнением может делать все, что мог делать прямоугольник без заполнения. Очевидно, методы grow () и shrink () вполне подходят, мы можем ими пользоваться, однако мы не удовлетворены методом draw (), поскольку он не показывает заполнения прямоугольника Исправим это. 85
Урок 7. Полиморфизм Касательно отрисовки в классе FilledRectangle, мы не должны менять методы drawTop () and drawBottom (), поскольку они не используют симнол заполнения. Оба метода нам подходят. Только метод drawMiddle () должен быть заменен. from rectangle class import * class FilledRectangle(Rectangle): def __init_(self, w, h, o, f) : super(). init (w, h, o) self.fill = f # drawMiddle() - переопределен! def drawMiddle(self) : for row in range (self-height - 2): print(self.outline, end="") for column in range (self.width - 2): print(self.fill, end-"") print(self.outline) Рис. 2-7-1. Класс FilledRectangle . from filled_rectangle_class import * r = FilledRectangle (4, 4, "-»•) r.draw() r . shrink. () r.draw () Рис. 2-7-2. Класс FilledRectangle — инстанцирование. **** *___* h___+ * *** * * * * _ * * * * Рис. 2-7-3. Класс FilledRectangle — тест. Сравните этот код и код в предыдущем уроке (Рис. 2 6 3). Они идентичны. Поскольку дочерний класс не имеет метода draw (), используется метод draw () из родительского класса. Тем не менее прямоугольник с заполнением правильно отрисован Волшебство? Не совсем, попробуем понять, что случилось Определение.: полиморфизм в ООП — это переопределения (перекрытия) в дочернем классе методов родительского класса 86
Полиморфизм означает много форм, т.е. разные классы могут иметь методы с идентичными именами, но которые работаеют по разному на разных обьектах. Мы видим здесь интересное отличие между композицией и наследованием При композиции новый класс берет старые обьекты как они есть. При наследовании новый класс может заменить в старом то, что ему не подходит. Другими словами, разработчик дочернего класса может «подкорректировать» родительский класс даже не имея доступа к его исходникам! Итак, оба класса Rectangle и FilledRectangle имеют метод drawMiddle (), но интерпретатор Питона вызовет динамически (во время выполнения программы) правильный метод исходя из адреса текущего обьекта Если текущий обьект — прямоугольник, вызывается родительский drawMiddle О , если текущий обьект —прямоугольник с заполнением, вызывается дочерний drawMiddle (). Метод draw () не реализован в дочернем классе, дочерний класс может использовать его как он есть, поскольку родительский метод drawMiddle () будет автоматически заменен на дочерний drawMiddle () во время выполнения программы. Еще один важный момент. Мы может присваивать обьектам родительского класса обьекты дочернего класса Представим, что оба, родительский и дочерний класс, имеют полиморфный метод move (). parenc = Parent (...) child = Child (...) parent = child # это нормально parent.move() # чей метод будет вызван? Метод дочернего класса! Дочерний метод вызывается потому, что переменная parent содержит ссылку на обьект класса Child. Интерпретатор Питона вызывает правильный метод базируясь на информации об обьекте во время выполнения программы. Урок 8. Еще раз о строках, контейнерах и потоках После того как мы изучили основы ООП, мы можем заметить что строки, списки, кортежи, словари и потоки на самом деле — обьекты. Следовательно, они принадлежат определенным классам. Нам не требуется знать имена этих классов, поскольку процесс инстанцирования упрощен, и мы не вызываем конструкторы явным образом myName = "Andrei" # инстанцирование строки myMarks = [79, 88, 55, 60] # инстанцирование списка mySubjectsAndMarks = {"С": 69, "C++": 88, "Java": 95} # инстанцирование # словаря # инстанцирование потока myResume = open("С:/users/Andrei/documents/my resume.txt") Мы манипулируем строками, контейнерами, потоками используя широкий набор библиотечных методов. 87
Например: myName.capitalize() myMarks.append(79) mySubjectsAndMarks.update({"Swift": 90}) buffer = myResume.read() Несмотря на то, что мы можем, вообще говоря, наследовать эти «неявные» классы и создавать наши собственные, «улучшенные» строки, контейнеры, потоки, мы обычно это не делаем. Однако мы часто используем некоторую разновидность композиции (часть 2, урок 5), «клады вая контейнеры Любой элемент контейнера может быть в свою очередь контейнером. myName = "Andrei" mySubjectsAndMarks = {"С": 69, "C++": 8в, myProfile = [myName, mySubjectsAndMarksJ print (myPrcfile[1] ["Java"]) # инстанцирование # строки "Java"; 95} # инстанцирование # словаря # композиция fl доступ к # вложенным элементам 88
Часть 3. Фреймворк tkinter Урок 1. Хэлло, tkinter! Небольшие консольные демо-приложения, которые мы разрабытывали до сих пор, хороши для иллюстрации базовых концепций программирования, но, откровенно говоря, не имеют большой практической пользы. Давно миновали те времена, когда мы использовали консольные команды и приложения для работы на компьютерах. Сейчас мы все привыкли к интуитивным, «дружественным» интерфейсам с окошками, разворачивающимися меню, кнопками, прокруткой, полями ввода, графикой, мултимедиа и т. д. Мы называем эти современные приложения — GUI (Graphical User Interface, рус. графический интерфейс пользователя) приложениями, старые — консольными приложениями. Современные приложения, конечно, легки для пользователей, но гораздо более трудны, нежели консольные, для разработчиков Обьемные наборы библиотек и инструментов (т.н. фреймворки, рус. — платформы) были созданы для того, чтобы облегчить разработку GUI приложений: Windows SDK. MFC, Swing, Android SDK, Cocoa Touch и т. д. Как правило, все они обьектно-ориентированы, и для того чтобы их освоить, нужно хорошее понимание основ ООП. Разработчики на Питоне используют платформу tkinter (произносится «ти-кей- интер») для создания GUI приложений; начнем с простейшего — "Hello, tkinter!" 1 froE.. tkinter import * 2 3 root = Tk() 4 root.title("My First GUI App") 5 root.geometry ('401x300") 6 7IblMixer = Label(master=root, text='Hello, tkinter!', font=( Arial", 40, 'bold')) IblMixez.pack() 9 H root .mainloopO Рис. 3-1-1. "Hello, tkinter!" — код. 89
/ My First GUI App □ X Hello, tkinter! Рис. 3-1-2. "Hello, tkinter!" — тест. В 1-й строке мы подключили к программе все классы из библиотеки tkinter. В 3-й строке мы создали главное окно приложения; объект root был инстанцирован посредством вызова конструктора класса Тк Мы установили заголовок и размер главного окна через вызон методов title() and geometry (). Это не означает, что размер не может быть потом изменен пользователем, — просто зацепите мышкой и передвиньте границу окна. В строке № 7 мы инстанцировали наш первый виджет. Label (лэйбл, рус метка, ярлык) — возможно, простейший из GUI виджетов; это просто кусочек текста Мы предваряем аргументы ключевыми словами (часть 1, урок 10). Аргумент master определяет родительское окно, к которому относится метка. Аргумент text, возможно, самый важный, поскольку это именно тот текст, который мы хотим показать Наконец, аргумент font это кортеж из 3-х элементов: имя шрифта, размер шрифта и стиль шрифта. Обратите внимание, недостаточно инстанцировать метку, ее нужно визуализировать посредством вызова метода pack (). В последней строке программы мы инициализируем т.н. цикл сообщений. Дело в том, что операционная система коммуницирует с программистом через различные сообщения Для того чтобы ловить и обрабатывать эти сообщения, должен быть создан цикл сообщений. Определение: виджет — визуальный элемент GUI. Примерь! виджетов метки, кнопки, галочки, полосы прокрутки, поля ввода и т д. Все виджеты реализованы как tkinter классы. Виджеты должны быть сначала инстанцированы, а затем расположены на экране. 90
Урок 2. Color Mixer (миксер цвета) с объектами класса Label Конструктор меток имеет много параметров, но хорошая новость состоит в том, что почти все они имеют значения по умолчанию, то есть многие параметры мы мо>кем опускать, и строка инстанцирования не будет очень длинной. Наиболее важные параметры конструктора меток: • master (родительское окно) • text (текст, сообщение) • font (шрифт текста) • fg (цвет текста) • bg (цвет фона) Вы можете задать вопрос: «секундочку.. но здесь чего-то явно не хватает... а где же в точности будет расположен текст внутри окна?» Ответ: «вам не нужно беспокоиться, об этом позаботится система»; t kin ter спозиционирует метку вверху и в центре окна. Попробуйте изменить размер окна, метка автоматически переместится опять в центральную верхнюю позицию! Если добавить еще одну метку используя метод pack (), она расположится ниже первой и тоже отцентруется; в методе pack () можно добавить параметр pady, чтобы создать вертикальное расстояние между метками Чтобы продемонстрировать метки в работе, создадим простое приложение Color Mixer Цвет в компьютерной графике определяется как т.н. RGB (Red - красный, Green - зеленый, Blue - синий) комбинация из 3-х чисел в диапазоне от 0 до 255. Первое число определяет количество красного, второе — количество зеленого, третье — количеств^» синего Например, (255, 0, 0) это чистый красный, (0, 255, 0) это чистый зеленый, (0, 0, 255) — синий, (0, 0, 0) — черный и (255, 255, 255) — белый. Существует второй формат для определения цветов, он использует хэш символ и три шестнадцатиричных числа в диапазоне OO-ff. Например, #ff0000 — чистый красный, #00ff00 — чистый зеленый, #000tf f — чистый синий, #00000^ — черный, и #f f f f f f — белый, tkinter использует вторую форму. Для тою, чтобы изменить цвета метки, мы используем метод conf i g (). Например: aLabel.config(fg=*#ff0000", bg="#00ff00") Создадим Color Mixer GUI приложение с использованием меток. 91
* # 1-2 from tkinter import from random import * seed() # 4 root = Tk() # 6-8 root.title("Color Mixer") root.geometry("400x300") IbiMixer = Label(master=root, width=10, font=("Arial", 4C, "bold"))#10-11 IbiMixer.pack(pady=20) red = randint(0, 255) # 13-16 redText = "RED: " + str(red) + "/255" IblRed = Label(master=root, text=redText, font=("Arial", 30, "bold"),\ fg="red") IblRed.pack() green = randint(0, 255) greenTexc = "GREEN: " + str(green) + "/255" IblGreen = Label(master=root, text-greenText, font= ("Arial", 30, "bold"),\ fg="green") IblGreen.pack() blue = randint(C, 255) blueText = "BLUE: " + str (blue) + "/255" IblBlue = Label(master=root, text=blueText, font=("Arial", 30, "bold"), fg="blue") IblBlue.pack() mixedColor = "#{C:02x}(1:02x}{2:02x}".format(red, green, blue) #28-29 IbiMixer .config (bg--mixedColor) root.mainloop() #31 Рис. 3-2-1. Color Mixer с меч ками — код. В первых двух строчках мы подключили к программе две библиотеки: tkinter и random; библиотека random содержит набор функций для генерации случайных чисел. Затем мы инициализировали генерацию случайных чисел посредством вызова функции seed (). В строках 6-8 мы создаем главное окно, устананавлиеаем его размер и заголовок. В строках 10-11 первая метка инстанцируется и визуализируется. Эта метка не имеет никакого текста, поскольку мы будем ее использовать как местозаполнитель для показа различных цветов В строках 13-16 мы генерируем случайное число для красного компонента (чем больше число, тем сильней красный); затем мы создаем метку для описания красного цвета и добавляем ее на экран. Эти шаги повторяются для зеленого и синего цветов. библиотечный метод format () используется для сборки строк, базируясь на списке переменных и различных спецификаторах формата. 92
Синтаксис: resulting string = formatting string.format(list_of variables) formatting string (строка форматирования) состоит из обычных символом и местозаполнителей в фигурных скобках Вся конструкция работает так; formatting string обрабатывается слева направо. Обычные символы копируются в resulting string (строку результата) без изменений, но местозаполнители заменяются на значения переменных из списка и тоже вставляются в строку результата. Местозаполнители состоят из номера переменной и спецификатора формата. Например, {1:02х} означает: преобразовать вторую переменную из списка к шесгандцатеричному формату с двумя цифрами (переменные в списке нумеруются с нуля). Существует много разных спецификаторов формата (см. справочник по Питону: https://wwww3schools.com/python/ref string format.asp). Таким образом мы должно иметь столько же местодержателей, сколько переменных в списке. Мы использовали метод format (), чтобы сгенерировать шестнадцатеричный код, который определяет цвет, например: #aOblff. В строке 28 мы сгенерировали код цвета, в строке 29 мы поменяли фоновый цвет метки / Color Mixer RED: 16/255 GREEN: 197/255 BLUE: 22/255 / Color Mixer □ X RED: 240/255 GREEN; 69/255 BLUE: 39/255 Рис. 3-2-2. Color Mixer с метками — 2 теста. 93
УрокЗ. Объектно-ориентированный Color Mixer с метками ООП — это определенная философия, если хотите — ментальная дисциплина. В этом уроке мы не будем менять приложение Color Mixer как таковое, мы просто поместим его в «обьектно-ориентированную оболочку». Мы создадим новый класс, назовем его Form (форма); в нем мы объединим все виджеты и связанные с ними переменные Наш класс Form будет иметь следующие переменные экземпляра: * IblMixer (обьект класса Label, где смешиваются цвета) * red (целая переменная, код красного компонента цвета, случайное число между 0 и 255) • redText (строковая переменная, текст для красного компонента цвета) • IblRed (обьект класса Label, показывает красный компонент) * green (целая переменная, код зеленого компонента цвета, случайное число между О и 255) • greenText. (строковая переменная, текст для зеленого компонента цвета) • IblGreen (обьект класса Label, показывает зеленый компонент) • blue (целая переменная, код синего компонента цвета, случайное число между 0 и 255) • blueText (строковая переменная, текст для синего компонента цвета) • IblBlue (обьект класса Label, показывает синий компонент) Мы используем здесь для построения класса Form принцип композиции (часть 2, урок 5), поскольку обьекты существующего класса Label вставляются в определение нового класса как переменные экземпляра; мы использовали префикс "1Ы" чтобы подчеркнуть их природу: это не переменные примитивных типов, это объекты. Приложение Color Mixer будет состоять из двух файлов головная программа и класс Form. from tkinter import * from random import * from forir class import * seed() root = Tk() root.title("Color Mixer") root-geometry("400x300") form = Form(root) # инстанцирование, вызов конструктора root.mainloop() Рис. 3-3-1. Объектно-ориентированный Color Mixer с метками — головная программа. Код выглядит элегантно, поскольку все технические детали инкапсулированы в объекте класса Form. Нам нужно передать в конструктор ссылку на главное окно, поскольку всем виджетам будет нужна эта ссылка для инстанцирования 94
from tkinter import from random import * class Form: def init (self, mainWnd): self.IblMixer = Label(master=mainWnd, width=lC,\ font=("Arial", 40, "bold")) self.IblMixer.pack(pady=20) self.red = randint(0, 255) self.redText = "RED: " + str (self.red) + "/255" self.IblRed = Label(master=mainWnd, text=self.redText,\ fcnt=("Arial", 30, "bold"),fg="red") self.IblRed.pack() self.green = randint(0, 255) self.greenText = "GREEN: " + str(self.green) + "/255" self.IblGreen = Label(master=mainWnd, text=self.greenText,\ font=("Arial", 30, "bold"), fg="green") self.IblGreen.pack() self.blue = randint(0, 255) self.blueText = "BLUE: " + str(self.blue) + "/255" self.lbiBlue = Label(master=mainWnd, text=self.blueText,\ font=("Arial", 30, "bold"), fg="blue") self.iblBlue.pack() mixedColor = "${0:02x}{1:02xJ{2:02x)". format(self.red,\ self.green, self.blue) self.IblMixer.confi g(bg-mixedColor) Рис. 3-3-2. Класс Form с метками. Как еы видите, мы просто взяли всю логику предыдущей версии программы и перенесли ее в конструктор. Все переменные экземпляра должны иметь префикс self, но mixedColor — локальная переменная, ей не нужен префикс self. Если вы сравните версии Color Mixer из уроко 2 и 3, то у вас может возникнуть некоторый скептицизм относительно тех преимуществ, которые нам дает ООП. Вам может показаться первая версия без классов проще и более интуитивной. Да, возможно для небольших проектов ООП — «стрельба из пушки по воробьям». Также не забывайте, что ООП это рекомендация для разработчиков. «Думайте сами, решайте сами». По крайней мере Питон не принуждает нас использовать ООП Однако нравится нам ООП или нет, мы должны хорошо его понимать, поскольку оно используется сейчас в большом количестве языков, библиотек и платформ. Это де-факто стандарт программирования 95
Урок 4. Кнопки (Button), фреймы (frame), обработчики событий Мы изучили наш перый ниджет — Label. Следующие дна: кнопка (класс Button) и фрейм (класс Frame). Кнопка — интерактивный виджет, по нему можно «щелкать». Фрейм — невидимый виджет, он нужен для обеспечения правильного позиционирования на экране других -иджетои. Фреймы могут быть вложенными Определение: обработчик события — функция (или метод) определенная программистом и вызываемая операционнной системой, когда пользователь манипулирует виджетом Некоторые виджеты активны (кнопки, списки, поля ввода и т. д,), они генерируют события, им требуются обработчики, другие виджеты скорее пассивны (метки, фреймы, картинки), они служат для декоративных целей, для них не нужны обработчики событий. Конструктор кнопки (Button) имеет много параметров, но наиболее важными являются: * master (родительское окно или фрейм) • text (текст на кнопке) • font (шрифт текста) • fg (иветтекста) • bg (цвет кнопки) • command (обработчик события) Конструктор фрейма (Frame) имеет один параметр: • master (родительское окно или фрейм) Когда виджеты добавляются к рамке (метод pack ()), они позиционируются вертикально Если нам нужно горизонтальное расположение, мы используем в методе pack () аргумент side="left". Напишем новую версию приложения Color Mixer с фреймами и кнопками. Мы по прежнему будем использовать метку для показа цветов. Каждый цвет будет управляться парой кнопок, первая кнопка увеличивает количество данного цвета, вторая — уменьшает. Три фрейма используются для группировки кнопок в пары. 96
Фокус ввода Обычно пользователи манипулируют виджетами используя мышь и тачпэд (сенсорную панель нотбука). Но возможно также использование клавиатуры. Иногда клавиатурный доступ быстрее чем мышиный или через тачпэд. Зиджет, который реагирует на клавиатуру, имеет т.н, фокус. В любой момент времени только один виджет может иметь фокус. Пользователи переносят фокус, щелкая на различных виджетах, либо (как альтернатива) посредством нажатия клавиши TAB (комбинация SHIFT+TAB переносит фокус в противоположном порядке). Кнопки с фокусом имеют контур с точками. Программисты также могут управлять фокусом через метод f ocus_set (). Если кнопка получила фокус, ее можно нажимать используя клавишу пробел / Color Mixer — □ X RED - j RED -J GREEN-J GREEN - BLUE + I BLUE - I Рис. 3-4-1. Color Mixer с шестью кнопками (кнопка RED+ имеет фокус). Головная программа остается без изменений (Рис. 3-3-1). Класс Form будет иметь следующие переменные экземпляра: • IblMixer (обьект класса Label для смешивания трех цветов) • red (int переменная, количество красного) • f rmRedControl (обьект класса Form, фрейм для группировки двух кнопок горизонтально) • btnRedlnc (обьект класса Button для увеличения количества красного) • btnRedDec (обьект класса Button для уменьшения количества красного) • green (int переменная, количество зеленого) • frmGreenControl (обьект класса Form, фрейм для группировки двух кнопок горизонтально) • btnGreenlnc (обьект класса Button для увеличения количества зеленого) * btnGreenE^ec (обьект класса Button для уменьшения количества зеленого) 97
• blue (int переменная, количество синего) • frmBlueControl (объект класса Form, фрейм для группировки двух кнопок горизонтально) • btnBluelnc (обьект класса Button для увеличения количества синего) • btnBlueDec (обьект класса Button для уменьшения количества синего) Чтобы дифференцировать обьекты и переменные примитивных типов мы используем префиксы: Ibl — Label, btn — Button, frm — Frame. Класс Form class будет иметь следующие методы * _init_ () (конструктор) • funcRedlnc () (обработчик события присоединенный к btnRedlnc) • funcRedDec () (обработчик события присоединенный к btnRedDec) • funcGreenlnc () (обработчик события присоединенный к btnGreenlnc) • f uncGreenDec () (обработчик события присоединенный к btnGreenDec) * funcBluelnc () (обработчик события присоединенный к btnBluelnc) • funcBlueDec () (обработчик события присоединенный к btnBlueDec) • updateMixer () (метод вызываемый всеми шестью обработчиками событий) from tkinter import * class Form: def init (self, mainWnd): self.iblMixer = Label(master=mainWnd, width=lL, bg="black",\ font=("Arial", 40)) seif.IbiMixer.pack(pady=20) # Красная секция self.red = 0 self.frmRedControl = Frame(master=mainWnd) #11-12 self.frmRedControl.pack(pady=10) seIf.btnRedlnc = Button(mascer=self.frmRedControl, text“"RED +",\ command=self.funcRedlnc) self.btnRedlnc.pack(side-"left") self.btnRedDec = Button(master=self.frmRedControl, text="RED -",\ command=self.funcRedDec) self.btnRedDec.pack(side="left", padx=5) # Зеленая секция self.green = 0 self.frmGreenControl = Frame(master=mainWnd) self.frmGreenControl.pack(pady=10) self.btnGreenlnc = Button(master=self.frmGreenControl,\ text="GREEN +", command=self.funcGreenlnc) self.btnGreenlnc.pack(s ide="left") self.btnGreenDec = Button(master-self.frmGreenControl,\ text="GREEN command-self.funcGreenDec) self.btnGreenDec.pack(side="left", padx=5) 98
# Синяя секция self.blue = С self.frmBlueControl = Frame(master=mainWnd) self.frmBlueControl.pack(pady=10) self.btnBluelnc = Button(master = self.frmBlueControl,\ text="BLUE + ", command=self.funcBluelnc) self.btnBluelnc.pack(side="left") self.btnBlueDec = Button(master = seif.frmBlueControl,\ text="BLUE , command-self.funcBlueDec) self.btnBlueDec.pack(side="ieft", padx=5) self.btnRedTnc.focus_set() # установить фокус ввода # шесть обработчиков событий def funcRedlnc(self): if self.red < 255: self.red += 5 self.updateMixer() def funcRedDec(self): if self.red > 0: self.red -= 5 self.mixUpdaceMixer() def funcGreenlnc(self): if self.green < 255: self.green += 5 self.updateMixer () def funcGreer.Dec(seif) : if self.green > 0: self.green -= 5 self.updateMixer() def funcBluelnc(self): if self.blue < 255: self.blue += 5 self.updateMixer() def funcBlueDec(self): if self.blue > 0: self.blue -= 5 self.updateMixer() # обновить цвет def updateMixer(self): mixedColor = "#{0:02x}{1:02x}{2:02x}".format(self.red,\ self.green, self.blue) self.IblMixer.config(bg=mixedColor) Рис. 3-4-2. Класс Forme рамками (Frame) и кнопками (Button). Идея новой версии приложения Color Mixer состоит в том, что цвета больше не являются случайными числами, они управляются посредством шести кнопок. В строчках 11-12 мы инстанцируем новый фрейм и добавляем его к главному окну. Следующие четыре оператора инстанцируют две кнопки для контроля красным компонентом и добавляют их к фрейму горизонтально. Зеленая и синяя секции аналогичны Когда кнопка инстанцируется, аргумент command определяет ссылку на обработчик события Мы подключили шесть обработчиков к шести кнопкам, они вызываются когда кнопки нажимаются («щелкаются» мышкой). Эти методы просто меняют количество красного, зеленого и синего; затем испомогательный метод updateMixer () собирает строку кода цвета и обновляет метку IblMixer. 99
Урик 5. Флажки (Checkbutton) и ассоциированные переменные Используя tkinter класс Checkbutton мы инкорпорируем в наши приложения флажок (галочку). Виджет имеет два состояния знл./выкл. / Color Mixer —OX 7 RED [7 teREENj Г BLUE Рис. 3-5-1. Color Mixer с гремя флажками. В новой версии Color Mixer, используя 3 флажка, мы можем произвести 8 различных цветов • черный (нет красного + нет зеленого + нет синего) • красный (красный + нет зеленого + нет синего) • зеленый (нет красного + зеленый + нет синего) • синий (нет красного + нет зеленого + синий) • желтый (красный + зеленый + нет синего) • фиолетовый (красный + нет зеленого + синий) • голубой (нет крчсного + зеленый + синий) • белый (красный + зеленый + синий) Наиболее важные параметры конструктора Checkbutton: master (родительское окно или фрейм) text (текст с правой стороны от флажка) command (обработчик события) variable (ассоциированная переменная типа int, синхронизируется с флажком, может иметь значение 0 или 1) Наиболее популярные методы класса Checkbutton: • select (установить флажок программным путем) * deselect (сбросить флажок программным путем) Головная программа остается без изменений (рис. 3 3-1). 100
Класс Form будет иметь следующие переменные экземпляра • IblMixer (метка, где показан смешанный цвет) • red (переменная типа int, может быть 0 or 1, обозначает присутствие красного цвета) • chbRed (обьект класса Checkbutton для красного цвета) • green (переменная типа int, может быть 0 or 1, обозначает присутствие зеленого цвета) • chbGreen (обьект класса Checkburton для зеленого цвета) • blue (переменная типа int, может быть 0 or 1, обозначает присутствие синего цвета) • chbBlue (обьект класса Checkbutton для синего цвета) Класс Form будет иметь следующие методы: • _init_ () (конструктор) • onCheckUncheck() (обработчик событий для всех трех флажков) from tkinter import * class Form: def init_ (seif, mainWnd): self.IblMixer = Label(master=mainWnd, width=10, bg="black",\ font- ("Arial", 40) self.IblMixer.pack(pady=20) # получить 3 ассоциированных переменных self.red = IntVar() self, green = IntVarO self.blue = IntVarO # Инстанцировать и добавить на экран три флажка # подключить обработчик события и ассоциированные переменные self.chbRed = Checkbutton (rr.aster-mainWnd, text="RED",\ command=self.onCheckUncheck, variable-self.red) self.chbRed.pack() self.chbGreen = Checkbutton(master=mainWnd, text="GREEN",\ command=self.onCheckUncheck, variable=self.green) self.chbGreen.pack() self.chbBlue = Checkbutton(master-mainWnd, text="BLUE", \ command=self.onCheckUncheck, variable=self.blue) self.chbBlue.pack () self.chbRed.focus set() # установить фокус на красном флажке # Обработчик событий def onCheckUncheck(seif): mixedColor = "#{С:02x}{1:02x){2:02x}".format(self.red.get() *\ 255, self.green.get () * 255, self.blue.get () * 255) self.IblMixer.confi g(bg=mixedColor) Рис. 3-5-2. Color Mixer с тремя флажками — код. 101
Мы использовали префикс chb для обьектов класса Checkbox. Это достаточно распространенная практика — обьединять обработчики событий. Другими словами, мы как бы «вешаем» на разные ниджеты тот же самый код. Три переменные типа int (red, green and blue) — т.н. ассоциированные переменные Они привязаны к трем виджетам как только меняются флажки, автоматически меняются ассоциированные переменные, они как бы синхронизируются с флажками Обработчик события чиатет состояние 3-х флажков через ассоциированные переменные и смешивает цвет Работа с ассциированными переменными включает следующие шаги; 1) получить ассоциированную переменную через библиотечную функцию IntVar (), 2) привязать переменную к виджету, когда виджет инстанцируется, 3) получить значение ассоциированной переменной через метод get (), 4) изменить значение ассоциированной переменной через метод set (), не пытайтесь использовать ассоциированные пременные напрямую (см часть 2, урок 4, сокрытие данных) Урок 6. Радио кнопки (Radiobutton) Класс Radiobutton поможет нам имплементировать в GUI приложении группу взаимно исключающих опций Имя имеет исторические корни Много лет назад мы использовали радиоприемники с взаимни исключающими наборами частот: когда вы нажимаете одну кнопку, другая кнонка, нажатая перед этим, выскакивает вверх. Рис. 3-6-1. Старый радиоприемник с восемью радиокнопками. Новая версия приложения Color Mixer будет иметь три группы радио кнопок: для красной, зеленой и синей компонент цвета; в свою очередь каждая группа будет состоять из трех радио кнопок, они отвечают за количество красного, зеленого и синего, NONE (НЕТ) означает О, WEAK (СЛАБЫЙ) — 127, STRONG (СИЛЬНЫЙ) — 255. Таким образом мы можем создать 27 различных цветов (3 х 3 х 3) 102
/ Color Mixer RED GREEN с none Г NONF Г* WEAK; ГТ WEAK С STRONG Г STRONG О X BLUE C NONE C WEAK f* STRONG Нис. 3-6-2. C olor Mixer с радио кнопками. Мы используем горизонтальное размещение для главного окна: метка миксера + 3 фрейма. Внутри кждого фрейма мы сопользуем вертикальное размещение: метка имени цвета + 3 радио кнопки Не забывайте, что метод pack () по умрлчанию размещает виджеты вертикально, но если вы добавляете аргумент: pack (side="left"), виджеты размещаются горизонтально Вкладывая фреймы и меняя ориентацию виджетов внутри фреймов, мы можем размещать виджеты так, как удобно пользователям. Как вы видите, фреймы могут иметь контуры. Наиболее зажные параметры конструктора Radiobutton: • master • text • command • variable • value (родительское окно или фрейм) (текст справа от радио кнопки) (обработчик события) (имя ассоциированной переменной) (значение ассоциированной переменной, когда кнопка будет нажата) Посредством подключения разных радио кнопок к той же самой ассоциированной переменной, мы логически групируем кнопки Наиболее ходовой метод класса Radiobutton: • select (ьыбрать кнопку программно) Головная программа остается без изменений (рис. 3-3-1), однако, вам возможно понадобится увеличить ширину главного окна Класс Form class будет иметь следующие переменные экземпляра: • IblMixer (метка для показа созданного цвета) • red (ассоциированная переменная типа int, может быть 0,127 или 255, означает количество красного) • f rmRed (фрейм, который обьединяет текст «RED" и три радио кнопки для красного) • lblRed ( метка "RED") 103
• rbtRedNone (обьект класса Radiobutton для отсутствия красного) • rbtRedWeak (обьект класса Radiobutton для слабого красного ) • rbtRedStrong (обьект класса Radiobutton для сильного красного) • green (ассоциированная переменная типа int, может быть 0,127 или 255, означает количество зеленого) • f rmGreen (фрейм, который обьединяет текст «GREEN" и три радио кнопки для зеленого) * IblGreen ( метка "GREEN") • rbtGreenNone (обьект класса Radiobutton для отсутствия зеленого) • rbtGreenWeak (обьект класса Radiobutton для слабого зеленого ) • rbtGreenStrong (обьект класса Radiobutton для сильного зеленого) • blue (ассоциированная переменная типа int, может быть 0,127 или 255, означает количество голубого) • f rmBlue (фрейм, который обьединяет текст «ВШЕ" и три радио кнопки для синего) • IblBLue ( метка "ВШЕ") • rbtBl ueNone (обьект класса Radiobutton для отсутствия синего) • rbtBlueWeak (обьект класса Radiobutton для слабого синего ) • rbtBlueStrong (обьект класса Radiobutton для сильного синего) Класс Form будет иметь следующие методы • _init () (конструктор) • onButconClick () (обработчик событий для всех 9-ти радио кнопок) from tkinter import * class Form: def init (self, mair.Wnd) : # Mixer self.IblMixer = Label(master=mainWnd, width=6, bg="black",\ font.— ("Arial", 30)) self.IblMixer.pack(side="left", padx=10) # Целая переменная ассоциированная с красной радиогруппой self, red = IntVarO self.red.set(0) # Widgets to control red component self.frmRed = Frame(master=mainWnd, borderwidth=2, relief=SOLID) self.frmRed.pack(side="left") self.lblRed = Label(master=self.frmRed, text="RED",\ font=("Arial", 15, "bold"), fg="red") self.IblRed.pack() self.rbtRedNone = Radiobutcon(mascer=self.frmRed, text="NONE",\ variable=self.red, value=0, command=self.onButtonClick) self.rbtRedNone.select() self.rbtRedNone.pack() 104
self.rbtRedWeak = Radiobutton(master=self.frmRed, text="WEAK",\ vardable=self.red, vaiue=127, command=self.onButtonClick) self.rbtRedWeak.pack() self.rbtRedStrong = Radiobutton(master=self.frmRed,\ text="STRONG", variable=self.red, value=255,\ command=self.onButtonClick) self.rbtRedStrong.pack() # Целая переменная ассоциированная с зеленой радиогруппой self.green = IntVar() self.green.set(0) # Wiagecs to control green component self.frmGreen = Frame(master=mainWnd, borderwidth=2,\ relief-SOLID) self.frmGreen.pack(side="left", padx=5) self.IblGreen = Label(master=self.frmGreen, text="GREEN",\ font=("Arial", 15, "bold"), fg-"green") self.IblGreen.pack() seIf.rbtGreenNone = Radiobutton(master=self.frmGreen, \ text="NONE", variable=self.green, value=0,\ command=self.onButtonClick) self.rbtGreenNone.pack() self.rbtGreenNone.select() self.rbtGreenWeak = Radiobutton(master=self.frmGreen,\ text="WEAK",\ variable=self.green, value=127, command=seif.onButtonClick) self.rbtGreenWeak.pack f) self.rbtGreenStrong = Radiobutton(master=self.frmGreen, \ text="STRONG", variable=self.green, value=255,\ command=self.onButtonClick) self.rbtGreenStrong.pack() # Целая переменная ассоциированная с синей радиогруппой self.blue = IntVar() self.blue.set(0) # Widgets to control blue component self.frmBlue = Frame(master=mainWnd, borderwidth=2, relief=SOL!D) self.frmBlue.pack(side="left", padx=5) self.IblBlue = Label (master=self.frmBlue, text="BLUE",\ font= ("Arial", 15, "bold"), fg="blue") self.IblBlue.pack() self.rbtBlueNone = Radiobutton(master=self.frmBlue, text="NONE",\ variable=self.blue, value=0, command=self.onButtonClick) self.rbtBlueNone.pack() self.rbtBlueNone.select() self.rbtBlueWeak = Radiobutton(master=self.frmBlue, text="WEAK",\ variable=seif.blue, value=127, command=self.onButtonClick) self.rbtBlueWeak.pack() self.rbtBlueStrong = Radiobutton(master=self.frmBlue, \ text="STRONG", variable=self.blue, value=255,\ command=self.onButtonClick) self.rbtBlueStrong.pack() self.rbtRedNone.focus set() # установить фокус ввода 105
# обработчик событий def cnButtonClick(self): mixedColor = "#{0:02x}(1:02x}{2:02x}".format(self.red.get(),\ self.green.get(), self.blue.get()) self.IblMixer.config(bg=mixedColor) Рис. 3-6-3. Color Mixer с радио кнопаками — код. Префиксы позволяют нам использовать несколько раз то же самое имя при кодировании: например: red — это целая переменная, f rmRed — это фрейм, IblRed — это метка. Мы используем префикс rbt для радио кнопок. Когда пользователт щелкает на радио кнопках, соответствующие им значения сохраняются автоматически в ассоциированных переменных: red, green или blue. Затем обработчик события должен просто завершить работу, отформатировать и скомбинировать эти три числа. Урок 7. Списки вариантов (Listbox) и привязка событий Класс Listbox нужен для создания в GUI приложении списка вариантов Как правило, пользователь может выбрать только один вариант из списка, однако существуют и многовариантные списки. В этой книге мы рассматриваем наиболее распространенный одновариантный список. Новая версия приложения Color Mixer будет иметь три списка: для красного, зеленого и синего компонентов цвета; каждая список в свою очередь будет иметь 11 градаций цвета от0% до 100% То есть мы сможем построить 1331 цвет (11 х 11 х 11). Функциональность списка вариантов похожа на функциональность радио группы, однако списки имеют гораздо большеую вместимость, количество выборов, потому что вы можете прокуручивать список используя колесико мышки, сенсорную панель или клавиши ВВЕРХ и ВНИЗ на клавиатуре, если список получил фокус ввода. f Cotoc Mixer □ X Рис. 3-7-1. Color Mixer с тремя списками вариантов. Мы используем вертикальное размещение для главного окна метка для микширования цвета + один фрейм. Внутри фрейма мы используем горизонтальное размещение: красная метка + список вариантов красного + зеленая метка + список вариантов зеленого + синяя метка + список вариантов для синего. 106
Наиболее важные параметры конструктора класса Listbox: • master (родительское окно или фрейм) • height (количество вариантов, которые мы можем видеть) • exportselection (True или False; если True, выбранный вариант теряет подсветку, если вы уходите из списка, если False, подсветка остается, даже если переносите фокус) • xsc г oil command (ссылка на горизонтальную полосу прокурутки, если она присоединена) * yscrollcommand (ссылка на вертикальную полосу прокурутки. если она присоединена) Обратите внимание что конструктор списка не имеет праметра command. Мы присоединим обработчик событий через специальный метод. Наиболее важные методы класса Listbox: * insert () (заполнить список вариантов значениями из списка или кортежа) • selection_set() (выбрать вариант из списка программным путем) • bind () (присоединить обработчик событий) * curselection () (получить из списка выбранный вариант) Головная программа остается без изменений (рис 3-3-1), однако, вам возможно понадобится изменить размер главного окна. Класс Form будет иметь следующие переменные экземпляра: • IblMixer (метка для показа смикшированного цвета) • choices (кортеж с именами, которыми будут заполнены три списка вариантов) • frm6Icems (фрейм, который группирует шесть виджетов горизонтально) • IblRed (метка '’RED") • IbxRed (список выбора для градаций красного) * IblGreen (метка "GREEN") • IbxGreen (список выбора для градаций зеленого) • IblBlue (метка "ВШЕ") • IbxBlue (список выбора для градаций синего) Класс Form будет иметь следующие методы: • __init___() (конструктор) • onChange () (обработчик событий, вызывается, если выбор изменился в любом из трех списков) 107
Мы будем использовать в этом приложении новый прием обработки событий: привязка событий (англ, events binding). Метод bind () может использоваться с любым виджетом и имеет два параметра имя события в угловых скобках и имя оработчика событий Все события в tkinter классифицированы и им назначены стандартные имена. Если сравнить этот новый прием со старым (через параметр command), то новый более продвинут, поскольку обработчик событий будет получать обьект класса Event из которого вы можете извлечь много полезной информации связанной с этим событием; например, вы можете получить ссылку на источник события (адрес е иджета, который сгенерировал, «триггернул событие). Этот подход также более универсален, поскольку не все констругоры виджетов имеют параметр command, но все виджеты могут вызывать метод bind (). from tkinter import * class Form: def init (self, mainWnd): # Миксер self.IblMixer = Label(master=mainWnd, width=15, bg="black",\ fcnt=("Arial", 30)) self.IblMixer.pack(pady=10) # List items self.choices = ("O'", "10%", "20%", "30%", "40%", "50%",\ "60%","70%", "80d", "90%", "100 ") # Frame self.frmOWidgets = Frame (master=mair.Wnd) self.frm6Widgets.pack(pady=10) # Красная группа self.lblRed = Label(master=self.frm6Widgets, text="RED:",\ font=("Arial", 10, "bold"), fg="red") self.IblRed.pack(side-"left", padx=10) self.lbxRed = Listbox(master=self.frm6Widgets, height=5,\ exportselection=False) self.IbxRed.pack(side="left") # populating red list box, selecting the first item seIf.IbxRed.insert(END, * self.choices) # asterisk is used self.IbxRed.selection set(first=0) # binding self.IbxRed.bind("<<ListboxSelect>>", seif.onChange) # Зеленая группа self.IblGreen = Label(master=self.frm6Widgets, text="GREEN:", \ font=("Arial", 10, "bold"), fg="green") self.IblGreen.pack(side="left") self.LbxGreen = Listbox(master=self.frm6Widgets, height=5,\ export selection=Fa±se) self.IbxGreen.pack(side="left") # populating green list box, selecting the first item seif.IbxGreen.insert(END, *self.choices) # asterisk is used self.IbxGreen.selection set (first=0) # binding self.IbxGreen.bind("<<ListboxSelect>>" , self.onChange) 108
# Синяя группа self.IblBlue = Label(master=self.frmGWidgets, text="BLUE",\ font=("Arial", 10, "bold"), fg="blue") self.IblBlue.pack(side="left") self.lbxBlue = Listbox(master=self.frm6Widgets, height=5,\ exportselection=Fal se) self.IbxBlue.pack(side="left") ♦populating blue list box, selecting the first item self.IbxBlue.insert(END, *self.choices) ♦ asterisk is used self.IbxBlue.selection _set(first=0) # binding self.IbxBlue.bind("<<Li stboxSelect>>", self.onChange) ♦event handler def onChange(self, event): r = int(*self.IbxRed.curselecticn()) * 255 // 10 # звездочка g = int(*self.IbxGreen.curselection ()) * 255 // 10 b = int(*self.IbxBlue.curselection()) * 255 // 10 mixedColor = "#{0:02x){1;02x}{2:Q2x}". format(r, g, b) self.IbiMixer.config(bg-mixedColor) Рис. 3-7-2. Color Mixer co списками вариантов — код. Посмотрите тему «Контейнеры данных» (часть 2 урок 11): оператор звездочка (англ, asterisk) используется, чтобы извлечь значения из контейнера; метод curse! ection () возвращает кортеж, поскольку списки с многонариантным выбором также возможны. Урок 8. Полоса прокурутки (Scrollbar) Полосы прокурутки помогают пользователям более эффективно продвигаться через длинный список вариантов. Как и все виджеты, полоса прокрутки должна быть: 1) инстанцирована 2) добавлена на экран методом pack (). Новая версия приложения Color Mixer — такая же как и предыдущая, но добавлены полосы прокрутки, чтобы облегчить пользовательскую навигацию теперь он может прокручивать список не только используя клавиши ВВЕРХ и ВНИЗ, сенсорную панель, колесико мышки, но также посредством перетаскивания бегунка полосы прокрутки. f Color □ X RED: 20% 30% 60% ‘ТОХ GREEN: 8С% BLUE: 1зэ% нс% |?0% Рис. 3-8-1. Color Mixer со списками вариашов и полосами прокрутки. 109
Очевидно, всякая полоса прокрутки должна знать, который список вариантов прокручивать, также полоса прокрутки должна обновлять позицию бегунка, если меняется выбранный элемент списка вариантов То есть мы должны установить двустороннюю связь между двумя виджетами. Главное окно имеет вертикальное размещение: метка + фрейм, но внутри фрейма мы позиционируем 9 виджетов горизонтально (3 метки, 3 списка, 3 полосы прокрутки). Параметры конструтора Scrollbar: • master (родительское окно или фрейм) • orient (ориентация, может быть VERTICAL или HORIZONTAL) • command (ссылка на свойство у view списка вариантов; это означает, что список должен быть инстацирован перед тем, как инстанцируется полоса прокрутки) Класс Form будет иметь следующие переменные экземпляра: * IblMixer (метка для показа смикшированного цвета) * choices (кортеж с именами, которыми будут заполнены три списка вариантов) • frm9Items (фрейм, который группирует 9 виджетов горизонтально) * IblRed (метка "RED") • IbxRed (список для выбора градаций красного) • scrRed (полоса прокрутки для градаций красного цвета) • IblGreen (метка "GREEN") • IbxGreen (список для выбора градаций зеленого) * scrGreen (полоса прокрутки для градаций зеленого цвета) • IblBlue (метка "BLUE") • IbxBlue (список для выбора градаций синего) * scr Blue (полоса прокрутки для градаций синего цвета) Класс Form имеет следующие методы: • init () (конструктор) • onChange () (обработчик событий, вызывается, когда новый выбор сделан в любом из трех списков, обратите внимание, манипулирование полосами прокрутки не меняют выбора как такового, оно только меняет видимую область виджета) Код практически идентичен коду предыдущей версии приложения Color Mixer; единственное новшество — это взаимное связывание списка и полосы прокрутки (эти два операторы выделены). 110
from tkinter imoort * class Form: def init (self, mainWnd): # Миксер self.IblMixer = Label(master=mainWnd, width=15, bg="black",\ font=("Arial", 30)) self.IblMixer.pack(pady=l0) # List items self.choices = ("0%", "10%", "20%", ”30%", "40%*, "50%",\ "601","70%", "80j", "90t", "1001") # Фрейм self.frm9Widgets = Frame(master=mainWnd) self.frm9Widgets.pack(pady=10) # Красная группа self.lblRed = Label(master=self.frm9Widgets, text="RED:",\ font=("Arial", 10, "bold"), fg="red") self.IblRed.pack(side="left") self.IbxRed = Listbox(master=self.frm9Widgets, height=5,\ exportselect ion-False) self.IbxRed.pack(side®"left") self.IbxRed.insert(END, *self.choices) self.IbxRed.selection set (first=0) # Инстанцирование и привязка полосы прокрутки к списку вариантов self.scrRed = Scrollbar(master=self.frm9Widgets,\ orient=VERTICAL,\ command=self.IbxRed.yview) # Привязывание списка вариантов к полосе прокрутки self.IbxRed.config(yscrollcommand=self.scrRed.set) # Визуализация и растягивание полосы прокрутки self.scrRed.pack(side="left", fi1l="both") # Привязка обработчика событий self.IbxRed.bind("<<ListboxSelect>>", self.onChange) ft Зеленая группа Рис. 3-8-2. Приложение Color Mixer co списками вариантов и полосами прокрутками — код (начало). В методе pack () мы использовали новый параметр fill, он растягивает виджет на осе отведенное ему пространство, вы можете растягивать горизонтально, вертикально или в обоих направлениях. 111
Урок 9. Поля ввода (Entry) и табличное размещение (Grid Layout) Поля ввода поистине вездесущи, — нам всегда нужно обрабатывать то, что пользователь печатает. Вы можете рассматривать виджет Entry как GUI версию консольной функции input () (см, часть 1 урок 6). Говоря об физическом размещении виджетов на экране, существует альтернатива методу pack () — метод grid (); мы как бы мысленно делим пространство формы на строки и колонки и затем позиционируем виджеты в конкретные ячейки этой таблицы; поэтому наиболее важными параметрами метода grid () являются строка (англ, row) и колонка (англ, column). Довольно часто нам нужно спозиционировать виджет в центр строки, в таком случае нам понадобится параметр columnspan. «Span» по русски — охват. В новой версии приложения Color Mixer мы разбиваем экран на 4 строки и 2 колонки. Строки и колонки нумеруются с нуля. Метка для микширования цвета будет охватывать две колонки. / Colo' Mixer — П X REP tO-255): IX < GRLEN (0-255) 255| BLUE (0-255): 170 Рис. 3-9-1. Color Mixer с 3-мя нолями ввода (зеленое ноле имеет фокус). Существует пара проблем, когда мы программируем поля ввода: 1) пользователь волен печатать все, что ему заблагорассудится, довольно часто нам требуется -;алидация(проверка) вкода; 2) пользователь может завершить ьвид по разному — посредством нажатия клавиши ВВОД (англ. RETURN или ENTER), клавиши TAB или посредством мышиного щелчка за переделами поля ввода. Очевидно, только одно поля ьнода в каждый текущий момент времени может иметь фокус Поле с фокусом имеет мигающий курсор. Когдв вы нажимаете клавишу TAB или щелкаете за переделами поля ввода, случается событие FocusOut, но когда вы нажимаете клавишу ВВОД, вы остаетесь в этом поле, событие FocusOut не происходит Удобно назначить для поля ввода ассоциированную переменную, она обновляется автоматически когда пользователь обновил поле в-ода. И наоборот, программист может обновить поле ввода посредством изменения ассоциированной переменной. Ассоциированная переменная может быть инициализирована через функцию StringVar (), затем она может считываться и записываться через методы get () and set (). 112
Параметры конструктора виджета Entry: * master (родительское окно или фрейм) • textvariable (ассоциированная переменная) Класс Form будет иметь следующие переменные экземпляра: * IbiMixer (метка для демонстрации смикшированного цвета) * IblRed (метка "RED (0-255):") • red (строковая переменная ассоциированная с «красным» полем ввода ) • entRed (поле ввода для красного) • IblGreen (метка "GREEN (0-255):") • green (строковая переменная ассоциированная с «зеленым» полем ввода) * entGreen (поле ввода для зеленого) • IblBlue (метка "BLUE (0-255):") • blue (строковая переменная ассоциированная с «синим» полем ввода ) • entBlue (поле ввода для синего) Класс Form будет иметь следующие методы: • init () • onReturn • onFocusOut • onChange () • validateColor() ошибка) (конструктор) (обработчик событий) (обработчик событий) (метод вызываемый обеими обработчиками) (валидатор поля ввода, возвращает код цвета или -1, если Рис. 3-9-2. Взаимосвязь методов класса Form 113
from tkinter import * class Form: def init _ (self, mainWnd): # Mixer, row 0, columns 0-1 self.IblMixer = Label(master=mainWnd, width=10, bg="black", \ font»("Arial", 30)) self. 1ЫМixer .grid (row=0, column=0, columnspan=2, padx=15,\ pady=l5) # Строка 1 self.lblRed = Label(master=mainWnd, text="RED (0-255) font»("Arial", 10, "bold"), fg="red") self.IblRed.grid(row=l, column=0, padx=5, pady=5) self.red = StringVarO self.entRed = Entry(master=mainWnd, textvariable=self.red) self.entRed.grid(row=l, column=l, padx=5, pady=5) self.entRed.bind("<Return>", self.onReturn) fl 2 обработчика self.entRed.bind("<FocusOut>", self.onFocusOut) self.red.set ("0") # Строка 2 self.IblGreen = Label(master=mainWnd, text="GREEN (0-255) :",\ font»("Arial", 10, "bold"), fg="green") self.IblGreen.grid(row=2, column=0, padx=5, pady=5) self, green = StringVarO self.entGreen = Entry(master»mainWnd, textvariable=self.green) self.entGreen.grid(row=2, column=l, padx=5, pady=5) self.entGreen.bind("<Return>", self.onReturn) self.entGreen.bind("<FocusOut>", self.onFocusOut) self.green.set("0") # Строка 3 self. IblBlue = Label(master=mainWnd, text.="BLUE (0-255) :",\ font»("Arial", 10, "bold"), fg="blue") self. 1Ы В Lue. grid (row=3, column=0, padx=5, pady=5) self, blue = StringVarO self.entBlue = Entry(master=mainWnd, textvariable=self.blue) self.entBlue.grid(row=3, column=l, padx=5, pady=5) self. ent В Lue .bind ("<.Return>", self . onReturn) seIf.entBlue.bind("<FocusOut>", self.onFocusOut) self.blue.set("0") self.entRed.focus_set() fl установить фокус на красном поле def onReturn(self, event): event.widget.tk focusNext().focus set() # переносим фокус self.onChange() def onFocusOut(self, event): self.onChange() 114
def onChange(self): r = self.validateColor(self.red.get()) if r == -1: self.red.set("ERR!" ) return g = self.validateColor(self.green.get()) if g ----1: self.green.set("ERR!" ) return b = self.validateColor(self.blue.get()) if b == -1: self.blue.set("ERR! ") return mixedColor = "#{0:02x}{1:02xJ{2:02x}".format(r, g, b) self.IblMixer.config(bg-mixedColor) def validateColor(self, userinput): try: result = int(userinput) # конвертация, «рискованная» строка except: result = -1 # конвертация не прошла else: if result < 0 or result > 255: К конвертация успешна result = -1 return result Рис. 3-9-3. Color Mixer с 3-мя полями ввода — код Мы присоединим к каждому полю свода два обработчика событий 1) если нажата клавиша ВВОД (ENTER), 2) если поле ввода потеряло фокус. В обоих случаях мы пересчитываем смикшированный цвет. Фокус может быть потерян, если пользователь нажал кнопку TAB или щелкнул мышкой на другом поле ввода Поскольку нажатие клавиши ВВОД не сдвигает фокус, мы делаем это прогоаммно. Вы видите здесь, как можно идентифицировать источник события посредством извлечения из обьекта event свойства widget. Метод onChange () проверяет содержимое трех полей, если оно не корректно, то оно заменяется на сообщение "ERR!", микшируемый цвет не обновляется. Если все поля валидны, цвет обновляется. Метод validateColor () проверяет пользовательский взод, он возвращает -1 (ввод не валиден) или RGB код в диапазоне от 0 до 255. Метод использует кастинг строки в целое число. Кастинг может не сработать в силу очевидных причин: пользователь напечатал нецифровые символы. В таком случае, возбуждается исключение (часть 1 урок 9). Поэтому мы используем оператор try-except-else чтобы «поймать» исключение. Таким образом мы предотвращаем «краш» (англ, crash), крушение программы. 115
о
Part 1. fundamentals Lesson 1. Getting Started Downloading Software python Python is a programming language that leu you work quickly and integrate systems more effectively, >» team More Figure 1-1-1. Python Software Foundation (PSF) official Web site. Go to python. org, download and install the Python IDLE for Windows, Mac OS, or Linux (various Linux distributions may have Python already preinstalled). Definition: IDE (or IDLE} stands for Integrated Development Environment (or Integrated Development and Learning Environment) To put it simply, IDE is a programmer's toolset. There are many popular IDEs: MS Visual Studio, MS Visual Studio Code, PyCharm, Android Studio, Xcode, Jet Brains etc. Some IDEs are tailored for a certain programming language; some are more universal and can be used for a variety of languages A typical IDE consists of at least three components: * Code editor • Compiler • Tester and Debugger So, you can type and edit the code, compile it (convert to the binary machine instructions), test the program and (if there are some errors or bugs), debug it. More advanced IDEs may also have built- in graphics editor to design GUI (Graphic User Interface) widgets, code generator (ready- to-use coding templates to speed up the development), project management utilities, version control tools etc. 9
Your First Python Program 6 Start the IDE. 7. Type the following code in the editor (you may provide your own name and city) print("Hello Python") print("I am Andrei") print ("I live in Toronto") 8 Select File->Save menu command in the editor window (or press CTRL+S keyboard shortcut). Save your first program in whatever folder and under whatever name you prefer. All Python source files must have .py extension 9. Select Run->Run Module menu command (or press F5). 10. You will see a new window (it is called console or shell), and three lines of text: 1) hello, 2) your name, 3) your city. Congrats, if you did it! As you see. Python IDE works in two windows editor (for typing code) and shell (for testing the program). You may rearrange two Python IDE windows any way you like For instance, editor on the left and console on the right, or you may maximize both windows and switch between them (ALT+TAB). * IСм-л ^ihtnleet ранг ktsenlljeot Cejqs, lprint(" i< He Python") 2 print("I am Andrei") 3 print("I live in Toronto") 4 If* Ш - H*.______________________________ Python 3.11.5 (Lags/v3.11.5:cce6ba9, Auq 24 2C23, 14:38:34) |MsC v.1936 64 bit (AMD64)] on Win32 Type "help", "copyright", "credits" or "lice nse()“ tor more information. »> - RESTART: G:\EVERYTHJNG\Courses\Python\Pyth onBouk\parl.1\lcsson01 int ro\f irst.py Hello Python I air Andrei I live in Toronto >>> Figure 1-1-2. My first Python program - the editor (on the left) and the shell (on the right). Fixing Errors What if I made a mistake? For instance, I forgot to type the closing parenthesis in the first print () ? Python compiler will highlight in red color the place where mistake approximately happened and show the message with the brief error's explanation. 10
4 first.py» E\EVERYTHING\Couf5«\Python\PythonBook\pert1\lesson01Jmro\fir«tpy(3.k. — OX File Edit Format Run Options Window Help print|("Hello Python" print("I am Andrei") print("I live in Toronto") [л SyntaxError X T was never closed I 01 I Lrtl Col-C Figure 1-1-3. Syntax error found. Sometimes, Python doesn't see the issue right away, but it finds it later, during the program execution Let's say, we typed prin () instead of print (). Here is what happens now when you run the program: Python 3.13.2 (tags/v3.13 2 4f8bb39, Feb 4 2025, 15 23 48) [MSC v-1942 64 bit (AMD64)] on Win32 Type "help", "copyright", "credits" or "license()" fo r more information. »> = RESTART: E:\EVERYTHlNG\Courses\Python\FythonBook\pa rtl\lesson01_intro\first py Traceback (most recent call last): File "E:\EVERYTHING\Courses\Pythcn\PythonBook\partl \lesr.on01_intro\f irst .py" , line 1, in <module> prin("Hello Python") NameExror: name ’prin* is not defined Did you mean: 'print1? >» Figure 1-1-4. Syntax errors could be found during the program execution. There is a red message explaining where approximately (line number) and why the mistake happened There are two types of errors: • Syntax • Semantic (logical) Syntax errors are formal you misspelled certain Python statements or keywords, missed a separating character, didn't close parenthesis properly etc. Such errors can be easily identified by the computer. Logical errors are subtler; your program was not designed properly, and it doesn't work the way you expected. Only human can identify and fix those errors 11
When you finish the lesson, close both windows. If you need this program later, you can find it using File->Recent Files menu command or Fil e->Open.. command. IDE — FAQ How to change the editor and the shell font size? Select the Options->Conf igure IDLE menu command, you will see dialog Settings with six tabs, select the Fonts tab, open the Size drop-down list, pick the font size you like. I've tried my program many times, as a result my shell has too much text; how do I clean it? Close the shell, don't close the editor; run the program again (F5). When I start the IDE I see the shell, not the editor. Can I change the settings so that the editor will be the default window? In the Settings dialog, select the Windows tab; you will see two At Startup choices: Open Edit Window or Open Shell Window; select Open Edit Window. I like dark colors for coding. How would I switch from light theme to dark? In the Settings dialog, select the Highlights tab; open IDLE Classic list and change the theme to IDLE Dark. How to display line numbers in the editor? Select menu command Options->Show Line Numbers. Lesson 2. Statements Definition: statement is a programmer's request for computer to fulfil a certain task. Definition: computer program (or script) is a sequence of statements. So, any computer program is similar to a series of English sentences in imperative mood: Get! Add! Subtract1 Compare! Show' Go to! etc. So, to put it simply, programmers' job is to write statements The computer executes statements one by one from the very top of the program to the very bottom. In our first lesson we wrote a very simple program, and it consisted of three statements only. There are two types of statements: • Simple • Compound 12
Simple statements occupy one line of code and cannot contain other statements. For example: • Assignment statement • Function call statement • Interruption statements (return, break, continue) Our first program consisted of three library function print () calls (we will discuss functions in Part 1 Lesson 10). Compound (or control) statements occupy many lines of code and contain simple statements. Examples: • if-else statement • for statement • while statement * try-except statement. Compound statements may be nested. Calculator Let's take a look at the simple Python program that accepts two numbers and the math operation symbol (+, * or /). Then it calculates the sum, the difference, the product, and the quotient of those two numbers. Just type the program ano try it. You don't have to understand all the details right now; the point is just to demonstrate what statement is. import sys # Part 1 - variables and assignments numberl = 0.0 operation = "" number2 = 0.0 result = 0.0 buffer = "" # Part 2 - input, library functions calls print("Type the first number:") buffer = input() numberl = float (buffer) print("Type the second number:") buffer = input!) number2 = float(buffer) print("Type the operation (+, /, *):") operation = input() 13
# Part 3 - if-else statement beginning if operation == result = numberl 4- number2 print ("Will add...") elif operation == : result = numberl - number2 print ("Will subtract...") elif operation == result = numberl * number2 print ("Will multiply. ") elif operation == "/": result = numberl / number2 print ("Will divide...") else: print("Wrong operation!") sys.exit () # if-else statement end # Part 4 - printing results print(numberl, operation, number2, " = ", result) Figure 1-2-1. Calculator — code. Lines with the # sign are comments, they are created for humans and explain the program logic. They are ignored by the Python interpreter. The import sys line is not a statement; it is a directive to connect to our program library module Library modules are to save our tfme, they implement the most common, standard programming tasks. Analysis The program consists of four parts. 1 . We have five variables' declarations and five assignment statements. 2 Then we ask the user to provide us with two numbers and a math operation. In order to do that we call different library functions-print (), input (), and float (). We may combine in one statement a library function call and an assignment. There are 8 simple operators in the second part of the program. 3 The compound if-else statement occupies 15 lines of code. You clearly see that inside compound if-else statement there are simple statements - assignments, and function calls. Simple statements within a compound statement are indented (that's important!). We call a group of indented statements block. 4 . We print the results. 14
Type Lhe first number: 12 Type the second number: 13 Type the operation (+, /, *): Will subtract... 12.0 - 13.0 = -1.0 »> Figure 1-2-2. Calculator — test. Sometimes simple statements are too long to fit in one line Use the backslash symbol to split a long simple statement into 2 or more lines: movieName = "The Assassination of Jesse James " + \ "by the Coward Robert Ford" Lesson 3. Variables, Literals, Types and Assignments Being a programmer, you have to process various types of data That's why we need variables and literals. If statements are elementary units of action, variables and literals are elementary units of data Definition: variable is a named area of computer memory (RAM). You may picture RAM as a two-columns table. All the memory cells (bytes) are numbered, i.e, they have addresses. Certain values are written by each address. Programming in concrete addresses is complicated, tiring and error prone process. Variables with meaningful names facilitate and make it simpler for a programmer to access RAM. Addresses Values myVar —< ... ... 1012 99 1013 -100 1014 0 1015 55 ... ... Figure 1-3-1. RAM and variablemyVar. 15
Any variable has: • name • value • type Names are created by programmers. Name is the variable's ID, value is the variable's current content and type it is the variable's data format. The name of the variable doesn't change during the program execution, but both type and value can change. The name of the variables can consist of letters and digits, but the first character cannot be a digit. Spaces are not allowed in the variables' names; if the variable name consists of many words, you may separate them using underscore or capitals- my_long_variable_name myLonqVari ableName. Use whatever naming style you like, but be consistent, don't mix in one project different naming styles. Variables names are case-sensitive. For example, price and Price are two different variables. There are four essential types of variables in Python. We call them primitive (or built-in) types. • integer (int) • floating-point (float) • string (str) • boolean (bool) int variables are needed for saving integer numbers, float variables — for numbers with the fractions, str variables — for text, and bool variables — for True/False values. Definition: literal is an explicit, immutable piece of data Literals don't have names, like variables, but they still have values and types. Examples: 5 — an int literal 9 9.8 — a float literal "abed" — a str literal True/False — bool literals. 16
Definition: assignment statement (=) assigns a new value to a variable, On the left there is a variable, on the right — new value. Variables and Literals Demo # 5 variables are declared and initialized firstName ="John" lastName = "Brown" age = 25 averagePythonMark = 88.5 honorAward = True # 5 prints print(firstName) print(lastName) print(age) print(averagePythonMark) print(honorAward) Figure 1-3-2. Variables and literals — code. As you can see, we declared five variables and assigned five initial values to them. Assigning initial values to variables is called initializing. In other words, we have five assignment statements. Sometimes the first assignment statement is called variable initialization. So, we have 5 assignment statements where the variables are on the left of the equal sign, the literals are on the right If after one assignment, you do another assignment later on, the old variable value is lost and cannot be recovered. So, int literals consist of digits only; float literals have an integer part, the period symbol, and a fraction; str literals have text in double quotes or single quotes, and finally, bool literals are presented by the keywords True and False. After assignments we print the variables' values using library function print (). This function can print one vanable/literal, or many variables and literals separated by comma Definition: keyword (or reserved word) is the word that has predefined, fixed meaning in programming language The number of keywords is limited Programmers cannot use keywords to name their own variables, functions (Part 1 Lesson 10) or classes (Part 2 Lesson 1). Keywords examples, if, else, while, import, except, True, False. John Brown 25 88.5 True Figure 1-3-3. Variables and literals — test. 17
You can assign a literal to a variable, one variable to another variable, but you can't assign anything to a literal, because literals cannot be changed by definition. Literals are so-called r-values, they may appear only on the right side of the assignment. On the other hand, variables are so-called I-values, they can appear on both sides of the assignment. These are incorrect statements 33.3 = 44.4 "abc" = anyVariable If you run such a program, you will ger error messages The following program shows us that Python variables can change their types (the Python library function type () is used to display the type of the variable). testVar = 99 print(type(testVar)) testVar = 99.99 print(type(testVar) ) testVar = "99.99" print(type(testVar ) ) testVar = False print(type(testVar)) Hgure 1-3-4. (.'hanging tjpes — code. <class <class Cclass cclass 1int•> ’float1> 1 st r 1 > ' bool1 > Figure 1-3-5. Changing types — test. Casting You may convert variables from one type to another using four library functions: int (), float (), str (), and bool (). We call those functions casting functions For example, the following 2 statements will convert a floating-point variable to a string (in other words 99.99 is reformatted as five characters "99.99"): myVar = 99.99 myVar = str(myVar) The following 2 statements will convert a floating-point variable to a boolean variable (so, 99.99 becomes True, because only 0 . C is interpreted as False) myVar = 99.99 myVar = bool(myVar) 18
Casting may produce unexpected results; be careful. For instance, casting numbers to string is always safe, but casting strings to numbers may cause the program's termination because not any string presents a number. Lesson 4. Operations Definition: operations are special symbols that allow programmers to manipulate data. Usually, an operation is presented in Python as one (+, *, /) or two special symbols (<=, ==) (no space between!); also, operations can be Python keywords, (and, or, not). Using operations, programmers can add, subtract, compare etc. We use terms operation and operator interchangeably Essential Python operations belong to three groups: • Arithmetic • Comparison • Logical Definition: operands are the vanables/literaIs an operation is applied to Definition: binary operation is applied to two operands. Definition: unary operation is applied to one operand. Arithmetic Operations • addition (+) • subtraction (-) • multiplication (*) • division (/) • integer division (//) • remainder (%) • power (**) • minus (-) • increment (+=) • decrement (-^) Arithmetic operations operate on int or float operands and produce int or float results. Symbol m Python is overloaded, i e it has two meanings, depending on context if there is only one operand on the right, it is minus, if there are two operands on the left and the right, it is subtraction 19
numl = 7 num2 = 2 result = numl + num2 print(numl, "+", num2, ”=", result) result = numl - num2 print(numl, num2, "=", result) result = numl * num2 print(numl, "*", num2, "=", result) result = numl / num2 print(numl, "/", num2, "=", result) result = numl // num2 print(numl, "//", num?, result) result = numl % num2 print(numl, "%", num2, "=", result) result = numl ** num2 print(numl, num2, result) result = -numl print ("-", numl, result) Figure 1-4-1. Arithmetic operations —code. 7 + 2 = 9 7-2 = 5 7 * 2 = 14 7 / 2 = 3.5 7 // 2 = 3 7 % 2 = 1 7 ** 2 = 49 - 7 = -7 »> Figure 1-4-2. Arithmetic operations— test. 2П
Name Symbol(s) Operands Type Number of operands Result Type Addition + int/f1 oat 2 int/float Subtraction — int/float 2 int/float Multiplication * int/float 2 int/float Division / int/float 2 int/float Integer Division // int/float 2 int/float Remainder % int/float 2 int/float Power ★ ★ int/float 2 int/float Minus « int/float 1 int/float Increment += int/float 2 int/float Decrement — = int/float 2 int/float Figure 1-4-3. Arithmetic operations summary. Increment example: myVar = 5 myVar += 10 # the same as myVar = myVar + 10 This short program increases the variable value by 10, making it 15. Note that increment and decrement operations are created just for your convenience, you don't have to use them. You can use classic syntax instead (which is shown in the comment). Also note that arithmetic operations don't change their operands except for increment/decrement; they both change their left operand. Comparison (Relational) Operations * equal (==) • not equal (!=) * greater than (>) • greater than or equal to (>=) • less (<) • less than or equal to (<=) 21
Comparison operations are binary and operate on all types of variables and literals, however both operands should be ol the same type. Comparison operations produce bool results (True/False). numl = 22 num2 = 20 result = (numl == num2) print("Is 22 equal to 20?", result) result = (numl != num2) print("Is 22 not equal to 20?", result) result = (numl > num2) print("Is 22 greater than 20?", result) result = (numl >= num2) print("Is 22 greater than or equal to 20?", result) result = (numl < num2) print("Is 22 less than 20?", result) result = (numl <= num2) print("Is 22 less than or equal to 20?", result) Figure 1-4-4. Comparison operations — code. Is 22 is equal to 20? False Is 22 is not equal to 20? True Is 22 greater than 20? True Is 22 greater than or equal to 20? True Is 22 less than 20? False Ts 22 less than or equal to 20? False Is ' abc ’ less than ’ABC'? False »> Figure 1-4-5. Comparison operalions — test. 22
Name Symbol(s) Operands Type Number of operands Result Type Equal === any 2 bool Not equal ; = any 2 bool Greater than > any 2 bool Greater than or equal to any 2 bool Less than < any 2 bool Less than or equal to any 2 bool Figure 1-4-6. Comparison operations summary. Comparison operations are used intensively in if statements (Part 1 Lesson 7} and loop statements (Part 1 Lesson 8). Comparing Strings Strings are discussed in Part 1 Lesson 13 But, in essence, string is a set of characters. You access characters in a string using square brackets and the character number (the first character has number 0, the second 1 and so on). Strings are compared, based on characters codes Any character has an integer ASCII code. ASCII stands for American Standard Code for Information Interchange. Let's say, we need to compare strl and str2 So, the first two characters are compared, if the strl [ 0] is less than str2 [0], the whole strl is considered to be less than str2, regardless of how long they are If the first characters are equal, the second characters are compared; the whole process is done when the end of the shortest string is reached The string "abc" is less than the string "abed" Capital letters have smaller ASCII codes than small letters. For instance, "a" string is greater than "A" string. An empty string (" ") is less than any not emptystring 23
Dec Hex Name Char (trichar Det Hex Char Dec Hex Char Dec Hex Char 0 0 Null NUL CTRL-0 12“ Space 64 40 96 60 1 1 Stan of head-no SOH CTRL-A 33 21 I 65 41 A 97 61 a 2 2 Start or text STX CTRL-8 34 22 «* 66 42 В 96 62 b 3 3 End of tert ETX CTRL-C 35 23 • 67 43 C 99 63 c 4 4 End of xrmt EOT CTRl-D 36 24 $ 68 44 D 100 64 d 5 5 Enqur> ENQ CTRl-E 37 25 % 69 45 E 101 65 • 6 6 ACK CTRL-F 38 26 a 70 46 F 102 66 f 7 7 Be<i BEL CTRL-G 39 27 71 47 G 103 67 g 8 8 Backspace B$ CTRL-H 40 28 ( 72 48 H 104 68 и 9 9 Honrontaf tab rfT CTRL-l 41 29 ) 73 49 1 IOS 69 I 10 OA Linefeed LF CTRL-J 42 2A • 74 4A J 106 6A J 11 06 Vertical tab VT CTRL-iC 43 2B 75 46 К 107 6B к 12 ОС Form feed FF CTRL-L 44 2C 76 4C L 108 6C 1 13 OO Cam age few CR CTRL-M 45 2D • 77 40 M 109 60 m 14 06 Shift out SO CTRl-N 46 2E 18 4E N 11C bE n 15 OF Shftm St CTRL О 47 2F / 79 4F 0 111 6F 0 16 10 Data Ime escape OLE CTRL-P 48 30 0 80 50 P 112 70 P 17 11 Device control 1 DC1 CTPL-Q 49 31 1 81 51 Q 113 71 Q 18 12 Device control 2 DC2 CTRL-R 50 32 2 82 52 R 114 72 r 19 13 Device control 3 DC3 CTRL S 51 33 3 83 S3 S 115 73 s 20 14 Device control 4 DC4 CTRL-T 52 34 4 84 84 T 116 74 t 21 15 Neg acknowledge NAK CTRL-U 53 35 5 85 55 и 117 75 u 22 16 Syncnronous Kfe SYN CTRL-V 54 36 6 66 56 V 118 76 V 23 17 End of xrmt block Elfe CTRl-w SB 37 7 87 57 w 119 77 V» 24 18 Cancel <4N CTRL-X 56 38 8 66 58 X 120 ?8 X 25 19 End of гпейип EM CTRL-Y 57 39 9 89 59 V 121 79 Y 26 1A Substitute SUB CTRL-2 58 ЗД 90 5A 2 122 7A Z 27 18 Escape ESC CTRL-( 59 38 9 91 56 t 123 7B < 28 1C File separator FS CTRL-\ 60 X < 92 X \ 124 7C 1 29 ID Group separator GS CTRL-] 61 3D 93 SD 1 125 70 > 30 IE Record sepa*ato RS CTRL-*4 62 ЭЕ > 94 SE 126 7E 31 If unit separator US CTRL-. 63 3F 95 SF 127 7F DEL Figure J-4-7. ASCII table. Logical Operations There are three logical operations in Python: • and • or • not They operate on bool variables/literaIs and produce bool results according to the well-known commonsense rules. True and True = True True and False = False False and True = False False and False = False True or True = True True or False = True False or True = True False or False = False 24
not False = True not True = False Operator "not" is the unary operation, and it is applied to the operand on the right. Ы = True b2 = False print("True and False is", Ы and b2) print("True or False is", bl or b2) print("Not True is", not bl) Figure 1-4-8. Logical operations — code. True and False is False True or False is True Not True is False Figure 1-4-9. Logical operations demo — test. Name Symbols Operands Type Number of operands Result Type And and bool 2 bool Or or bool 2 bool Not not bool 1 bool Figure 1-4-10. Logical operations summary Concatenation "+" operation is overloaded in Python, it does two different things: add numbers and combine (concatenate) strings It depends on the context. If the operands are numeric, it is the sum, if they are strings, it is the concatenation. You cannot concatenate a string and a number; a number should be converted to string first using casting. firstName = "John " lastName = "Brown" age = 25 print("Full name is", firstName + lastName) print(firstName + lastName + " is " + str (age) + " years old") Figure 1-4-11. Concatenation — code. 25
Full name is John Brown John Brown is 25 years old »> Figure 1-4-12. Concatenation — test. Name Symbol(s) Operands Type Number of operands Result Type Concatenation + string 2 string Figure 1-4-13. Concatenation summary. Operations - important facts to remember • Operations belong to three major groups: arithmetic, comparison, and logical * Operations are implemented as special symbols or keywords (+, <=, not, and etc.). • Operations can be binary (two operands) or unary (one operand). • All the operations are different, memorize what type of operands they accept, and what type of results they produce. * Operations don't change their operands, except increment and decrement (+= and -=). • Operation '+' is overloaded, it has two meanings: addition and concatenation. • Operation is also overloaded, it has two meanings: subtraction (if there are two operands) and minus (if there is only one). Lesson 5. Expressions The idea behind expressions is simple; we can chain operations Expressions are ubiquitous. We use them a lot. Here is an example. We bought two items, and we need to calculate the price at the counter (including tax and discount). We could do it in three steps, in a very straightforward manner: sumOfTwo = item]Price + item2Price priceWithTax = sumOfTwo * tax total = priceWithTax - 15.0 # applying $15 discount However, there is a more elegant solution, and we could code it in just one line total = (itemlPrice + item2Price) * tax - 15.0 26
We not only shortened the code, but also got nd of two temporary variables (sumOf Two and priceWithTax). So, expressions save programmers a lot of hassle. In the assignment statement above there is an expression after the equal sign (it is highlighted). Definition: expression is a combination of operations Very importantly, in an expression operators can be nested- the result of an operation can be used as an operand for the next operation. The depth of nesting in expression is unlimited. Expressions are calculated from the left to the right. However, inside expressions different operations have different precedence (or strength). For instance, * and / operations are stronger than + and -, and Python executes them first, unless we tell him otherwise by using parenthesis. Parenthesis is used in expressions to Impose the right calculation order. Comparison operations are stronger than logical operators. Which means you don't have to type this: (a < b) and (c < d) You may simplify: a < b and c < d In Python documentation you may find detailed tables with all operations' priorities listed It is nice to know them, but there is no point in memorizing everything Just use parenthesis whenever you are not sure about precedence, and you want to impose certain order of expressions evaluation Expressions — important facts to remember: • A variable is an expression, • A literal is an expression, • An operation taken with its operands is an expression, • Expressions can be nested, • Any expression has a result, • Evaluating expression is the process of calculation of its resulting value, • Any expression has a type (it is the type of its result), • Parentheses are used in expressions to control the order of evaluation. Expressions are bigger lexical units than variables, literals, and operations but they are smaller lexical units than statements (expressions are used in statements) 27
Statement Expressions Variables/Literals Operations Figure 1-5-1. Relationship between statements, expressions, variables/literals, and operators. Lesson 6. Input/Output Library function input () is used to take the user's keyboard input, print () function is used to output data on the screen. You've seen both of them already in our short demos. Let's discuss them in greater detail. Output The library function print () format print(exprl [, expr2] [, ехргЗ] ...) We use square brackets to show optional parameters. Those expressions can be of any type, but when they are printed, they are automatically cast to strings. userAge = 28 print(userAge, " years is equal to ", userAge * 12,\ " months or ", userAge * 12 * 365, " days ", sep="") Figure 1-6-1: print () —code. 28 years is equal to 336 months or 122640 days »> Figure 1-6-2: print () —test. By default, after printing, the print () statement automatically transfers output to the next line, but sometimes you need a few print () outputs to be placed in the same line. You can achieve that by using a special parameter end as an empty string, for instance: print("Hello", end = "") print("John") This way both words are printed in the same line. 28
Also, by default, print () produces an additional blank space between expressions to be printed. If you don't need that, you may use the sep = "" parameter. This way no space characters are added. Input The system library function input () stops the program execution, waits for the user's keyboard input, and then (after the user hits ENTER) saves all the typed characters in a string variable. print("Ycur name?") name = inputO print("Hi ", name, "!", sep = "") print("Your age?") age = int (inputO) # input and casting print("Ycu are", age, "years old.”) Figure 1-6-3: inputO —code. Ycur name? Andrei Hi Andrei! Your age? 33 You are 33 years old. Figure 1-6-4: inputO —lest. Pay attention to the line when you are taking the age. If you need int or float numbers from the user, use int () or float () casting. However, not any string can be converted to a number. In such a case your program may crash, (so-called exception will be raised, exceptions are covered in part 1, lesson 9). So, before casting and to avoid crash, you may validate it, for example, using Python library function isdigit (), which can check if a symbol is a digit. Lesson 7. if Statement if is the Python decision-making compound statement. Based on certain conditions, the computer may perform different tasks, take different paths of program execution. There are three types of if statement’ • One case • Two cases • Multiple cases 29
One Case if Format if boolean expression: statement 1 statement_2 Statement N statement _after Usually, we call the boolean-expression in if statement condition. The computer evaluates the condition, then if it is True, does all the indented statements from 1 to N If the boolean expression is False, the indented statements are ignored, and the execution goes directly to the statement after. The most common operators used in if statement are comparison operators because they produce bool results (True/False). A group of indented statements is called block. The most common Python beginner's mistakes are: • to forget the colon symbol after the condition, • to forget to indent the statements that belong to a block (you may indent statements in a block using TAB key). age =18 if age >= 18: print("You are an adult") print("You are allowed to vote") print ("1st if completed") age = 8 if age >= 18: print ("You are an adult") print("You are allowed to vote") print("2nd if completed") Figure 1-7-1: one case if — code. You are an adult You are allowed to vote 1st if completed 2nd if completed »> Figure 1-7-2: one case if — test. As you see, the first block of two print () statements was executed, because the user is older than 18, but the second block of two print () statements was bypassed, because he is only 8 years old. Very often we use logical operators in the if statement: and, or, not This way we may combine conditions. 30
income = 60000.0 if income > 53359.0 and income <= 106717.0: print("Your tax brackets are 20.5%") print("You will pay from $", income, " income $", income * 0.205,\ " taxes", sep="") print("if completed") Figure 1-7-3: one case if statement with two conditions — code.. Your tax brackets are 20.5% You will pay from $60000.0 income $12300.0 taxes if completed »> Figure 1-7-4: one case if with two conditions — test. According to Lesson 2 terminology, if statement is a compound statement. So, it may contain other statements, for example, another if. income = 60000.0 if income > 53359.0: if income <= 106717.0: print("Your tax brackets are 20.5%") print("You will pay from income, " income $",\ income * 0.205, " taxes", sep="") print("if completed") Figure 1-7-5: nested if statement — code. This version of the program works exactly the same as the previous one, but, probably, the first one is preferable because it is shorter and is easier to understand. Two Cases if Format if boolean_expression: statement^! statement _2 sta tement_N else: statement N-+1 statement N+2 statement _N+M statement after 31
Computer evaluates the boolean _expression, and if it is True, does all the indented statements from 1 to N; the second block of statements after else is ignored, and we go to the statement_af ter. If the bool ean express! on is evaluated to False, the first block is ignored, and the second block after else is executed, and we go to the statement after . One case if statement can be empty (computer may do nothing), two cases if cannot be empty, because one case or the other is always done. However, they are never done together. income = 55000.0 if income > 53359.0 and income <= 106717.0: print("Your taxes are 20.5%") print("You will pay", income * 0.205) else: print("Your taxes are not 20.5%”) Figure 1’7-6: two cases if — code. So, when you run this program, the first two messages are printed, the third message is ungored. Multiple Cases if Format: if boolean expression 1: block of_ statements _1 elif boolean expression 2: block_of_statements_2 elif boolean_expression_3: else: block of statements N statement after Sometimes, we call the multiple cases if statement if-else ladder. First of all, the boolean expression 1 is evaluated, if it is True, the first block is executed, and computer jumps to rhe statement after. If the bool ean_ express! on 1 is False, we are going one step down and evaluate the boolean_expression_2, if it is True, the second block is done, and computer jumps to the s tatemen t af ter. Finally, if none of the expressions are True, the else block is executed. So, checking conditions one by one, we are tike going down the ladder step by step. That's why the "ladder" metaphor. 32
Here's an example: alpha = "" print("Type the grade:") grade = float(input()) if grade >= 80.0: alpha = "A" elif grade >= 70.0: alpha = "B” elif grade >= 60.0: alpha = "C" elif grade >= 50.0: alpha = "D” else: alpha = " F" print("The alpha:", alpha) Figure 1-7-7: Multiple eases if — code. Type the grade: 51 The aIpha: D Figure 1-7-8: Multiple cases if — test. The else case in if-else ladder is optional If we don't have it, the whole ladder may be empty, we are falling through. In other words, we do not assign alpha anything, if the user provides percentage less than 50 alpha = "" print("Type the grade:") grade = float(input()) if grade >= 85.0: alpha = "A" elif grade >= 70.0: alpha = "B" elif grade >= 60.0: alpha = "C" elif grade >= 50.0: alpha = "D" print("The alpha:", alpha) Figure 1-7-9: Multiple cases if without else — code. Lesson 8 Loops Looping in programming means asking computer to repeat the same or almost the same job many times. One loop repetition (or just one step of the loop) is called iteration. There are two types of loop statements in Python* • for • while 33
for Loop The for loop is to repeat a task certain number of times; in Python for loop relies on the system function range () (we cover functions in Chapter 1, Lesson 10); function range () gives us a set of integer numbers, it may have one parameter (stop), two parameters (start and stop), or three parameters (start, stop and step): • range(stop) • range(start, stop) • range(start, stop, step) So, the range with one parameter includes integers from 0 to stop - 1. The range with two parameters includes integers from start, to stop - 1. The range with three parameters includes integers from start to stop - 1, but you increase numbers not by one, by step. range () — important thiiigs to remember: • start is included into the range, but stop is excluded. • range can be empty, for instance, range (0) doesn't contain any numbers. for loop format: for int_variable in range (...) : b1ock of statements We call the line with keyword for as a loop header and the indented block of statements as a loop body. The block of statements will be repeated for each number in the range If the range is empty, nothing is done. print("*** range(5) ***") for i in range(5): print(i) print("*** range(l, 5) ***") for i in range(l, 5): print(i) printf*** range(l, 5, 2) ***") for i in range(1, 5, 2): print(i) print("*** range(5, 0, -1) ***") for i in range(5, 0, -1): print(i) print("*** range(O) ***") for i in range(9): print (i) Figure 1-8-1; for loop — code. 34
о 1 2 3 4 * ** range(1, 5) *** 1 2 3 4 * ** range(l, 5, 2) *** 1 3 * ** range(5, 0, -1) *** 5 4 3 2 1 * ** range(0) *** »> Figure 1-8-2: for loop — test. while Loop while loop is more universal than for loop; it can be used when the number of iterations is not yet known ahead of time. while loop format. while bool_expression: block of statements The block of statements is executed for as long as bool expression is True. As soon as the bool_expression becomes False, loop is finished, and the control is transferred to the next statement after the loop i = 1 while i <= 10: print (i) i = i + 1 print("Loop finished”) Figure 1-8-3: while loop — code. 35
1 2 3 4 5 6 7 8 9 10 Loop finished Figure 1-8-4: while loop — test. If bool expression is False right away, at the beginning of the loop, then the whole while statement is empty, we are "falling through" to the next statement. If the expression is True (the loop is working) but it doesn't change during iterations, you face very common programming mistake — endless loop. Sometimes, it feels like your computer is frozen, it is not responding This is because it is doing the same job over and over again For instance, if in the deme above you forget the increment statement (i = i + 1), you will run into an endless loop Finishing Loops Ahead of Time Due to certain circumstances, we may need to finish loop ahead of time Two Python statements may help • break • continue The difference is that break statement terminates the loop and transfers the execution to the next statement after the loop, whereas the cont inue statement terminates only the current loop iteration, but it does not terminate the whole loop. Both statements are used within if statement: when a certain condition is met, the whole loop (or just one loop iteration) is terminated. i = 1 while i <= 10: print (''**********") print ("Number:”, i) print("Square, i * i) if i == 5: break print("Cube:", i * i * i) i = i + 1 print("Loop finished") Figure 1-8-5: break — code. 36
к * * * * Numoer: 1 Square: 1 Cube: 1 ********** Number: 2 Square: 4 Cube: 8 ********** Numoer: 3 Square: 9 Cube: 27 Number: 4 Square: 16 Cube: 64 Number: 5 Square: 25 Loop finished Figure 1-8-6: break — test. As you see, the loop planned tor 10 iterations, in fact did only 5. for i in range(5): print. ("-------") print("Number, i) print("Square, i * if i == 3: continue print("Cube:", i * i print("Loop finished") i) * i) Figure 1-8-7: continue — code. »> Number: 0 Square: 0 Cube: 0 Number: 1 Square: 1 Cube: 1 Number: 2 Square: 4 Cube: 8 Number: 3 Square: 9 Number: 4 Square: 16 Cube: 64 Loop finished Figure 1-8-8: continue — test. 37
As you have seen, the third iteration was interrupted and the cube of three was not printed, but the loop was continuing. Where ts do-while though? If you are already familiar with other programming languages, you may wonder where Python operator do-while is. The point is sometimes we need to do the iteration first, and only after we need to check if we should continue. That's why almost all programming languages feature do-while operator. If the while loop can be empty, the do-while loop works at least once Let's say, we need to take a number from the user, then calculate its square, then decide if we should continue or not. We can emulate do-while construct in Python using loop while True and operator break. # do - while emulation num = 0 yesNo = "" while True: print("Type a number:") num = int(input()) print("The square of", num, "is", num * num) print("Continue? (y/n)") yesNo = input () if yesNo == "n": break Fig. 1-8-9: do-while emulator. Lesson 9. Exceptions Let's try a super simple program (Adder) which adds two integers nl = 0 n2 = 0 пЗ = 0 chars = "" print ("*** Welcome to Adder App! ***") print("nl: ", end="") chars = input () nl = int(chars) print("n2: ", end="") chars = input () n2 = int(chars) n3 = nl + n2 print(nl, "+", n2, "=", n3) Figure. 1-9-1: Adder vl—code. 38
*** Welcome to Adder App! *** nl: -99 n2: 100 -99 + 100 = 1 »> Figure. 1-9-2: ?\dder — test. Everything seems to be fine but what if the user makes a mistake and types a non-numerical character? »** Welcome to Adder App' *** nl: a Traceback (most recent call last) : File "E:\EVERYTHING\Courses\Python\PythonBook\partl\l esson09 exceptions\adderO,py", line 8, in <module> nl = int(chars) ValueError: invalid literal for int() with base 10: 'a' »>l Fig. 1-9-3: Adder vl — crash. That's how exceptions look like. You see that the program started normally, but crashed at line 8, because of the invalid parameter for library function int () (see "Casting" topic in Lesson 3). All the exceptions in Python have standard names; in this particular case it is ValueError . Definition: exceptions are abnormal situations that may happen during the program execution. If an exception is not anticipated and not processed by the programmer the program crashes. We use "throw-catch" terminology when talking about exceptions. If the exception happens, it is thrown or raised by the Python run-time system; if it is handled, it is caught by the programmer. "Exceptions" is quite comprehensive topic, this lesson is just to introduce the most basic concepts. 39
SQL PYTHON JAVA PHP HOWTO W3.CSS C C++ C# BOOTSTRAP REACT M Built-in Exceptions The table below shows built-in exceptions that are usually raised in Python: Exception Description AiithmeUcErfoi Raised when an enoi occurs in numeric calculations AsSfittifinEaai Raised when an assert statement fails AKribuieError Raises when attribute reference or assignment fails Exception Base class for all exceptions EOFError Raiseo when the mputf) method hits an “end of file" condition (ЕОГ) FloatingPomtError Raised when a floating point calculation fails GeneraloiExil Raisea when a qeneialoi is closed (with the closef) method) I TiijfirttrLpi Raised when an imported module does not exist lOdeniaUfinfCLor Raisea when maentabon is not correct InaexErroi Raised when an index of a sequence does not exist Key[ rror Raisea when a key does not exist in a dictionary Keyboardinterrupt Raised when the user presses Ctrl+c, Cti l+z or Delete LookupError Raised when errois raised cant be found Figure 1-9-4. The beginning of the Python exceptions names list from vv3schools.com. Exceptions are called built-in in this table, because programmers can also create their own exceptions. In order to handle exceptions, we use compound statement try-except; it consists of two blocks: 1) potentially dangerous ("risky") block that may throw an exception, 2) the block that catches, takes care of the exception The try-except statement format: try: "risky" block except [exception name]: handling exception block So, if the exception is thrown, except block is executed where we catch it, if nothing bad happens, except block is skipped and we proceed to the line after try-except statement. The name of the exception is optional, if we don't provide it, we react to all the exceptions, if we do, to only specific one. 40
try-except statement logic is similar to if statement logic. But if statement logic is simpler: we check the condition, and we act accordingly, try-except logic is vaguer because we don't know precisely which line inside the block caused the issue (unless there is only one line in a block). If we nest try-except statements, we may have even more complicated logic. Figure 1-9-5. try-except statement logic Let's fix Adder and make it more reliable nl = 0 n2 = 0 n3 = 0 chars = "" print!"*** Welcome to Adder App! ***") while True: print ("nl: ", end="") chars = input() try: nl = int(chars) # "risky" line break except.: print("Bad number! Try again...") while True: print ("n2: ", end="") chars = inputO try: n2 = inc(chars) # "risky" line break except: print("Bad number! Try again...") n3 = nl + n2 print(nl, "+", n2, " = ", n3) Figure 1-9-6. Adder v2 — code. 41
We understand ahead of time that conversion a string to an integer is risky and may crash the app, that's why we place int () function into try block If the conversion is done nicely, we break out of the loop, if it is not, we don't execute break statement, we jump from the try block to the except block, so, we print a message and repeat the loop iteration one more time asking user for a number. *** Welcome to Adder App1 *** First number: abcdef Bad number’ Try again... First number: 12 Second number: -0-0-0- Bad number! Try again... Second number: 13 12 + 13 = 25 »> Figure 1-9-7. Adder v2 - test. Pay attention that except line could be more concrete except ValueError: ValueError is the name of the exception that may be thrown during the numeric conversion. That means we catch only conversion exceptions and ignore others. Sometimes we want to provide an alternative branch of execution for a good -case scenario. So, we may add to try-except statement else block; try-except-else operator consists of three blocks 1) potentially dangerous (risky) block that may throw an exception, 2) bad-case scenario, the exception is caught, 3) good-case scenario, exception was not thrown. try-except-else statement format: try: "risky" block except [exception name]: bad-case scenario block else: good-case scenario block As you see try-except-else statement logic is similar to if-else statement logic. Blocks except and else are mutually exclusive. 42
Figure 1-9-8. try-except-else statement logic. Let's rewrite Adder using try-except-else statement. # variables nl = 0 n2 = 0 n3 = 0 chars = "" print)"*** Welcome to Adder App! ***'•) while True: print ("First number: ") chars = inputO try: nl = int(chars) # "risky" line except ValueError: print("Bad number! Try again...") else: break while True: print("Second number: ") chars = input () t ry: n2 = int(chars) # "risky" line except ValueError: print("Bad number! Try again...") else: break n3 = nl + n2 print(nl, n2, n3) Figure 1-9-9. Adder version 3 - code. We may shorten the program a little by combining two loops and two exception handling statements. 43
# variables nl = О n2 = О пЗ = О chars = "" print("*** Welcome to Adder Арр! ***") while True: try: print("nl: H, end="") chars = inputO nl = inc(chars) # "risky" line print("n2: ", end="") chars = inputO n2 = int(chars) # "risky" line except ValueError: print("Bad number! Try again...") else: break n3 = nl + n2 print(nl, "+", n2, "=", n3) Figure Adder version 4 - code. However, this version has a shortcoming if the first number was good, but the second was not, we still have to repeat both numbers input. We may also add finally block to the try-except-else statement So, try-except- else-f inally statement consists of four blocks: 1) potentially dangerous ("risky") block that may throw an exception, 2) bad-case scenario, the exception was thrown and caught, 3) good-case scenario, exception was not thrown, 4) block that executed after 2nd or 3,d block regardless of the fact that exception was thrown or not. Lesson 10. Functions Basic Terms Definition: function is a named, reusable set of statements. Functions are created by programmers to handle certain tasks Because we can reuse functions, they make code more concise, structured, easier to understand and modify. Functions are essential technique to break down complex problems into simpler problems. Let's take an example. 44
1# function def inition (lines 2-5) 2 def greeting (): # function header 3 print (''************'') # function body (lines 3-5) 4 print("Hello John Brown!”) 5 print (11 ************ ”) 6 7 greeting() # 1st function call greeting() # 2nd function call 9 greeting() # 3rd function call 10 print("Main program is completed”) 11 # Execution order: 734583459345 10 Figure 1-10-1. Function example — code. Hello Juhn Brown! ************ Hello John Brown' ************ ************ Hello John Brown' ************ Main program is completed Figure 1-10-2. Function example — test. It is important to understand that you have to prepare the function first. We call this step function definition Then you can use it. We call this step function call The function definition consists of a function header and a function body. Header is where you name the function, body is where the function inner working is explained. All the statements in the body must be indented. The function definition must be done before the first function call. The program above is executed this way: • Line 7 (the application starts, because function definition is skipped, Г’ function call) • Lines 3, 4, 5 (the function is working, three messages are printed) * Line 8 (2™ function call) • Lines 3,4, 5 (the function is working, three messages are printed) • Line 9 (3rd function call) • Lines 3, 4, 5 (the function is working, three messages are printed) • Line 10 (the main script is completed) Before the function call computer remembers the current execution position. After function is completed, the execution will go to the next statement after the function call. Inside one function we may call another. You may have lots of interrelated, calling each other functions in a real-life application 45
Let's introduce the following terms, just to simplify our further discussion • calling function-parent, • function that is being called - child. In the coding above main program is a parent, function greeting () is a child. Some languages like C/C++ or Java require that the main program must be also defined formally as a function, Python does not require that but leaves it up to the programmer. Parameters and Arguments Quite often parents not only ask kids to do fulfill a task but alsc provide some explanations on certain details. We may pass to a function some extra information using parameters. This way functions could be more flexible and powerful. Let's take an example def greeting(firstName, lastName): print ("****»*******") print("Hello ", firstName, " ", lastName, "!”, sep-"") greetir.g("John", "Lennon") # "Beatles" gang's members greeting("Paul", "McCartney") greeting("George", "Harrison") greeting("Ringo", "Starr") Figure 1-10-3. A function with two parameters — code. Hello Lennon John! Hello Paul McCartney! Hello George Harrison! ******* ***** Hello Ringo Starr! Figure 1-10-4. A function with two parameters — test. firstName and lastName in the function definition are so-called parameters, parameters are placeholders, later on when the function is called, they will be replaced by the arguments. Arguments are variables, literals, or expressions that will be copied into parameters when the function is called. So, the actual last and first names of four famous musicians are arguments. You can clearly see that our function is more flexible now, it can print anybody's name, not just "John Brown" as it did before. 46
Make sure you understand the difference between formal parameters and actual arguments. For example, parameter is a variable, it is never literal or expression, but an argument quite often could be. For example, the first argument here is an expression, the second is the string literal: greeting("Dear " + "John", "Lennon") Keyworded Arguments and Default Arguments When you call a function, you can use parameters names with the equal sign in front »f the arguments. For example greeting (firstName="John", lasrName="Lennon") Yes, the code is a little longer, but it is more self- explanatory An interesting detail, — when using this technique (keyworded arguments), you may position the arguments in any order, they will be still processed properly. greeting(lastName="Lennon", firstName="John") You may also define for function arguments default values, so when an argument is nor provided, the default value is substituted. def greeting(firstName="???", lastName="???"): print с************") print("Hello ", firstName, " ", lastName, "!", sep="") greetingO # question marks will be printed by default Figure 1-10-5. A function with default arguments — code. Hello ??? ???’ »> I Figure 1-10-6. A function with default arguments — test. Both keyworded and default arguments are used intensely in Graphical User Interface (GUI) applications with tkinter library (Chapter 3). Keyworded arguments help to make code easier to understand. Default arguments allow us to shorten significantly library functions calls. Premature Exit Quite often, there are circumstances when it doesn't make sense to continue the function execution, we have to get out ahead of time. That's when we need return statement The return statement immediately terminates the function execution, and we go to the next statement after the function call, Let's say that our greeting should be printed only if not empty names are provided. 47
def greeting(firstName, lastName): print("************”) if firstName == print("No first name provided...") print("Function terminated") return if lastName == print("No last name provided!") print("Function terminated") return print("Hello ", firstName, " ", lastName, "!", sep="") greeting("", "Lennon") greeting("Paul", "") greeting("George", "Harrison") greeting("Ringo", "Starr") Figure 1-10-7. return demo — code. ************ No first name provided... Function terminated No last name provided! Function terminated ii**i*iii**i Hello George Harrison! Hello Ringo Starr! Figure 1-10-8. return demo — test. Returned Values If a parent may talk to a kid. why is a kid not allowed to talk back? Arguments are to pass data into a function, returned values are to pass data from the function. Any function may return to the calling function a number, a string, a boolean, an expression It is achieved through return statement. Let's take an example def average(numl, num2) : sumOfTwo = numl + num2 averageOfTwo = sumOfTwo / 2.0 return averageOfTwo print(average(5.0, 3.5)) markl =90.5 # global variable mark2 =67.5 # global variable averageMark = average(markl, mark2) print(averageMark) # two parameters # local variable sumOfTwo # local variable averageOfTwo # returned value Figure 1-10-9. Returned value — code. 48
4.25 79.0 »> Figure 1-10-10. Returned value — test. As you can see, if a function returns the result of its work, you can use such call on the right side of the assignment. This way a parent saved what a child did: n3 = average(nl, n2) Also, you may use the returned value in expressions print(average(nl, n2) * 100.0 + w%") Parent Arguments I Returned Value Child Figure 1-10-11. Communications between the calling function (parent) and the called function (child) As we've demonstrated, a parent function can pass on a child many arguments, but a child can return only one value However, it is possible to overcome such a restriction through containers (it is the next lesson's topic) Both types of communication (from a parent to a child and backwards) are optional: yes, functions may have parameters and returned values, but they don't have to. Global and Local Variables One more important point you've seen in the program above. Two variables were defined inside the function, they are called local, two variables were declared outside, they are called global. Local variables are dynamically allocated in RAM when the function starts, and they are automatically deleted from RAM when the function is completed So, they can be referred only inside the function where they are defined. Don't try to access local variables,fLQ m the o utsid e o f the function! Definition: stack — dynamic area of RAM. It is implemented on LIFO principle ("Last In, First Out"). Stack is used for local variables. Also, when a function is called the next statement address is saved on a stack, so when the function is completed the parent function retrieves it and continues execution properly. 49
Modules Functions provide us with a powerful technique to divide the program into logical units. But it is not enough to divide the code logically, you have to divide it physically too The solution is to save your functions as separate files; we call those files modules If we don't do that, sooner or later, in real life apps, mam program may grow to enormous size (hundreds and thousands of lines). In the flowing example I saved three functions m the helper .py file. My main program connects those functions using the import directive Pay attention, now in order to call the function you have to use the module name, period, and the function name def addition(numl, num2): return numl + num2 def product(numl, num2): return numl * num2 def average(numl, num2): return (numl + num2) / 2.0 Figure 1-10-12. Module helpers.py consists of three functions. import helpers print(helpers.addition(1, 2)) print (helpers.product(1, 2)) print(helpers.average(1, 2)) Figure 1-10-13. Main program — code. 3 2 1.5 »> Figure 1-10-14. Main program — test run If you feel that using a module name as a prefix becomes a burden, there is a shortcut. Use import with the asterisk. from helpers import * This way you don't have to use module name as a prefix when calling the functions from that module. User Defined Functions, Library Functions and Event Handlers User Defined functions are the functions created by you. But there are plenty of useful Python functions that are already written tor you to save your precious time. All we need is to call them properly. We call them library functions. 50
Examples • print () • inputO • open () Some library functions belong to certain system modules. So, in order to call them you must connect those system modules using import directive. Then, in a function call you have to use the name of the module, as a prefix, for instance: sys . exit () Definition: event handler is a function defined by the user and called by the OS (Operating System) in case certain event happens. We don't need event handlers when you create simple console applications. But as soon as you start creating real-life GUI (Graphic User Interface) applications, event handlers become crucial For instance, you may define an event handler for a button. If user clicks, your event handler is called by the OS, and you may program appropriate actions related to the mouse click. Event handlers will be covered in the third part of the book. Type Defined by: Called by: User defined You You Library "Somebody" You Event Handler You "Somebody" Figure 1-10-15. Three types of functions. Lesson 11. Data Containers The set of standard statements in Python is very limited. Functions are to combine statements; in a way functions are your own, problem -oriented super-statements. Similarly, we have only four primitive types of variables in Python. Also, variables are very small in terms of their capacity. Data Containers are to combine variables. So, in a way, containers are super-variables. Let's analyze three essential data containers in Python • List • Dictionary • Tuple 51
Lists Definition: list is a package of numbered variables. All the variables in a list share the same name. We access different variables in a list using the list name and so-called index in square brackets We start numbering list items from 0. Very common mistake beginners do is to believe that if a list has for instance 10 items, the last item index is 10, Not true, the last item's index is 9! numbers = [1, -2, 3] print("item 0:", numbers[0]) print("item 1:", numbers[1]) print("item 2:", numbers[2]) print("Hew many items?", 1en(numbers)) Figure 1-11-1. List demo — code. item 0: 1 item 1: -2 item 2: 3 How many items? 3 Figure 1-11-2. List demo — test. You see in the first line of the ptogram how to create a list and initialize it. Very popular library function len () takes the name of the list as an argument and returns us the total number of the list items. If the list is empty, its length is 0 Let's take a look at how to create and initialize a big list: numbers = [C] * 100 print("irern 0:", numbers[0]) print("item 1:", numbers[1]) print("item 99:", numbers[99]) print("Hew many items?", len(numbers)) Figure 1-11-3. Big list — code. item 0: 0 item 1: 0 item 99: 0 How many items? 100 »> Figure 1-11-4. Big list — test. If you don't know the size and the content of the list ahead of time, you can create an empty list and append items to the end of the list dynamically, during the program execution. You may also remove items at run-time 52
numbers = [] print("Before append: ", end="") print(numbers) for i in rangefl, 11): # loop from 1 to 10 numbers.append(i) print("After append: ", end="") print(numbers) numbers.remove(5) print("After remove: ", end="") print(numbers) Figure 1-11-5. Dynamic appending and removing list items — code. Before append: [ ] After append: [1, After remove: [i, 2, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10] 4, 6, 7, 8, 9, 10] Figure 1-11-6. Dynamic appending and removing list items - test. Pay attention that library function remove () removes the first occurrence of an item, if there are a few identical items, the first occurrence is removed, if there is no such item, ValueError exception is raised (Chapter 1 Lesson 9). Another useful function is called pop (), and it removes an item at certain index. As you can see the print () function is quite versatile, it can print not only variables, literals and expressions but also lists. Variables in a list don't have to be of the same type mixed = [0, "0", False] Python has a rich set of methods to manipulate lists: append (), extend (), insert (), count (), clear (), copy (), index (), pop (), remove (), reverse (), sort () etc. Methods are "higher-level" functions and will be explained in Part 2 (OOP). Dictionaries In a list, items are numbered, in a dictionary, items are named. Dictionary is a set of key-value pairs. Keys are always strings, but values can be of any type. In the following example countries are the keys, populations are the values. 53
populations = {"Canada": 37000000, "UK": 66000000, \ "USA": 327000000} print("Canada populations["Canada"]) print("UK -", pcpul ations["UK"]) print("USA populations["USA"]) populaticns2 = {} # an empty dictionary created populaticns2 [ "Canada" ] = 370000:'0 # adding elements populations?["UK"] = 66000000 populations?["USA"] = 3270C0000 print("Canada populations?("Canada"]) print("UK populations2["UK"]) print ("USA -", populations?["USA"]) Figure 1-11-7. Dictionary —code. Canada - 38000000 UK - 67000000 USA - 329000000 Canada - 37000000 UK - €6000000 USA - 327000000 Figure 1-11-8. Dictionary — test. As you see, it is possible to create a dictionary and initiate it immediately, or (alternatively) you can create an empty dictionary and dynamically add key-value pairs All the keys in a dictionary must be unique. Tuples Tuples are similar to lists, but they cannot be modified after they are created They are processed faster than lists, they are more efficient marks = (66.5, 70.0, 88.3) print(marks[0] ) print (marks(11) print(marks[21) Figure 1-11-9. Tuples — demo. 66.5 70.0 ?8.3 Figure 1-11-1(1. Tuples — test. Asterisk Operator Sometimes we need the whole container, sometimes just the values from that container. In the second case we may apply to a container * operator; it peels the container and gives you only the container's content. 54
container = (1, 2, 3) print(container) print(‘container) »> (1, 2, 3) 12 3 Figure 1-11-11. Asterisk operator — code. Figure 1-11-12. Asterisk operator — test. Containers - important facts to remember • Lists are created with square brackets, • Dictionaries — with curly braces, • Tuples — with parenthesis. Lesson 12. Containers and Loops Data containers in real-life programs may consist of hundreds or even thousands of items, and in order to scan and process lists, dictionaries, or tuples we need loops. Keep in mind, however, that for and while loops statements are universal and can be applied to any repetitive tasks, not only to processing containers. The following program demonstrates how to process lists using for loop myGrades = [60.0, 70.0, 80.0, 90.0] print(myGrades) print() howMany = len(myGrades) for i in range(howMany): print(myGrades[i] ) print() for grade in myGrades: print(grade) print() for i in range(howMany): myGrades[i] += 5.0 print(myGrades) for grade in myGrades: grade += 5.0 print(myGrades) Figure 1-12 # 1. scaning using indexes # 2.scaning using values # 3.changing using indexes # 4.changing using values # (not working1) 1. Processing lists in for loop — code. 55
We have shown you two techniques to process a list: first one is using list indexes, second is looping through list items values (disregarding indexes). The first technique is the most universal, because you are allowed to read and change list items. The disadvantage is that you have to count list items before the loop The second technique is simpler, there is no need to count, but it is kind of limited, because you have read-only access, you cannot change the items. In 4th кюр, we change the variable grade but not the actual element of the list, [60.0, 70.0, 80.0, 90.0] 60.0 70.0 80.0 90.0 60.0 70.0 80.0 90.0 [65.0, 75.0, 85.0, 95,0] [65.0, 75.0, 85.0, 95 0] »> Figure 1-12-2. Processing lists in for loop - test. Of course, you may use with the lists while loop too, but then you have to set up and increment index i manually; probably for loop serves lists more naturally. myGrades = [60.Э, 70.0, 80.0, 90.0] howMany = len(myGrades) i = 0 while i < howMany: myGrades[i] += 1 i += 1 Figure 1-12-3. Processing lists in a while loop The next example shows you how you can process a dictionary in a loop using keys. We keep in a dictionary myGrades a student's marks related to three different programming subjects: myGrades = {"C++":60.0, "Java":70.0, "Python":80.0} print(myGrades) for lang in myGrades: # looping through the keys myGrades[lang] +=5.0 # and updating the values print(myGrades) Figure 1-12-4. Processing a dictionary using loop for — code. {'C++': 60.0, 'Java': 70.0, 'Python': 80.0} {'C++': 65.0, 'Java': 75.0, 'Python': 85.0} »> Figure 1-12-5. Processing a dictionary using for loop - test. 56
Iterators The most common way to access elements in a container is through the indexes (could be a number in case of list/tupie or a string in case of dictionary) There is an alternative though Sometimes it makes sense to retrieve a pointer to the first element of the container, process the first, then move that pointer to the second, process, move, process... etc. So. we may access the container’s data through dynamic pointer; we call it iterator. Let's demonstrate how to use iterators. myLisc = [1, 2, 3, 4, 99] iterator = iter(myList) try: while True: print(next(iterator) ) except Stopiteration: print("LIST DONE!\n”) myDict = {"First": 1, "Second": 2, "Third": 3, "Fourth": 4, "Fifth": 99} iterator = iter(myEict) try: while True: key = next(iterator) print("Key:", key, "| Value:", myDict[str(key)]) except Stopiteration: print("DICTIONARY DONE!") Figure 1-12-6. Iterators — code. First, we created a list. Using Python library function iter () we initialized an iterator. Library function next () does two things- • returns the pointer to the first element, • moves the pointer to the second element. So, by using next () in a loop we may scan the whole container When we reach the end, the exception Scoplteraion is thrown {review Lesson 9), that means we should finish processing Secondly, we created a dictionary. In this case next () gives us the next key, not the next value. We retrieve the next value by casting the key to string and using it as an index. 57
1 2 3 4 99 LIST DONE' Key: First | Value: 1 Key: Second | Value: 2 Key: Third I Value: 3 Key: Fourth | Value: 4 Key: Fifth | Value: 99 DICTIONARY DONE' >» Figure 1-12-7. Iterators — test. Lesson 13. Strings Definition: string is a set of characters (a piece of text). All the characters in a string have indexes. In a way, strings are containers too, they are similar to lists/tuples; for instance, the first character of the string myString is myString [ 0 ]. Library function len () is still applicable to strings Python doesn't have a special data type for individual characters, like C/O+/C# or Java. That means that myString [N] would still be of str type; it is just one character string. An important point-strings are immutable in Python, i.e. they cannot be changed after they are created. For instance, this statement would produce an error: myString10] = "a" However, this is correct: myString = "aaaa" myString = "bbbbbb" So, how is it possible, if strings are immutable? The answer is Python will completely delete from the memory "aaaa" string first, then allocate memory for a brand-new string "bbbbbb" and, finally, save the address of a new string in myString variable again. 58
Let's try to go through a string character by character. First time we do this using indexes, second time — without them. str = "abed" howManyChars = len(str) for i in range(0, howManyChars): print(str[i]) for ch in str: print(ch) Figure 1-13-1. Processing a string in for loop — code a b c d a b c d Figure 1-13-2. Processing a string in for loop — test. The most common operations on strings (covered in Lesson 4) are: • Concatenation (+) * Comparison (>, >=, <, <=, ==, ! =) Python also has a rich set of library functions that deal with the strings: splitting, searching, converting to lower/upper case and many others We call those functions methods because we apply them to the strings using dot symbol: stringName. methodName (...) Methods are more advanced, object-oriented functions. We cover OOP (Object-oriented Programming) in Chapter 2 ft HTML CSS JAVASCRIPT SQL PYTHON PHP BOOTSTRAP HOWTO W3.CSS JAVA JQUERY Ы . .. . — ... Method Description MySQL Jotn Python MongoDB MongoDB Get Startea MongoDB Create Database LSIHUllZS.. casefotdQ cent erf) Converts the first character to upper case Converts string into lower case Returns a centered string MongoDB Create Collection MongoDB Insert csuPtU Returns the number of times a specified value occurs m a string MongoDB Find MongoDB Query MongoDB Sea MongoDB Delete MongoDB Drop Совестью MongoDB Update MongoDB Limit encoded Returns an encoded version of the string endswithf) Returns true rf the string ends with the specified value expandtabsQ Sets the tab size of the string find!) Searches the stnng for a specified value and returns the position of where it was found formatf) Formats specified values in a string Python Reference Python Overview Python Bmlt-m Functions Python String Methods focmat_map{) Formats speafied values in a string mdexf) Searches the string for a specified value and returns the position of where it was found iSalnumO Returns True »f all characters in the stnng are alphanumeric Figure 1-13-3. Python string methods reference from vs3chools.com 59
Lesson 14. Two-Dimensional Lists Any element of a container could be in turn another container. That's what makes containers really great — the ability to nest. You may picture list as a linear sequence of variables where each variable has its unique position, offset, or index. 99 -5 10 2 Index Figure 1-14-1. One-dimensional list. Quite often however, we need to process table-like data structures where each variable has a row number and a column number Row Column - 99 -5 10 2 12 0 3 1 ... 0 -1 10 12 ... Figure 1-14-2. Two-dimensional list. That's where two-dimensional (or simply 2D) lists can help us. 2D list in Python implemented as a "list of lists", in other words, it is one-dimensional list of one-dimensional lists. my2DList = [ [1, 1, 1] ,\ [2, 2, 2],\ [3, 4, 5],\ [11,12,13] ] print(my2DList) print(my2DList[1 ] ) print(my2DList[3] [2] ) Figure 1-14-3. 21) list—code. [[1, 1, 1], [2, 2, 2] 13 [2, 2, 2], [3, 4, 5], [11, 12, 13]J Figure 1-14-4. 2D list - test. 60
As you can see, we defined a 2D list and initialized it. The very top row has number 0, and the row numbers are increasing as you go down, the leftmost coiumn has number 0, and the column numbers are increasing as you go right. You can use 2D list name without brackets, that means you are referring to the 2D list as a whole; you can use the 2D list name with just one index in square brackets, that means you are referring to one tow only, which is one-dimensional list; or, most likely, you can use the 2D list name with two indexes in square brackets, that means you are referring to only one element from certain row and certain column. Processing two-dimensional list items, use the row number first, and then the < olumn number1 If we can iterate through one-dimensional list using for or while loops, in order to iterate through 2D lists, we need nested for or while loops. my2DList = [ [1, 2, 3, 4], \ [21, 22, 23, 24],\ [31, 32, 33, 34]\ ] print("Before, my2DList) rows = len(my2DList) # how many rows? cols = len(my2DList[G]) # how many columns? for r in range(rows): for c in range(cols): my2DList[r][c] += 1 printCAfter:", my2DList) Figure 1-14-5. 2)) list in a nested loop — code. Before: [[1, 2, 3, 4], [21, 22, 23, 24], [31, 32, 33, 34]] After: [[2, 3, 4, 5], [22, 23, 24, 25], [32, 33, 34, 35]] »> Figure 1-14-6. 2D list in a nested loop — test. You may define and initialize 2D list with the same value using the asterisk character: myBigzDList = [[0.1] * 10] * 10 2D list may not be necessarily "rectangular" when all the rows have the same length. It could be so-called "jagged". 61
Column Row 99 -5 10 2 12 0 3 0 -1 10 12 15 Figure 1-14-7. “Jagged” 2D list jagqedList = [[1, 1], [2], [3, 3, 3], [4, 4, 4, 4, 4, 4] ] print (jaggedList) rows = len(jaggedList) # how many rows? for r in range(rows): cols = len(jaggedList[r]) # how many columns in a row? for c in range(cols): jaggedList[r][c] += 1 print(jaggedList) Figure 1-14-8. Jagged 2D list processing — code. [[1, 1], [2], [[2, 2], [3], [3, 3, 3], 14, 4, 4, 4, 4, 4]] [4, 4, 4], 15, 5, 5, 5, 5, 5]] Figure 1-14-9. Jagged 21) list processing - test. Lesson 15. Files and Streams Permanent Storage All kind of data we covered so far (variables and containers) are stored in Random Access Memory (RAM). RAM is superfast in terms of data processing, but it has an obvious drawback - it is working only when the system is powered. The second problem is capacity; the size of modern software, multimedia files, databases is enormous and quite often simply doesn't fit into RAM limits. That's why computers have a variety of permanent storage drives: built-in hard disk and external hard disks, memory sticks, SD cards, optical drives. Besides, you may have access through local network to other computers' storages. Under Windows OS permanent storage drives are assigned to different letters: for example, hard drive — c:, optical drive — d:, memory stick — e: and etc. Mac OS and Linux use instead of the letters logical, more meaningful names, for instance "Macintosh HD" or "USE MY DATA". 62
Because permanent storage drives are big m terms of their capacity, data there is organized in folders (catalogs) and files. Folders are like boxes where the files belong; folders don't have actual data; folders may have subfolders, those subfolders in turn may have their own subfolders etc. Files are the actual data. Any folder and any file have a name Files may contain different types of data: photos, videos, music, documents, spreadsheets etc. That's why a file name may have an extension 3 or 4 letters after dot telling us what kind of data is saved in a file For example, photos usually have . jpeg extension, MS Word documents — . docx, text files — . txt etc. The entire tree of folders on a hard drive could be huge. Many folders and files are created by the OS for various programs. In order to separate programs' data and people's data, there are a few standard pre-created folders for personal use Documents, Pictures, Music etc. If a programmer needs to specify the precise file location on a permanent storage drive, we need to provide so-called path: a sequence of subfolders that lead to the actual folder where the file resides. Paths can be; • Absolute • Relative Absolute path starts from so-called root folder Root fofder is the name of the drive Absolute path format: {drive_name}/folderl/folder2/folderN For instance, a resume saved on a hard drive could have the following absolute path- c:/Users/John/Documents/my resume.docx. Relative path starts from the current folder. Current folder is where we saved the Python program. For instance, another Python program located somewhere else could have the following relative path ./../chapter2/lessonl9/another pychon_program.py. When we specify the relative path we use symbols dot (current folder) and double dot (parent folder). From the programmer's point of view files can be • text, * binary 63
Mainly, text files are meant for people, they consist of lines of readable characters; it doesn't mean though that computers can't process text files. Binary files are not readable by humans; they consist of bytes Binary files are for computers and programmers who create computer programs; it is kind of "raw" data. If you try to open a binary file in a simple text editor like Notepad or Notepad++ you will see "gibberish" symbols. LE( LlNULK И C ,Jse*ii• Noteped** File Ed4 Seetch View Encoding L«ngu*ge Settings Too*» Моего Pun Plugins Window T о t <|гвгаьи~> с т: и В J 00 unknpwn iMiknown.mp3 & □] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ППЛГ AisMiWeij§ ran XjAU...l< .»• i»J4« =EIt 3 £4n11*: I?/{i> 4IM{t> 143 £ кТз{№1гё£№Ш11 h^SK ЬМ-ИУиИ ;ддЁ~р11И„Я1йУ rUVri l5J5ffl5»«uid iC41S 05 АдАЁИЯ=#\ ;x ВгаЯэёС|?И pBD r 1Я1!ИТ®61 ЯЗИ B3 Qawa-g/Rx/EoR ( > ЙЛ | Ё ЕЯЯ :ueiH3yyu,r_yY ПНИ X ВИТ1 ’7 rAAaj , t ~, пЯПЗР Аё(?„ (ЯП31 ( ;P*€K 1531/'' AEoe'Js®u2ATy (X 1Я31 ob°yzSiKJ (33 T0Bli59„0&йШ?1йоШ-н SHE i~X -HiRl'pzBH a>3E-g1Wa,0 -EY„gY2 BYtU* IWWRXu Г.Я hCyl 1ЫЛГОШ 153 Aa ПИЯ"i А IXoWWHWJIIIIkW! 1ЯЯП .@ha< 9 (5Ш1 IsiCOi -®< Н-ЁГ 15Я1 ul331'mU| tai?4Ee6d±II»0XW« iNIJT.jNUT.I liornul tert Me length: 902,814 lines: 7,917 Ln 1 Сок 1 Рос 1 Macintosh (CR) ANSI INS Figure 1-15-1. Binary file opened in Notepad++. Pay attention that MS Word documents are binary files, because they have not only text but a lot of embedded formatting characters. Programmers handle files in 3 steps 1) Opening, 2) Reading/Writing (quite often in a loop), 3) Closing Let's discuss terminology. Files are rarely read or written in one step as a whole because of the RAM size limitations. Instead, we allocate in RAM relatively small area from where we can read or write data by portions. We call this area buffer. Besides the buffer, we need to trace the current reading/writing position inside the body of the file. So. basically during reading/wntmg Python interpreter creates dynamic structure to maintain the process. Some technical details of this structure are hidden from programmers, some are visible. We call this structure stream. 64
Streams are not files. Files are rather static entities, they exist on a hard disk, even if the computer is not powered. On the other hand, streams are dynamic, they are created when the file is opened, they are changing during reading/writmg, and they cease to exist when the files are closed We may say that streams are files m the processing. Figure 1-15-2. biles and streams. Reading Let's say we have a text file nums . txt located in the same folders as our Python program. Figure 1-15-3. Text file nums . txt opened in Notepad++. 65
Here's the 1st file reading application: import sys try: stream = openCnums.txt") except lOError: print("Opening error!") sys.exit () content = stream.read() for ch in content: print(ord(ch), "/", end="", sep-"") if ord(ch) ==10: # Line Feed (LF) print () stream.close () Figure 1-15-4. Reading text file in tine step — code. 49/49/49/49/49/49/49/49/49/49/10/ 50/50/50/50/50/50/50/50/10/ 51/51/51/51/51/51/51/51/10/ 52/52/52/52/10/ Ю/ 53/53/53/53/53/53/53/53/53/53/53/53/53/53/53/53/10/ »>l Figure 1-15-5. Reading text file in one step — test. open() function format: newStream = open([path]fileName) if the opening is successful, a reference to a new stream is saved in newStream variable, This reference is needed later on to process the file, path is optional, if we don't provide it the system assumes the file is resides in the same folder as our Python program. So, opening is an attempt to identify the exact physical sector on a hard disk where the beginning of the file is located. If a file is not found, lOError exception is raised (see Lesson 9). In this case function sys . exit () terminates the program execution. Library function read () reads the whole file into a string variable content. Library function ord () gives us the ASCII code of a character (Chapter 4). For instance, number "1" ASCII code is 49, number "2" is 50 etc. LF (Line Feed) character terminates lines in a text file; its code is 10 Be careful though, some text files have two characters to the end of each line CR (Carriage Return, code 13) and LF. If the file is located somewhere else (not in the current folder), we need to provide the absolute path. 66
import sys try: stream = open ("C:/Users/Compu/OneDrive/Uesktop/nums.txt") except lOError: print("Opening error!") sys .exit () content = stream.read() for ch in content: print (ord(ch), end="", sep="") if ord(ch) ==10: # Line Feed (LF) print() stream.close() Figure 1-15-6. Reading text file in one step using the absolute path. If nums. txt file is located beside the current folder, we have to go up to the parent folder first, then down. import sys try: stream = open("./../Iessonl3 strings/nums.txt") except lOError: print("Opening error!") sys.exi t () content = stream.read() for ch in content: print(ord(ch), "/", end="", sep="") if ord(ch) ==10: # Line Feed (LF) print() stream.close() Figure 1-15-7. Reading text file using the relative path. If a text file is not very big, we may try to read it in one step, but much more common approach is to do that line by line. import sys oneLine = "" # reading buffer try: stream = open("nums.txt") except lOError: print ("Opening error!") sys.exit() while True: oneLine = stream.readline() if len(oneLine) == 0: break print(oneLine, end="") stream.close () Figure 1-15-8. Reading text file line by line. The readline () library function returns one line from the file or an empty set of characters when the end of the file is reached. 67
Reading/Writing The open () function simple format with one parameter we have just discussed is good for reading text files, but there is more universal format with two parameters which is suitable for both reading/writing text and binary files. open () function with two parameters newStream = open([pa tn]name, mode) Where mode is a character that specifies the type of file processing There are quite a few mode characters, but the most common are "r" — reading, "w" — writing to a new file, "a" — appending to an existing file. You may also add to the mode letters "t" (text) or "b" (binary). For instance, "wb" would be to open a new binary file for writing The following program creates a copy of a text file. Pay attention that if the file nums_copy. txt already exists, it will be recreated, i.e. an old version would be deleted. import sys inputstream = None outputstream = None oneLine = "" # reading buffer try: inputStream = open("nums.txt") except lOError: print("Opening error!") sys.exit () try: outputStream = open("nums copy.txt", "w") except lOError: print("Creating error!") sys. ex it () while True: oneLine = inputstream.readline() if len (oneLine) == 0: break outputstream.write(oneLine) inputstream.close() outputSt ream.close() Figure 1-15-9. Copying text file. 68
None keyword means no value yet. Streams don't belong to primitive types, that's why we initialized them with None. In fact, streams are objects and that's our next topic The next program does binary file copying; read () function has as a parameter the number of bytes to be read, in our case — just 1. Function write () parameter is the buffer to be written into a new file. import sys # variables inputstream = None outputstream = None oneByte = "" # reading buffer try: inputstream = open ("benefits.jpg", "rb") except lOError: print("Opening error!") sys.exit () try: outputstream = open("benefits copy.jpg", "wb") except lOError: print("Creating error!") sys.exit () while True: oneByte = inputstream.read (1) if len(oneByte) == 0: break outputstream.write(oneByte) inputstream.close() outputstream.close() Figure 1-15-10. The binary file’s copying. 69
Part 2. Object Oriented Programming (OOP) Lesson 1, Introduction Object Oriented Programming is a modern, de-facto standard software developers' paradigm when you combine functions and variables into new, syntactic and semantic units that are called classes and objects. In essence, classes and objects are conglomerates of data and code (executable instructions). OOP started to gain popularity in the mid of 1980th. An old programing paradigm (without objects) is called procedural programming Python doesn't force programmers to use OOP like Java or C#. The choice is yours; you can use in your projects an old procedural approach, a new Object-Oriented approach or combine both. Essential Terms Definition: class is a set of related variables and functions aimed at a certain programming task. Definition: instance variable, property is a variable that belongs to a class. Definition: method is a function that belongs to a class. Definition: object is a representative (an instance) of a class. Definition: instantiation is a process of creating objects. Definition: constructor/initializer is a special method that is called automatically when and an object is instantiated. Does it sound abstract yet? Don't worry, as soon as we start practicing all the pieces of the puzzle will come together quickly. Let's say, we develop a college database application, and we decided to create a new data type (or class) for students' personal data processing. 70
Then we work in two steps 1) We create the Student class where we define all a student's properties (e.g. name, address, current marks, course etc.) and processing methods (e.g, register (), grade (), transfer () etc.) 2) We instantiate concrete students as objects, so that everybody has different name, address and marks. We manipulate students through the methods defined in Student class. All the students share methods defined in a class, but a method should know which concrete student it is applied to. So, we don't call methods as regular functions by using their names; we first provide so-called qualifier (the object name + period) and only after — the method name. For instance- student 1 . transfer (...) or: student2 .grade (...) So, Student class is like a blueprint (a plan, a template) to create and manipulate Student objects. Properties are different for different students, but all the students share the same behavior through the methods. In other words, all the students are different, and all the students are the same. Using OOP, we can model real-life entities creating new, programmer-defined types of data. First Class Let's create a new data type Rectangle, that has three instance variables: width, height, and outline. As we are going to see very soon (Part 2 Lesson 4) there is a subtle difference between terms "instance variable" and "property". But right now, it is not important, we will use both. Also, we will define three methods to work with rectangles: grow (), shrink () and draw (). The width and the height properties are self-explanatory; the outline is just a character which we use tc draw the rectangle on a console window. The grow () and shrink () methods are supposed to increase or decrease width and height respectively So, you can type the following and save it as rectangle_class .py file: 71
class Rectangle: def init_ (self, w, h, o): self.width = w self.height = h self.outline = о # constructor # 3 instance variables def grow(self): # method self.width += 1 self.height += 1 def shrink(self): # method self.width -= 1 self.height -= 1 Figure 2-1-1. Class Rectangle — definition. Usually, we capitalize the class names, but it is not technically required by Python. As you see there are three methods defined in the class: init (), grow (), and shrink () The constructor's name is standardized in Python and consists of two underscores, the init keyword and two underscores. But where are the instance variables? They are defined and initialized within the constructor body with the prefix self. Whenever you use instance variables inside methods, prefix self is required. This way you can clearly differentiate local variables and instance variables because local variables don't have prefixes. Forgetting the "self" keyword when manipulating instance variables is a very common beginner's mistake Don't forget that OOP development always assumes two steps. 1) defining classes, 2) instantiation (creating objects). The 1st step is done, let's go to the 2nd: create another file, save it as rectangle test .py and type the following. from rectangle_class import * rl = Rectangle(11, 11, # instantiation r2 = Rectangle (20, 2, "I") print("Before grow():", rl.width, rl.height, r2.width, r2.height) rl.grow () r2.grow () printCAfter grow():", rl.width, rl.height, r2.width, r2.neight) print("Before shrink():", rl.width, rl.height, r2.width, r2.height) rl.shrink() r2.shrink() printCAfter shrink():", rl.width, rl.height, r2.width, r2.height) Figure 2-1-2. Class Rectangle — instantiations. 72
As you see, first of all, we connect the class definition file using import directive. Then we instantiate two rectangles using the class name and three arguments. That means that the _init () constructor was implicitly called twice. Note that the constructor has four parameters in the class definition, but only three arguments when it is called. What happens inside the constructor? Look back at the class definition. First, the constructor asks OS to allocate new memory for 2 integer variables and one string, The received memory address is saved in the variable self. Using self all the properties are initialized. Finally, self (the newborn object's address) is returned to the mam program and saved in rl variable. The same happens to r2. So, the instructor's job is: • to allocate memory properties, • to initialize properties, • to return the allocated memory address to the mam program, so that a new object can be used. When you refer to the object properties, you need the qualifier (object name + dot), and the property name. Qualifier is needed because different rectangles may have different dimensions and outlines. Methods grow () and shrink () have three parameters, but only two arguments. When you call grow () on r 1, r 1 address is copied to se 1 f. When you call grow () on r2, r2 address is copied to self. So, the same method can handle different rectangles. Definition: keyword self represents in the class definition the address of the object the method is being used with. Constructors return self, regular methods receive self. Right now, we have two files opened in IDLE. Which one to run? We should run the rectangle test.pyfile. Before grow(): 11 11 20 2 After grow(): 12 12 21 3 Before shrink(): 12 12 21 3 After shrink(): 11 11 20 2 »> Figure 2-1-3. ('lass Rectangle instantiations — test. 73
Lesson 2. Developing Methods Our new method draw () is to visualize (print) rectangles. Let's draw a rectangle in three steps: the top, the middle and the bottom parts. class Rectangle: def init (self, w, h, o): self.width = w self.height = h self.outline = о def grow(self): self.width += 1 self.height += 1 def shrink(seif): self.width -= 1 self.height -= 1 def drawTop(self): for column in range(self.width): print(self.outline, end="") print() def drawMiddle(self) : for row in range (self.height - 2): print (self.outline, end="") for column in range (self.width - 2): print (" ", end="") print(self.outline) def drawBottom(self): self.drawTop() def draw(self): self.drawTop() self.drawMiddle() self.drawBottom() Figure 2-2-1. Class Rectangle with draw () method — code. The coding of the drawing methods is quite straightforward; in order to draw the middle part, you need nested loops. Pay attention whenever you call inside one method another method "self" prefix is required. from rectangle_?lass import * r = Rectangle(5, 6, "I") r.draw () Figure 2-2-2. Class Rectangle with draw () method — instantiation. 74
»> Figure 2-2-3. Class Rectangle with draw () method — test. Lesson 3. Static Class Members Static Variables There is an issue, though, with the Rectangle class we should address: method draw () doesn't draw properly very small (less than 3) and very big (exceeding the size of the console) rectangles; nothing even prevents us from trying to create rectangles with negative dimensions. In order to make objects' behavior more predictable, robust and less error prone, we may set maximums and minimums for the rectangles' dimensions 3 would be a minimum for both width and height, 60 — a maximum for the width and 20 — a maximum for the height. Definition: static (or class) variables are shared by all the instances of the class. In order to use static variables, there is no need to instantiate any objects: use the class name as a qualifier. class Rectangle: MIN W = 3 MIN H = 3 MAX_W = 60 MAX_H =20 def init (self, w, h, o) : if w < Rectangle.MIN W: self.width = Rectangle.MIN_W elif w > Rectangle..MAX W: self.width = Rectangle.MAX_W else: self.width = w if h < Rectangle.MIN_H: self.height = Rectangle.MIN H elif h > Rectangle.MAX H: self.height = Rectangle.MAXH else: self.height = h self.outline = о 75
def grow(self): if self.width < Rectangle.MAX_W and \ self.height < Rectangle.MAX H: self.width += 1 self.height += 1 def shrink(seif): if self.width > Rectangle.M1N_W and \ self.height > Rectangle.MIN H: self.width -= 1 self.height -= 1 # 4 drawing methods - no changes... Figure 2-3-1. class Rectangle with validation. Four static variables were declared at the top of the class; we capitalize their names to show that they are constants (variables that don't change). Capitalizing constants names is very common programming practice. Everywhere we needed them, we added the class name (Rectangle) as a qualifier Now our constructor as well as grow () / shrink () methods can validate the dimensions and won't let them get out of the certain range from rectangle_class import * r = Rectangle(-100, -100, "I") r.draw() print(Rectangle.MIN W, Rectangle.MIN H, Rectangle.MAX W, Rectangle.MAX H) Figure 2-3-2. class Rectangle with static members — use. I I I I I I I I 3 3 €0 20 Figure 2-3-3. Class Rectangle with static members — test. As you can see, incorrect dimensions were replaced by the correct values. Static Methods Static methods don't access instance variables, which means there is no need to specify the object name when you call them. Instead, we use the class name as a qualifier. In order to demonstrate the concept, we are going to create in Rectangle class static method help (). 76
class Rectangle: MIN_W = 3 MIN H = 3 MAX W = 60 MAX H = 20 @staticmethod def help () : print("Rectangle class is to create, manipulate & draw" "rect angles") print("Creating rectangles requires 3 arguments: width, height "and outline") print ("Methods : grow(), shrink (), draw()") print ("Rectangles can be from:", Rectangle.MIN W, "x", \ Rectangle.MIN H) print("Rectangles can be up to:", Rectangle.MAX_W, "x", \ Rectangle.MAX_ H) # the rest is the same... Figure 2-3-4. Static method help () from rectangle_class import * Rectangle.help () Figure 2-3-5. Calling static method help (). Rectangle class is to create, manipulate & draw rectangles Creating rectangles requires 3 arguments: width, height and outline Methods: grow(), shrink(), draw() Rectangles can be from: 3x3 Rectangles can be up to: 60 x 20 Figure 2-3-6. Static method help () — test. Let's summarize. We may classify variables defined in a class as: * Instance (regular) • Static We may also classify methods defined in a class as: • Instance (regular) • Static 77
Important facts Ip remember: • Instance methods must have parameter self. • Instance methods can access both instance variables and static variables. • Static methods don't have parameter self . • Static methods can't access instance variables. • Static methods can access only static variables. • Static methods must have @staticmethod directive before the definition. Fig. 2-3-7. Relationship between methods and variables. Lesson 4. Data Hiding (Encapsulation) By setting up limits for rectangles dimensions we improved the Rectangle class robustness. When rectangles are instantiated or manipulated using grow () and shr ink (), the dimensions are validated. However, nothing prevents the class user from directly changing the width and the height after instantiation. For example r = Rectangle(10, 10, "x") r.width = -999У9 #??? Definition: data hiding (or encapsulation) is an OOP concept. The idea is to hide instance variables, so that the class user can't access them directly and inadvertently damage Safer access to the instance variables is provided through special methods — setters and getters. 78
METHODS METHODS Figure 2-4-1. Data Hiding (Encapsulation). You can clearly see from this picture why we use a term encapsulation. Data is put into kind of capsule or shell, and the shell consists of methods. Methods guarantee safe access to data Getters and Setters Definition: getter is a special method to read instance variables When we call a getter, there are no arguments, it returns the instance variable value. Definition: setter is a special method to safely change instance variables. When we call a setter, there is only one argument to set up a new value for the instance variable. Encapsulation is not an iron-made rule, it is just a commonsense recommendation. Getters and setters may slow down objects' processing. If you think that leaving some instance variables open won't make your class dangerous and vulnerable, go ahead; the choice is yours. Programming is mathematical modeling real life processes, that's why there are no easy-to-follow recipes for all occasions. Let's create now a safer and more robust version of the Rectangle class. First, we will change width to width and height to height everywhere in the Rectangle class (we add two underscores prefix). This way we hide instance variables from the class user. At the same time, we will provide two getters and two setters (no changes to the other methods of the class). 79
def getWidth(self): return self._______width def setwidth(self, w) : if w < Rectangle.MIN W: self, width = Rectangle.MIN W elif w > Rectangle.MAX W: self. _width = Rectangle.MAX_W else: self, width = w def getHeight(self): return self._______height def setHeight(self, h): if h < Rectangle.MIN H: self. height = Rectangle.M1N_H elif h > Rectangle.MAX H: self. height = Rectangle.MAX H else: self. _neight = h Figure 2-4-2. Getters and setters. frcm rectangle_class inport * r = Rectangle (]0, 10, "*") r.setwidth(99) print(r.getWidth()) Fig. 2-4-3. Getters and setters — use. 60 »> Fig. 2-4-4. Getters and setters — test. As you can see, an attempt to put into the instance variable an incorrect value (99) was prevented, and the safe maximum (60) was assigned. Properties So, this approach to hide data is good, but using prefixes get and set very often could be tiresome; also, you have to always type parentheses when calling getter and setters. Properties allow us to use getters and setters as if they are instance variables. So, properties look like variables for the class user, but they are methods for the class creator Properties ate "smart" instance variab es. Let's take a look at how it is programmed (instead of getters and setters we created properties using special directives with symbol @). 8<1
^property def width(self) : return self._______width @width.setter def width(self, w): if w < Rectangle.M1N_W: self.____________width = Rectangle.MIN_W elif w > Rectangle.MAX W: self. width = Rectangle.MAX W else: self._______width = w ^property def height(self): return self.________height @height.setter def height(self): if h < Rectangle.MIN H: self.____________height = Rectangle.MINH elif h > Rectangle.MAX_H: self. height = Rectangle.MAX H else: self.___height = h Fig. 2-4-5. Properties. frem rectangle class inport * r = Rectangle(10, 10, "*") # instantiation r.width =99 # implicit setter's call print(r.width) # implicit getter's call Fig. 2-4-6. Properties — use. Compare this version with the previous one (Fig. 2-4 3), it may look more natural. 60 »> Fig. 2-4-7. Properties — test. Lesson 5. Composition Here is a crucial question in Object-Oriented Design: • How to build new, more advanced classes based on the existing ones? Anybody who is already familiar with the topic will suggest: inheritance* That's true, and we will discuss inheritance in the next lesson But there is probably a simpler way of classes' reuse. It is called composition, let's analyze it first. 81
Definition: composition is OOP technique to include into a new class existing objects as instance variables. Let's build a new class Box. Each box consists of six rectangles: front, back, left, right, top and bottom Class Box has two methods similar to class Rectangle: grow () and shrink (), but instead of method draw () it has method str (we will explain it in a little later). In order to instantiate boxes, we need three arguments: width, height and depth Rectangle Figure 2-5-1. A new class (Box) uses grow() shrink() draw() grow() shrink() _ str_() an existing class (Rectangle) from rectangle_class import * class Box: def init (self, w, h, d): self.rcFront = Rectangle(w, n, "I") self.rcBack = Rectangle(w, h, "I") self.rcLeft = Rectangle(d, h, "I") self.rcRight = Rectangle(d, h, "I") self.rcTop = Rectangle(w, d, "I") self.rcBottom = Rectangle(w, d, "I") def grow(self): self.reFront.grow() self.rcBack.grow () self.rcRight.grow () self.rcLeft.grow () self.rcTop.grow() self.rcBottom.grow () 82
def shrink(self): self.reFront.shrink() self.rcBack.shrink() self.rcRight.shrink() self.rcLeft.shrink() self.rcTop.shrink() self.rcBottom.shrink() def str (self): return "Width: " + str (self.reFront.width) + \ ", height: " + str (self.rcFrcnc.height) + ", depth: " + \ str(self.rcRight.width) Figure 2-5-2. Box class In the very first line we imported class Rectangle. As you can see the new Box class consists of 6 Rectangle objects, they are instantiated inside the Box constructor, You may include in each class a special method str (). This method is returning a string, a text description of the object. If there is such a method, you can print objects of that class using the library function print(). from box class import * box = Box(4, 5, 6) print("A box instantiated1") print(box) print("Front rectangle:") box.reFront.draw() print("Left rectangle:") box.rcLeft.draw() Figure 2-5-3. Class Box — use. A box instantiated! Width: 4, height; 5, depth: 6 Front rectangle: Illi Left rectangle: »> Figure 2-5-4. Class Box — test. 83
As you can see in order to access embedded objects' methods and properties we need nested qualifiers. If you nest objects too deeply, you may end up having hard to understand expressions with many qualifiers: obj1.obj 2.obj 3......objN.property Lesson 6. Inheritance Definition: Inheritance is the essential OOP technique when you improve/extend existing classes by adding to them new instance variables and methods. Inheritance is quite different from composition, though In composition you include in a new class definition some instances of the existing classes It is like copy/pasting into a new Word document the other already existing Word documents. In inheritance, your new class definition is based on the existing class definition. It is similar to creating new Word documents based on a certain template. Right now, our rectangles do have an outline character, but they are empty inside. Let's create another class called FilledRectangle where we can have not only width, height, and outline, but also fill. Both classes will have three methods grow (), shrink () and draw (). Rectangle FilledRectangle grow!) shrink!) draw() grow!) shrink() draw!) Figure 2-6-1. A new class (FilledRectangle) uses an existing class (Rectangle) frcm rectangle class import * class FilledRectangle(Rectangle): # constructor receives width, height, outline and fill def init (self, w, h, o, f) : super ().___init (w, h, o) # parent's constructor is called self.fill = f # extra variables are initialized Figure 2-6-2. FilledRectangle class 84
We call the existing class parent and the new class — child Unfortunately, terminology here is not consistent across languages. For example, in C++we call those classes base and derived, in Java — superclass and subclass, but it doesn't change the idea. As you see, the child definition must include the name of the parent in parenthesis. After that, in the first statement of the constructor, we must receive a reference to the parent instance and initialize it. So, super () function gives us the reference of the parent class instance. Using that reference, we call the parent class constructor, because we can build the child instance only on the top of the parent instance. Then we initialize the child's extra instance variables the parent did not have, in our case, it is fill. from filled rectangle_class import * r = FilledRectangle(4, 4, r.draw() r.shrink() r.draw() Figure 2-6-3. FilledRectangle — demo. A A A A * * * * A A A A * * * * * A A A Figure 2-6-4. FilledRectangle — test. The most exiting point here is that with just a few lines of code we got the whole functionality of the parent class ' for free" without even knowing how the parent is implemented The filled rectangle can do everything the rectangle without fill could. Apparently, the grow () and shrink () methods are still good, we can use them, but we are not quite happy about the draw () method, because it doesn't show us the fill of the rectangle Let's fix it. 85
Lesson 7. Polymorphism Talking about drawing in the FilledRectangle class, we don't have to change the drawTop () and drawBottom () methods because they don't use the till character. They are both still good. Only drawMiddle () should be modified. from rectangle class import * class FilledRectangle(Rectangle): def __init_ (self, w, h, o, f) : super ()._ init (w, h, o) self.fill = f # drawMiddle() - redefined! def drawMiddle(self): for row in range (self-height - 2): print(self.out line, end="") for column in range (self.width - 2): print(self.fill, end="") printfself.outline) Figure 2-7-1. FilledRectangle class. from filled rectangle class import * r = FilledRectangle (4, 4, r.draw () r.shrink() r.draw () Figure 2-7-2. FilledRectangle class — instantiation. Figure 2-7-3. FxlledRectangle class — test. Let's compare this code and the previous lesson code (Fig. 2 6 3). They are identical. Because the child class doesn't have method draw (), the draw () method from the parent class is used. Nevertheless, the rectangle with the fill is properly drawn. Magic? Not quite, let's try to understand what just happened Definition: Polymorphism in OOP is redefining (overriding) methods from the parent class in a child class 86
Polymorphism means many forms, which means different classes may have methods with the same name that work differently on different objects We see here an interesting difference between composition and inheritance. In composition, old objects are taken as they are. In inheritance a new class may replace in the old class stuff that doesn't fit. In other words, the child class developer can "edit" the parent class without even having access to its source code! So, both Rectangle and FilledRectangle classes have the drawMiddle () method, but the Python interpreter will call dynamically (at run-time) the right method based on the current object reference, If it is a rectangle, the parent's drawMiddle () is called If it is a filled rectangle, the child's drawMiddle () is called. The draw () method is not implemented in the child's class. The child class can still use it as it is, because the parent's drawMiddle () method call will be replaced automatically by the child's drawMiddle () method call at run-time. One more important point. We are allowed to assign to the Parent objects the Child objects. Let's say that both parent and child classes have a polymorphic method move (). parent = Parent (...) child = Child (...) parent = child # this is correct assignment parent.move() # which one is called? The child's method is called! The child's method move () is called because the parent variable contains in fact the reference to the Child instance Python interpreter calls the right method based on run-time information about an object. Lesson 8. Strings, Containers and Streams Revisited Now after we covered OOP basics we can state that strings, lists, tuples, dictionaries and streams are in fact objects. Consequently, they belong to certain classes. But we don't have to know those classes names because the instantiation process is simplified, there are no explicit constructors' calls: myName = "Andrei” # a string's instantiation myMarks = [79, 86, 55, 60] # a list's instantiation raySubjectsAndMarks = {"C": 69, "C++": 88, "Java": 95} # a dictionary's d instantiation # a stream's instantiation myResume = open("C:/users/Andrei/documents/my_resume.txt") We manipulate strings, containers and streams through a wide variety of library methods. 87
For example: myName.capi talize() myMarks.append(79) mySubjectsAndMarks.update({"Swift": 93}) buffer = myResume.read() Despite the fact we can, technically speaking, inherit those hidden classes and create our own, "improved" strings, containers, streams, we don't normally do that. But very often we use some sort of composition (Part 2, Lesson 5), and we nest containers. So, an element of a container can be another container: myName = "Andrei" mySubjectsAndMarks = {"C": 69, "C++": 88, "Java": myProfile = [myName, mySubjectsAndMarks] print(myProfile[1]["Java"]) # a string's Ц instantiation 95} # a dictionary's # instantiation # composition # accessing # nested element 88
Part 3. Framework tkinter Lesson 1. Hello, tkinter! Small console demo applications we have developed so far are good to illustrate basic programming concepts, but, honestly, are not very practical. Those times, when we used console commands and applications to work with computers, are gone long time ago. Nowadays, we are all used to intuitive, user friendly interfaces with windows, drop-down menus, buttons, scrollbars, entry fields, graphics, multimedia etc. We call these modern applications — GUI (Graphical User Interface) applications as opposed to old console applications. Modern applications are definitely easier to use, but they are harder to develop than console apps Comprehensive libraries and toolsets (so-called frameworks) were created for programmers to facilitate GUI applications development — Windows SDK, MFC, Swing, Android SDK, Cocoa Touch and many others. The majority of them are Object-Oriented and in order to master them we need a solid OOP foundation Python developers use tkinter framework to create GUI desktop applications; let's start from the simplest — "Hello, tkinter!". 1 from tkinter ort * 2i 3 root = Tk() 4 root.title("My First GUI App1) 5 root.geometry("400x300 ) 6 7IblMixer = Label(master-root, text="Hello, tkinter1", font=("Arial", 40, "bold')) IblMixer.pack() 9 10 root.mainloopO Figure 3-1-1. "Hello, tkinter!" — coding. 89
/ My First GUI App —OX Hello, tkinter! Figure 3-1-2. "Hello, tkinter!" — test. In the Г’ line we connected to the program all the classes from tkinter library, in the 3rd — we set up the application's mam window; an object root was instantiated by calling Tk class constructor By calling methods title () and geometry (), we set up the main window's title and initial size It doesn't mean, the user can't change the size after, — just try drag the window border. In line #7 we instantiated our first widget. Label is probably the simplest type of GUI widget; it represents a piece of text Keyworded arguments were used (Part 1, Lesson 10). Argument master is to define a parent window the label belongs to Argument text is probably the most important, because it is the actual text we want to display. Finaly, argument font is a tuple with 3 items1 font name, font size and font style. Pay attention it is not enough to instantiate a label; we have to visualize it by calling method pack (). The last line of the program is needed to initialize the so-called messages loop. The point is an Operating System is communicating with the programmer through various messages In order to be able to catch and process those messages we need the messages loop to be created. Definition: widget is a visual element of the Graphical User Interface. Examples of widgets: labels, buttons, check marks, scrollbars, entry fields etc. All the widgets are implemented as tkinter classes. Widgets have to be first — instantiated, secondly — added to the screen 90
Lesson 2. Color Mixer with Labels The Label constructor has lots of parameters, but the good news is almost all of them have default values, so we can omit many parameters, and the instantiating line doesn't have to be that long. The most important Label constructor's parameters are • master (the parent window) * text (the actual message) * font (the font of the text) • fg (foreground color) • bg (background color). You may ask: "wait a second... there is something missing . where exactly the text is positioned within a window?" The answer is "don't worry, it is taken care of"; tkinter positions a label at the top and at the center of the window. Try to resize the window, and the label is automatically repositioned! If you add another label to the window using pack () method, it is located below the first one and is also centered; you may use with the pack {) method pady parameter to add some vertical space between the labels In order to demonstrate labels in action we will create a simple Color Mixer app Any color in computer graphics is defined as so-called RGB combination of 3 numbers, where each number is in 0-255 range. RGB stands for "Red, Green and Blue". So, the first number defines the amount of red, the second — the amount of green and the third — the amount of blue For example, (255, 0, 0) is pure red, (0, 255. 0) is pure green, (0, 0, 255) is pure blue, (0, 0, 0) is black, and (255, 255, 255) is white There is a second format to present colors, it uses hash symbol and 3 hexadecimal numbers in OO-ff range. For example, #f f 00CH। is pure red, # OOff00 is pure green, tfOOOOff is pure blue, #000000 is black, and #f f f f f f is white; tkinter uses the second form; in order to change the label's colors, we use config () method. For instance: aLabel.ccnfig(fg="#ff0000", bg-"#00ff00") Let's build Color Mixer GUI app using labels. 91
from tkinter import * # 1-2 from random import * seed!) # 4 root = Tk() # 6-8 root.title("Color Mixer") root.geometry("400x300") IblMixer = Label(master=root, width=10, font=("Arial", 4C, "bold"))#10-11 IblMixer.pack(pady=20) red = randint(0, 255) # 13-16 redText = "RED: " + str(red) + "/255" IblRed = Label (master=root, text.-=redText, font= ("Arial", 30, "bold"),\ fg="red") IblRed.pack () green = randint(0, 255) greenText = "GREEN: " + str(green) + "/255" IblGreen = Label(master=root, text-greenText, font=("Arial", 30,"bold"),\ fg="green") IblGreen.pack() blue = randint(i, 255) blueText = "BLUE: " + str (blue) + "/255" IblBlue = Label(master=root, text=blueText, font=("Arial", 30,"bold"),\ fg="blue") IblBlue.pack() mixedColor = "#{0:02x}(1:02x}{2:02x}".format(red, green, blue) #28-29 IblMixer . config (bg-’mixedColor ) root.mainloop() #31 Figure 3-2-1. Color Mixer with labels — code. In the first two lines we connected to the program two libraries- tkinter and random; random library contains a set of functions to generate random numbers Then we initialized random number generation by calling seed () function In lines 6-8 we create a main window and set up its title and size. In lines 10-11 the first label is instantiated and visualized. This label doesn't have any text, because we will use it just as a placeholder to show different colors In lines 13-16 we generate a random number for the red component (the higher this number the stronger the red); then we create a label to describe red color and add that label to the screen After that we repeat the same steps for green and blue colors. Library method format () is used to assemble strings based on a list of variables and various for mat specifiers. 92
The syntax is. resulting_string = formatting string.format(list_of variables) The formatting string consists of normal characters and placeholders within curly braces. The whole construction is working this way. the formatting_ string is processed from the left to the right. Normal characters are copied to the resulting string as they are, but placeholders are replaced by the variables values from the list and also inserted into the resulting_string. Placeholders consist of the variable's number and format specifier. For example {1: 02x} means convert the second variable from the list into hexadecimal format with two digits (the variables in a list are numbered from zero). There are many different format specifiers, check the Python reference for the detailed info (https //www.w3schools com/python/ref string format.asp). So, we have to have as many placeholders as many variables. We used format () method to assemble hexadecimal code that defines the color, for instance: tfaOblf f. In line 28 we assembled the color code; in line 29 we changed the label's background color. □ X f Color M«er RED: 240/255 GREEN: 69/255 BLUE: 39/255 Figure 3-2-2. Color Mixer with labels — 2 rests. 93
Lesson 3. Object-oriented Color Mixer with Labels OOP paradigm is a way of thinking, sort of mental discipline We will not change in this lesson Color Mixer as such, we will just wrap it into "OOP paper". Let's create a new class named Form, where we combine all the widgets and related variables So, our Form class will have the following instance variables: • IblMd xer (an object of the Label class to mix various colors) • red (an integer variable for the red component of the color, a random number between 0 and 255) • redText (a string variable, a text on a label for the red component of the color) • IblRed (an object of the Label class to visualize the red component of the color) • green (an integer variable for the green component of the color, a random number between 0 and 255) • greenText (a string variable, a text on a label for the green component of the color) • IblGreen (an object of the Label class to visualize the green component of the color) • blue (an integer variable for the blue component of the color, a random number between Oand 255) • blueText (a string variable, a text on a label for the blue component of the color) • IblBlue (an object of the Label class to visualize the blue component of the color) We apply here composition technique (Part 2 Lesson 5) to build a class Form, because objects of an existing class Label are embedded into a new class as instance variables; we used prefixes "Ibl" to emphasize their nature: they are not variables of primitive types, they are objects. So, the application Color Mixer will be divided into two files: the main script and the Form class. from tkinter import * from random import * from form_class import * seed() root = Tk() root.ci tie("Color Mixer") root.geometry("400x300") form = Form(root) # instantiation, constructor call root.mainloop() Figure 3-3-1. Object-oriented Color Mixer with labels — mam script. The script does look elegant, because all the technical stuff is encapsulated into a Form object. We need to pass the reference to the main window to the constructor because all the widgets need that reference to be instantiated. 94
from tkinter import * from random import * class Form: def init (self, mainWnd): self.IblMixer = Label(master=mainWnd, width=lC,\ font=("Arial", 40, "bold")) self.IblMixer.pack(pady=20) self.red = randint(0, 255) self.redText = "RED: " + str (self.red) + "/255" self.IblRed = Label(master=mainWnd, text=self.redText, \ fcnt=("Arial", 30, "bold"),fg="red") self.IblRed.pack() self.green = randint(0, 255) self.greenText = "GREEN: " + str(self.green) + "/255" self. IblGreen = Label (master=mainWnd, text.=self .greenText, \ font=("Arial", 30, "bold"), fg="green") self.IblGreen.pack() self.blue = randint(0, 255) self.blueText = "BLUE: " + str(self.blue) + "/255" self.IblBlue = Label(master=mainWnd, text=self.blueText,\ font=("Arial", 30, "bold"), fg="blue") self.IblBlue.pack() mixedColor = "${0:02x}{1:02xJ{2:02x)". format(self.red,\ self.green, self.blue) self.IblMixer.config(bg-mixedColor) Figure 3-3-2. Class Form with labels. You can see that we just extracted the logic from the previous version of the program and inserted it into constructor. All the instance variables must have prefix seif; but mixedColor is a local variable, it doesn't need prefix self. If you compare the Lesson 2 and Lesson 3 versions of the Color Mixer, you may first feel skeptical about the advantages OOP provides You may even think that the first version without classes is simpler and more intuitive. Yes, for a small projects OOP may be just an overkill Also, let's not forget that OOP is still recommendation to software developers. You may decide whether to use it or not. At least Python doesn’t force us The thing is. whether you like OOP or not, you have to learn It well, it is used today in many languages, libraries and frameworks. OOP is de-facto programming standard. 95
Lesson 4. Buttons, Frames, Event Handlers We just learned the first widget — Label. Button and Fr ame are the next two widgets we are going to cover. Button is an interactive ("clickable") widget. Frame is invisible widget to organize the other widgets. Frames are containers the other widgets belong to. Frames can be nested. Definition: Event Handler is a function (or a method) defined by a programmer and called by the Operating System when the user interacts with the widget. Some widgets are active (buttons, lists, entry fields etc.), they can trigger events, so event handlers are needed, some widgets are rather passive (labels, frames, bitmaps), they work as decorative elements of GUI, consequently, even handlers for them are not needed The Button constructor has many parameters, but the most important Button constructor parameters are • master (the parent window or frame) • text (the text on the button) • font (the font of the text) • fg (the color of the text) • bg (the color of the button) • command (event handler) The Frame constructor has one parameter: • master (the parent window or parent frame) When widgets are added to the frame with method pack (), they are placed vertically. If you need horizontal placement we use in method pack () argument side="left". Let's implement a new version of Color Mixer with frames and buttons. We will still use Label as a mixed color display. Each color would be manipulated by a pair of buttons, the first button is to increase the amount of a certain color, the second — to decrease. Three frames are used to group buttons in pairs. 96
Keyboard Focus Usually, users manipulate widgets using mouse and touchpad but also they can use a keyboard. Sometimes, keyboard actions can be even faster than mouse/touchpad clicks The widget that reacts on keyboard actions has so-called focus. Only one widget at a time may have focus. Users can change the focus by clicking on different widgets or (alternatively) by pressing TAB key (SHIFT+TAB combination is to move in the opposite order). Buttons with the focus has dotted outline. Programmers can also control the focus by calling the focus set () method. If a button has focus, you can press it by hitting the spacebar. / Coior M xer — □ X Figure 3-4-1. Color Mixer with six buttons (RED+ button has a focus right now) The mam script would be still the same (Figure 3-3-1). The Form class will have the following instance variables • IbiMixer (an object of the Label class to mix 3 colors) • red (an integer variable, red color value) • f rmRedControl (an object of Form class, a container to group two buttons horizontally) • btnRedlnc (an object of Button class, it is to increase the amount of red) • btnRedDec (an object of Button class, it is to decrease the amount of red) • green (an integer variable, green color value) • frmGreenControl (an object of Form class, a container to group two buttons horizontally) • btnGreen Inc (an object of Button class, it is to increase the amount of green) • btnGreenDec (an object of Button class, it is to decrease the amount of green) 97
• blue (an integer variable, blue color value) • frmBlueControl (an object of Form class, a container to group twi buttons horizontally) • btnBluelnc (an object of Button class, it is to increase the amount of blue) • btnBlueDec (an object of Button class, it is to decrease the amount of blue) In order to differentiate objects and variables of primitive types we use prefix Ibl for labels, btn for buttons, and frm for frames The Form class will have the following methods: • init_() (constructor) • funcRedlncO (the event handler attached to btnRedlnc) • funcRedDecO (the event handler attached to btnRedDec) • funcGreenlnc() (the event handler attached to btnGreenlnc) * funcGreenDec () (the event handler attached to btnGreenDec) * funcBluelnc() (the event handler attached to btnBluelnc) • funcBlueDec () (the event handler attached to btnBlueDec) • updateMixer () (a method called by the previous six event handlers) from tkinter import * class Form: def init (self, mainWnd): seif.IblMixer = Label(master=mainWnd, width=10, bg="black",\ font-("Arial", 40)) self. IblMixer .pack (pady=-20) # Red section self.red = 0 self.frmRedControl = Frame(master=mainWnd) #11-12 self.frmRedControl.pack(pady=10) self.btnRedlnc = Button(master=self.frmRedControl, text="REL +’’,\ command=self.funcRedlnc) self.btnRedlnc.pack(side="left") self.btnRedDec = Button(master=self.frmRedControl, text="RED -”,\ command=self.funcRedDec) self.btnRedDec.pack(side="left", padx=5) # Green section self.green = 0 self.frmGreenControl = Frame(master=mainWnd) self.frmGreenControl.pack(pady=10) self.btnGreenlnc = Button(master=self.frmGreenContro1,\ text="GREEN +", command=self.funcGreenlnc) self.btnGreenlnc.pack(si de="left") self.btnGreenDec = Button(master-self.frmGreenControl,\ text="GREEN -", command=self.funcGreenDec) self.btnGreenDec.pack(side="left", padx=5) 98
# Blue section self.blue = 0 self.frmBlueControl = Frame (master=mainWnci) self.frmBlueControl.pack(pady=l0) self.btnBluelnc = Button(master = self.frmBlueControl,\ text="BLUE + ", command=self.funcBluelnc) self.btnBluelnc.pack(side="left") self.btnBlueDec = Button(master = seif.frmBlueControl,\ text="BLUE , command-self.funcBlueDec) self.btnBlueDec.pack(side="left", paax=5) self.btnRedlnc.focus_set() # ser the keyboard focus # Six event handlers def funcRedlnc(self): if self.red < 255: self.red += 5 self.updateMixer () def funcRedDec(self): if self.red > 0: self.red -= 5 self.mixUpdateMixer () def funcGreenlnc(self): if self.green < 255: self.green += 5 self.updateMixer () def f uncGreer.Dec (seif) : if self.green > 0: self.green -= 5 self.updateMixer() def funcBluelnc(self): if self.blue < 255: self.blue += 5 self.updateMixer() def funcBlueDec(self): if self.blue > 0: self.blue -= 5 self.updateMixer() # color update def updateMixer(self) : mixedColor = "#{0:U2x}{1:02xЦ2:02x}".format(self.red,\ self.green, self.blue) self.IbLMrxer.config(bg=mixedColor) F igure 3-4-2. Class Form with frames and buttons. The idea of this new version of Color Mixer is that colors are not random anymore, they are manipulated by six buttons. In lines #11-12 a new frame is instantiated, and it is added to the main window. In the next four operators twr buttons to control red component are instantiated and added to the frame horizontally. Green and blue sections are very similar When a button is instantiated the command argument provides a reference to the event handler. We attached six event handlers to six buttons; they are called when the corresponding buttons are clicked. These methods just change the value of the red, green or blue component; after that updateMi xer () helper method assembles the mixed color string and updates IblMixer. 99
Lesson 5. Checkbuttons and Associated Variables tkinter class Checkbutton is to incorporate in our apps a check mark. So, this widget can have two states: ON or OFF. if Color Mixer □ X P RED P GREEN Г BLUE Figure 3-5-1. Color Mixer with three check buttons. In a new Color Mixer version, using 3 check buttons, we can produce 8 different colors: * black (no red + no green + no blue) • red (red + no green + no blue) • green (no red + green + no blue) • blue (no red + no green + blue) * yellow (red + green + no blue) • purple (red + no green + blue) • cyan (no red + green + blue) • white (red + green + blue) The most important Checkbutton constructor parameters are: master (the parent window or frame) text (the text on the right side of the checkmark) command (the event handler) va riable (an integer associated variable, it is synchronized with the check button, could be 0 or 1) The most popular Checkbutton methods are: • select (to put a check mark programmatically) * deselect (to remove a check mark programmatically) The main script would be still the same (Fig. 3-3-1). 100
The Form class will have the following instance variables: • IbiMixer (a label to show mixed color) • red (an integer variable, could be 0 or 1, signifies the presence of the red color) • chbRed (Checkbutton object for red) • green (an integer variable, could be 0 or 1, signifies the presence of the green color) • chbRed (Checkbutton object for green) • blue (an integer variable, could be 0 or 1, signifies the presence of the blue color) • chbBlue (Checkbutton object for blue) The Form class will have the following methods: • _init_ () (constructor) • onCheckUncheck () (an event handler for all three check buttons) from tkinter import * class Form: def init _(self, mainWnd): self.IbiMixer = Label(master-mainWnd, width=10, bg-"black",\ font=("Aria 1", 40)) self.IbiMixer.pack(pady=20) # Retrieve 3 associated variables self.red = IntVarO self.green = IntVarO self.blue = IntVarO # Instantiate and pack 3 check buttons # Connect the event handler and three associated variables self.chbRed = Checkbutton(master-mainWnd, text“"RED",\ command=self.onCheckUncheck, variable=self.red) self.chbRed.pack() se If.chbGreen = Checkbutton(master-mainWnd, text="GREEN",\ command=self.onCheckUncheck, variable=self.green) self.chbG reen.pack() self.chbBlue = Checkbutton(master=mainWnd, text="BLUE",\ command=self . onCheckUncheck, variable=self .glue) self.chbBlue.pack() self.chbRed.focus_set() 4 set the focus # Event handler def onCheckUncheck(self): mixedColor = "#{0:02x}{1:02x}{2:02x}".format(self.red.get() *\ 255, self.green.get() * 255, self-blue.get() * 255) self.iblMixer.config(bg=mixedCoior) Figure 3-5-2. Color Mixer with three check huttons — code. 101
We used prefix chb for the objects of Checkbutton class. It is quite common practice to combine event handlers. In other words, we may hook the same code to different widgets. Three integer variables (red, green and blue) are so-called associated variables. They are linked to three widgets whenever the widget's state is changed, associated variables are changed automatically, they shadow widgets. The event handler reads the states of 3 check buttons through associated variables and then mixes the resulting color. The use of the associated variables involves the following steps: 1) retrieve the associated variable using IntVar () library function, 2) link it to the widget when the widget is instantiated, 3) get the associated variable's value using method get(), 4) change the associated variable's value using method set (), don't try to access associated variables directly (see Part 2 Lesson 4 Data Hiding). Lesson 6. Radiobuttons tkinter Radiobutton class is to implement in GUI app a group of mutually exclusive choices. The name comes from history. Long time ago we used to have radio receivers with mutually exclusive ranges of frequences, when you push one button, the other one (which was pressed) is popped up. Figure 3-6-1. Old-fashiuned radio receiver with 8 buttons. The new version of Color Mixer will have three groups of radio buttons: for red, green, and blue components, each group in turn will consist of 3 buttons; they will control the amount of red, green and blue; NONE means 0, WEAK — 127, STRONG — 255 . So, now we can build 27 different colors (3*3*3). 102
/ Color Mixer RED GREEN r NONE Г NONE t? weak; WEAK Г STRONG C STRONG BLUE Г NONE С ЛЕАК STRONG Figure 3-6-2. Color Mixer with radio buttons. We use horizontal placement for the main window: the mixer label + 3 frames Inside each of those frames we use vertical placement: the color name label + 3 radio buttons. Remember, pack () method by default adds widgets vertically, but if you provide parameter: pack (side="lef t"), widgets are added horizontally. By nesting frames and changing widgets orientation within frames you can achieve quite sophisticated layouts that suit users' needs. As you see frames may have borders. The most important Radiobutton constructor parameters are: • master • text • command • variable • value (the parent window or frame) (the text on the right side of the radio button) (the event handler) (the associated variable's name) (the value of the associated variable when this button will be pressed) By connecting different radio buttons to the same associated variable, we logically group them. The most common Radiobutton method is • select (to select a radio button programmatically) The mam script would be still the same (Fig. 3-3-1), but you may increase the width of the main window. The Form class will have the following instance variables. * IblMixer (a label to show mixed color) • red (the associated integer variable, could be 0,127 or 255, signifies the amount of red) * f rmRed ( a frame to combine text "RED" and three radio buttons for red) • IblRed ( a label "RED") 103
• rbtRedNone (Radiobutton object for "no red") • rbtRedWeak (Radiobutton object for "weak red") • rbtRedStrong (Radiobutton object for "strong red") • green (the associated variable, could be 0,127 or 255, signifies the amount of green) • f rmGreen ( a frame to combine text "GREEN" and three radio buttons for green) • IblGreen (a label "GREEN") • rbtGreenNone (Radi ©button object for "no green") • rbtGreenWeak (Radiobutton object for "weak green") • rbtGreenStrong (Radiobutton object for "strong green") • blue (the associated integer variable, could be 0,127 or 255, signifies the amount of blue) • f rmBlue ( a frame to combine text "BLUE" and three radio buttons for blue) • IblBlue (a label "BLUE") • rbtBlueNone (Radiobutton object for "no blue") • rbtBlueWeak (Radiobutton object for "weak blue") • rbtBlueStrong (Radiobutton object for "strong blue") The Form class will have the following methods: • init () (constructor) • onButtonClick () (an event handler for all nine radio buttons) from tkinter import. * class Form: def init (self, mainWnd): # Mixer self. IbiMixer = Label(master=mainWnd, width=6, bg="bJack",\ font=("Arial", 30)) self.IbiMixer.pack(side="left", padx=10) # Integer variable associated with the red radio group self.red = IntVarO self.red.set(0) # Widgets to control red component self.frmRed = Frame(master=mainWnd, borderwidth=2, relief=SOLID) self.frmRed.pack(side="left") self.IblRed = Label(master=self.frmRed, text="RED",\ font-("Arial", 15, "bold"), fg="red") self.IblRed.pack() self.rbtRedNone = Radiobutton(master=self.frmRea, text="N0NE", \ variable=self.red, value-0, command-self.onButtonClick) self.rbtRedNone.select() self.rbtRedNone.pack() 104
self.rbtRedWeak = Radiobutton(master=self.frmRed, text="WEAK",\ vardable=self.red, vaiue=127, command=self.onButtonClick) self.rbtRedWeak.pack() self.rbtRedStrong = Radiobutton(master=self.frmRed,\ text="STRONG", variable=self.red, value=255,\ command=self.onButtonClick) self.rbtRedStrong.pack() # Integer variable associated with the green radio group self.green = IntVarO self.green.set(0) # Wiagets to control green component self.frmGreen = Frame(master=mainWnd, borderwidth=2,\ relief-SOLID) self.frmGreen.pack(side="left", padx=5) self.IblGreen = Label(master=self.frmGreen, text="GREEN",\ font=("Arial", 15, "bold"), fg-"green") self.IblGreen.pack() self.rbtGreenNone = Radiobutton(master=self.frmGreen, \ text="NONE", variable-self.green, value=C,\ command=self.onButtonClick) self.rbtGreenNone.pack() self.rbtGreenNone.select() self.rbtGreenWeak = Radiobutton(master=self.frmGreen, \ text="WEAK",\ variable=self.green, value=127, command=self.onButtonClick) self.rbtGreenWeak.pack f) self.rbtGreenStrong = Radiobutton(master=self.frmGreen, \ text="STRONG" , variable=self.green, value=255,\ command=self.onButtonClick) self.rbtGreenStrong.pack() # Integer variable associated with the blue radio group self.blue = IntVarO self.blue.set(0) # Wiagets to control blue component self.frmBlue = Frame(master=mainWnd, borderwidth=2, relief=SOLlD) self.frmBlue.pack(side="left", padx=5) self.IblBlue = Label(master=self.frmBlue, text="BLUE",\ font=("Arial", 15, "bold"), fg="blue") self.IblBlue.pack() self.rbtBlueNone = Radicbutton(master=self.frmBlue, text="NONE",\ variable=self.blue, value=0, command=self.onButtonClick) self.rbtBlueNone.pack() self.rbtBlueNone.select() self.rbtBlueWeak = Radiobutton(master=self.frmBlue, text="WEAK",\ variable=self.blue, value=127, cornmand=self. onButtonClick) self.rbtBlueWeak.pack() seIf.rbtBlueStrong = Radiobutton(master=self.frmBlue,\ text="STRONG", variable=self.blue, value=255,\ command=self.onButtonClick) self.rbtBlueStrong.pack() seIf.rbtRedNone.focus set() # set the keyboard focus 105
# event handler def cnButconClick(self) : mixedColor = "#{C:92x}{l:02x}{2:02x}".format(self.red.get(),\ self.green.get(), self.blue.get()) self.IblMixer.config(bg^mixedColor) Figure 3-6-3. Color Mixer with radio buttons — code. Prefixes allow us to reuse the same name in coding, for example: red is an integer, f rmRed is a frame, IblRed is a label We use prefix rbt for radio buttons. When the user clicks radio buttons the right values are automatically saved in the associated variables: red, green, or blue Then m the event handler we just have to do the rest: format and assemble those three values. Lesson 7. Listboxes and Events Binding tkinter Listbox class is to implement in GUI app a list of choices. Usually, the user can pick just one choice from the list, but it is possible to implement list boxes with multiple choices too. We learn the most common list box with one choice in this book. The new version of Color Mixer will have three list boxes: for red, green, and blue components; each list in turn will have 11 grades of color: from 0% to 100%. So, now we can build 1331 different col ars (11 * 11 * 11). List boxes functionality is similar to radio groups, but list boxes may have much bigger capacity (range of options), because you can scroll through the list box using mouse wheel, touchpad (on a laptop) or UP and DOWN keys, if the list box has the keyboard focus. Figure 3-7-1. Color Mixer with three list boxes. We use vertical placement for the mam window one mixer label + one frame. Inside the frame we use horizontal placement: red label + list box for red + green label + list box for green + blue label + list box for blue 106
The most important Listbox constructor's parameters are • master (the parent window or frame) • height (how many items from the list are visible at a time) • exportselection (True or False; if True, the selected item highlight disappears when you leave the list, if False, the highlight stays even you move the focus) • xscrollcommand (reference to the horizontal scrollbar if it is attached) • yscrollcommand (reference to the vertical scrollbar if it is attached) Pay attention that the list box's constructor doesn't have parameter command. We will attach the event handler using a special method. The most important Listbox methods are • insert () • selection set() • bind () • curselection () (populate list box with the values from the tuple or list) (select an item from the list programmatically) (attach an event handler) (retrieve the current user's selection) The mam script would be still the same (Figure 3-3-1), but you may adjust the sizes of the main window. The Form class will have the following instance variables: • IblMixe r (a label to show mixed color) • choices (a tuple with items' names to populate three list boxes) • frm6Items (a frame to group six widgets horizontally) • Ibl Rea (the label "RED") • IbxRed (a list box to choose the grade of red) • IblGreen (the label "GREEN") • IbxGreen (a list box to choose the grade of green) • IblBlue (the label "BLUE") • IbxBlue (a list box to choose the grade ol blue) The Form class will have the following methods: • _init () (constructor) • onChange () (the event handler, it is called whenever the selection is changed in any of three list boxes) 107
We will use in this application a new events handling technique - events binding. Method bind () can be applied to any widget and has two parameters: the name of the event in the angle brackets and the name of the event handier. All the events in tkinter are classified and have standard names. Compared to the old technique (with command parameter) the new one is more advanced, because your event handler will receive an object of Event class from which you can extract a lot of useful event-related information; for instance, the reference to the source of the event (the address of the widget that triggered the event). This technique is also more universal because not all the widgets' constructors have parameter command, but all of them can call method bind (). from tkinter import * class Form: def __init__(self, mainWnd): # Mixer self. IblMixer = Label(master=mainWnd, width=15, bg=’’black",\ font=("Arial", 30)) self.IblMixer.pack(pady=10) # List items self.choices = ("04", "10%", "20%", "304", "40..", "50%",\ "60%","70%", "80%", "90%", "100%") # Frame self.frm6Widgets = Frame(master=mainWnd) self.frm6Widgets.pack(pady=10) # Red group self.IblRed = Label(master=self.frm6Widgets, text="RED:",\ font=("Arial", 10, "bold"), fg="red") self.IblRed.pack(side="left", padx=L0) self.IbxRed = Listbox(master=self.frm6Widgets, height=5,\ export selection=False) self.IbxRed.pack(side="left") # populating red list box, selecting the first item self.IbxRed.insert(END, *self.choices) # asterisk is used self.IbxRed.selection set(first=0) * binding self.IbxRed.bind("<<LiscboxSelect>>", seif.onChange) # Green group se1f.IblGreen = Label(master=seif.frm6Widgets, text="GREEN:",\ font=("Arial", 10, "bold"), fg="green") self.iblGreen.pack(side="left") self.IbxGreen = Listbox(master=self.frm6Widgets, height=5,\ exportselection=False) self.IbxGreen.pack(side="left") # populating green list box, selecting the first item self.IbxGreen.insert(END, *self.choices) # asterisk is used seIf.IbxGreen.selection set (first=C) # binding self.IbxGreen.bind("<<ListboxSelecc>^" , self.onChange) 108
# Blue group self. IblBlue = Label (niaster=self . frm6Wi dgets, text="BLUE", \ font=("Arial", 10, "bold"), fg="blue") self.IblBlue.pack(side="left") self.IbxBlue = Listbox(master=self.frm6Widgets, height=5,\ exportselection-False) self.IbxBlue.pack(side="left") ttpopulating blue list box, selecting the first item self.IbxBlue.insert(END, *self.choices) # asterisk is used self. IbxBlue . selection_set (f irst.=0) # binding self.IbxBlue.bind("<<ListboxSelect>>", self.onChange) #event handler def onChange(self, event): r = int(*self.LbxRed.curselection()) * 255 // 10 # asterisk g = int(*self.IbxGreen.curselection ()) * 255 // 10 b = int(*self.IbxBlue.curselection ()) * 255 // 10 mixedColor = { 0 : 02x}{1: 02x}{2 : 02x}". format (r, g, b) self.IblMixer.config(bg=mixedColor) Figure 3-7-2. Color Mixer with list boxes — code. Review "Data Containers" topic (Chapter 2 Lesson 11): asterisk operator is used to extract values from containers; curselection () method returns us a tuple because multiple choice lists are also possible Lesson 8. Scrollbar Scrollbars can help users to go through a long list of choices more efficiently. As with all the widgets the Scrollbar object has to be 1) instantiated and 2) packed New version of the Color Mixer is the same as the previous, but scrollbars are added to facilitate the user's navigation: now he can do it not only by using keyboard UP and DOWN keys, touchpad, and mouse wheel but also through draggable scrollbars. Figure 3-8-1. Color Mixer with list boxes and scrollbars. 109
Obviously, any scrollbar should know which widget's content to scroll. Also, a scrollbar should change its thumb position when the list box selection is changed. So, we have to establish mutual relationship between those two widgets. The mam form layout is vertical, label + frame, but in the frame we place 9 widgets horizontally (3 labels, 3 list boxes and 3 scrollbars). Scrollbar constructor's parameters are: • master (the parent window or frame) • orient (VERTICAL/HORIZONTAL) • command (reference to the property yview of the associated list box, which means the list box must be instantiated before the scrollbar) The Form class will have the following instance variables; • IbiMixer (a label to show mixed color) • choices (a tuple with items' names to populate three list boxes) • f rm91tems (a frame to group nine widgets horizontally) • IblRed (the label "RED") • IbxRed (a list box to choose the grade of red) • scrRea (a scroll bar to go through red color's grades) • IblGreen (the label "GREEN") • IbxGreen (a list box to choose the grade of green) • scrGreen (a scroll bar to go through red color's grades) • IblBlue (the label "BLUE") • IbxBlue (a list box to choose the grade of blue) • scrBlue (a scroll bar to go through blue color's grades) The Form class has the following methods: • __init____() (constructor) • onChange () (the event handler, it is called whenever the selection is changed in any of three list boxes; pay attention, scrollbars manipulations don't change the selection as such, they change the view) The coding is almost identical to the previous Color Mixer; the only difference is mutual linking of the list box and the scroll bar (those 2 statements are highlighted). 110
from tkinter imoort * class Form: def __init (self, mainWnd): # Mixer self.IbiMixer = Label(master=mainWnd, width=15, bg="black",\ font=("Arial", 30)) self.IbiMixer.pack(pady=l0) # List items self.choices = ("0%", "10%", "20%", ”30%", "40%*, "50%",\ "601","70%", "80."90%", "100s") # Frame s elf.frm9Widgets = Frame(master=mainWnd) self.frm9Widgets.pack(pady=10) # Red group self.IblRed = Label(master=self.frm9Widgets, text="RED:",\ font=("Arial", 10, "bold"), fg="red") self.IblRed.pack(side="left") self.lbxRed = Listbox(master=self.frm9Widgets, height=5,\ export select ion=False) self.IbxRed.pack(side="left") self.IbxRed.insert(END, *self.choices) self.IbxRed.selection_set(first=0) # Instantiating and linking scrollbar to list box self.scrRed = Scrollbar(master=self.frm9Widgets,\ orient=VERTICAL,\ command=self.IbxRed.yview) # Linking list box to scrollbar self.IbxRed.config(yscrollcommand=self.scrRed.set) * Packing and stretching scrollbar self.scrRed.pack(side-"left", fill="both") # Binding self.IbxRed.bi nd("<<ListboxSelect>>", self.onChange) ft Green group Figure 3-8-2. Color Mixer with list boxes and scrollbars — code (the beginning). There is a new parameter fill that we used in method pack (): it can stretch the widget inside the allocated space, — you can stretch horizontally, vertically or both directions. Ill
Lesson 9. Entry Fields and Grid Layout The Entry widgets are truly ubiquitous, — we need to accept what user typed. You may think about Entry widget as a GUI version of the console input () function (see Part 1 Lesson 6). Talking about physical placement of the widgets on the screen, there is an alternative to pack () method — it is called grid (); we divide the space of the form into virtual rows and columns and then position widgets into concrete cells of the table; that's why grid () method's most important parameters are row and column. Quite often though we need to place a widget into the center of the entire row, in such situations we need the columnspan parameter. In our new Color Mixer app, we will divide the screen into 4 rows and 2 columns. Both rows and columns are numbered from 0. The mixer label at the top will span two columns. / Color Млег — С X RED (0 255|: FlOO I ___ GREEN (0 255): -255| BLUE (0-255). 170 Figure 3-9-1, Color Mixer with 3 entry fields» (green field has a focus) We face a couple of challenges when programming Entry fields: 1) the user can type whatever he wants, so, quite often we need input validation; 2) the user can finish input different ways — by pressing RETURN key, TAB key or by mouse click outside of the entry field Obviously, only one Entry widget at a time may have focus. The field with the focus has a blinking cursor. So, whenever you press TAB or click outside the entry filed, the FocusOut event happens, but when you press ENTER, you stay where you type, the FocusOut event doesn't happen. It is convenient to define an associated variable with the entry field, so it is updated automatically any time the user updates the field. Vice versa, a programmer can update the entry filed widget by updating the associated variable The associated variable can be initialized using StringVar () function and can be read and written using methods get () and set (). 112
The Entry constructor's parameters are: • master • textvariable (the parent window or frame) (associated variable) The Form class will have the following instance variables: • IblMixer (the label to show mixed color) • IblRed (the label "RED (0-255):") • red (the string variable associated with the red entry field ) • entRed (the entry field for red) • IblGreen (the label "GREEN (0-255):") • green (the string variable associated with the green entry field ) • entGreen (the entry field for green) • IblBlue (the label "BLUE (0-255):") • blue (the variable associated with the blue entry field ) * ent Blue (the entry field for blue) The Form class will have the following methods: * _init _() (constructor) • onReturn (an event handler) • onFocusOut (an event handler) • onChange() (method called by both event handlers) • val idateColor () (entry field's validator, returns valid color or -1, if an error happens) Figure 5-9-2. Class Form’s methods relationship 113
from tkinter import * class Form: def init_ (self, mainWnd): # Mixer, row 0, columns 0-1 self. IbiMixer = Label (master-=mainWnd, width=10, bg="black",\ font=("Arial", 30)) self.IbiMixer.grid(row=0, column=0, columnspan=2, padx=15,\ pady=15) # Row 1 self.IblRed = Label(master=mainWnd, text="RED (0-255) :",\ font=("Arial", 10, "bold"), fg="red") self.IblRed.grid(row=l, column=0, padx=5, pady=5) self, red = StringVarO self.entRed = Entry(master=mainWnd, textvariable=self.red) self.entRed.grid(row=l, column=l, padx=5, pady=5) self. entRed.bind ("<Return.>", seif.onReturn) # 2 event handlers self.entRed.bind("<FocusOut>", self.onFocusOut) self.red.set("0") # Row 2 self.IblGreen = Label(master=mainWnd, text="GREEN (u-255) :",\ font=("Arial", 10, "bold"), fg="green") self.IblGreen.grid(row=2, column=0, padx=5, pady=5) self, green = StringVarO seIf.entGreen = Entry(master=mainWnd, textvariable=self.green) self.entGreen.grid(row=2, column=l, padx=5, pady=5) self. entGreen .bind ("cReturn>", self . onReturn) self.entGreen.bind("<FocusOut>", self.onFocusOut) self.green.set("0") # Row 3 self.IblBlue = Label(master=mainWnd, text="BLUE (0-255) :",\ font®("Arial", 10, "bold"), fg="blue") seIf.IblBlue.grid(row=3, column=0, padx=5, pady=5) self.blue = StringVarO self.ent.Blue = Entry (master=mainWnd, textvariable=self.blue) self.entBlue.grid(row=3, column=l, padx=5, pady=5) self.entBlue.bind("<Return^", self.onReturn) self.entBlue.bind("<FocusOut>", self.onFocusOut) self.blue.set("0") self. entRed. focus_set. () # set focus on red field def onReturn(self, event): event.widget.tk EocusNext().focus_set () # moving focus self.onChange() def onFocusOut(self, event): self.onChange() 114
def onChange(self): r = self.validateColor(self.red.get()) if r == -1: self.red.set("ERR!") return g = self.validateColor(self.green.get()) if g ----1: self.green.set("ERR!" ) return b = seif.validateColor(seif.blue.get()) if b == -1: self.blue.set("ERR! ") return mixedColor = "#{0:02x}{1:02x}{2:02x}".format(r, g, b) self.IblMixer.config(bg-mixedColor) def validateColor(self, userinput): try: result = int(userInput) # conversion, "risky" line except: result = -1 # conversion failed else: if result < 0 or result > 255: И conversion succeeded result = -1 return result Figure 3-9-3. Color Mixer with 3 entry fields — code We attach to each Entry widget two event handlers: 1) if ENTER key is pressed, 2) if the input focus is lost. In both cases we recalculate the mixer color. The focus could be lost because the user hit TAB key or click on the other entry field Because ENTER key hit doesn't change the focus, we do it programmatically. You can see here how you can identify the source of the event by extracting from the event object widget property. The onChange () method checks the content of 3 fields, and if it is not valid, it is replaced by the "ERR!" message, the mixed color is not updated. If all the fields are valid, the color is updated The va 1 idateColor () method is checking the user's input; it returns an integer: -1 (not valid input) or the RGB code between 0 and 255 This method relays on casting a string to an integer. The casting may fail for obvious reasons: non digital characters were typed In such case, an exception is raised (Part 1 Lesson 9). So, we use try-except-else statement to handle (or catch) the exception This way we prevent our program from crashing. 115