Legkiy sposob vyuchit Python
Sorry, preview is currently unavailable. You can download the paper by clicking the button above.
See Full PDF
Free Related PDFs
Download Free PDF View PDF
Download Free PDF View PDF
![]()
ФГБОУ ВО «Кубанский государственный технологический университет» Кафедра информационных систем и программирования ТЕСТИРОВАНИЕ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ Методические указания по выполнению лабораторных работ для студентов всех форм обучения и МИППС направления 09.03.04 Программная инженерия Краснодар 2017
Download Free PDF View PDF
10 ценных Python-советов: для начинающих, но не только
Нам нравится Python за универсальность и скорость разработки. Мы хотим, чтобы пайтонистов становилось больше, а их скиллы преумножались и прокачивались. 30 мая запускаем новый поток обучения разработчиков на Python. А пока делимся удобными приёмами, которые эксперт TechBeamers собрал из разных источников, включая вебинары по программированию на Python, Stack Overflow и Wikipedia. Прежде чем попасть в обзор, каждый из подходов прошёл экспертный отбор, то есть его удобство и эффективность проверены на практике.
Эти советы пригодятся и разработчикам, и тестировщикам. Некоторые из них будут новыми и полезными даже для опытных программистов на Python.
Среди причин создания такого пособия — растущая популярность Python как языка программирования, обеспечивающего высокую скорость разработки. Оглянитесь вокруг, и вы увидите, что он эффективен везде — от различных конфигурационных инструментов до анализа XML.
Сейчас доступны разные версии Python, но большинство программистов предпочитают использовать Python версий 2.x и 3.x. Все предлагаемые советы работают в каждой из них.
1. Запуск скриптов Python
На большинстве систем UNIX можно запускать скрипты Python из командной строки следующим образом:
# run python script
2. Запуск программ на Python из интерпретатора
Интерактивный интерпретатор Python очень прост в использовании. Вы можете совершить свои первые шаги в освоении этого языка, просто набирая любые команды в консоли Python одну за другой, и немедленно получать результаты.
Консоль Python можно запустить с помощью команды:
# start python console
*В этой статье весь код, идущий после >>>, нужно набирать в строке ввода Python. Важно помнить, что Python очень серьезно воспринимает табуляцию, так что если вы получаете какие-либо ошибки с её упоминанием, то исправьте выравнивание.
3. Использование функции enumerate()
Функция enumerate() добавляет счетчик в итерируемый объект, в котором используется метод __iter__ , возвращающий итератор. Он может принимать последовательные значения индекса, начиная с нуля. И выдаёт ошибку IndexError, когда индексы больше недействительны.
Типичный пример использования функции enumerate() — создание цикла по списку с отслеживанием индекса. Для этого можно использовать переменную в качестве счетчика. Но функция enumerate() позволяет сделать то же самое намного удобнее.
# First prepare a list of strings
subjects = ( ‘Python’ , ‘Coding’ , ‘Tips’ )
for i, subject in enumerate (subjects):
print (i, subject)
0 Python
1 Coding
2 Tips
4. Тип данных SET
Тип данных «set» — это своего рода коллекция. Она стала частью Python, начиная с версии 2.4. Множество содержит неупорядоченную коллекцию уникальных и неизменяемых объектов. Это один из типов данных Python, реализующих множества из мира математики. Множества, в отличие от списков или кортежей, не могут содержать дублей.
Если вы хотите создать множество, просто используйте встроенную функцию set() с последовательностью или другими итерируемыми объектами в качестве аргументов.
# *** Create a set with strings and perform search in set
# Print set.
print (objects)
print ( len (objects))
# Use of «in» keyword.
if «tips» in objects:
print ( «These are the best Python coding tips.» )
# Use of «not in» keyword.
if «Java tips» not in objects:
print ( «These are the best Python coding tips not Java tips.» )
< 'python' , 'coding' , 'tips' , 'for' , 'beginners' >
5
These are the best Python coding tips.
These are the best Python coding tips not Java tips.
Добавление объектов в множество:
# *** Lets initialize an empty set
items = set ()
# Add three strings.
items.add( «Python» )
items.add( «coding» )
items.add( «tips» )
5. Динамический ввод
В Java, C++ и других статически типизированных языках нужно указывать тип данных возвращаемого значения функции и тип каждого её аргумента. Напротив, Python, как динамически типизированный язык, не вынуждает явно указывать типы данных. На основе присвоенных значений Python отслеживает их сам.
Вот ещё одно хорошее определение динамической типизации:
«Имена связываются с объектами во время выполнения с помощью операторов присваивания. И существует возможность прикрепить имя к объекту другого типа прямо во время выполнения программы».
В следующем примере показано, как функция может проверять свои аргументы и как делать разные вещи в зависимости от типа аргументов.
# Test for dynamic typing.
from types import *
def CheckIt (x):
if type (x) == IntType:
print ( «You have entered an integer.» )
else :
print ( «Unable to recognize the input data type.» )
# Perform dynamic typing test
CheckIt( 999 )
# Output:
# You have entered an integer.
CheckIt( «999» )
# Output:
# Unable to recognize the input data type.
6. Операторы == И =
Python использует «==» для сравнения и «=» — для присваивания. Присваивание внутри операторов не поддерживается. Поэтому нет никаких шансов случайного присваивания значений, если их нужно сравнить.
7. Условные выражения
Python допускает условные выражения. В этом языке есть интуитивно понятный способ написания условных выражений. В дальнейшей практике вы сможете следовать приведенному примеру:
# make number always be odd
number = count if count % 2 else count — 1
# Call a function if the object is not None.
data = data.load() if data is not None else ‘Dummy’
print ( «Data collected is » , data)
8. Конкатенация строк
Вы можете использовать оператор ‘+’ для конкатенации строк. Делается это так:
# See how to use ‘+’ to concatenate strings.
>>> print ( ‘Python’ + ‘ Coding’ + ‘ Tips’ )
Python Coding Tips
9. Метод __init__
Метод __init__ вызывается после того, как инстанцирован объект класса. Этот метод полезен для выполнения любой запланированной вами инициализации. Метод __init__ аналогичен конструктору в C++, C# и Java.
# Implementing a Python class as InitEmployee.py
class Employee( object ):
def __init__( self , role, salary):
self .role = role
self .salary = salary
def is_contract_emp(self):
return self .salary 1250
def is_regular_emp(self):
return self .salary > 1250
emp = Employee( ‘Tester’ , 2000 )
if emp.is_contract_emp():
print ( «I’m a contract employee.» )
elif emp.is_regular_emp():
print ( «I’m a regular employee.» )
print ( «Happy reading Python coding tips!» )
Вот как будет выглядеть результат этого кода:
[~/src/python $:] python InitEmployee.py
I’m a regular employee.
Happy reading Python coding tips!
10. Модули
Для сохранения удобства управления своими программами по мере их роста, вы можете разбить их на несколько файлов. Python позволяет поместить множество функций в файл и использовать их в качестве модуля. Эти файлы должны иметь расширение .py. В дальнейшем модули можно импортировать в другие скрипты и программы.
# 1- Module definition => save file as my_function.py
def minmax(a,b):
if a min , max = a, b
else :
min , max = b, a
return min , max
# 2- Module Usage
import my_function
x,y = my_function.minmax( 25 , 6.3 )
Хотите писать код на Python быстрее и круче? — Регистрируйтесь на курс #tceh.
Изучаем Python. Программирование игр, визуализация данных, веб-приложения 978-5-496-02305-4

Изучаем Python. Программирование игр, визуализация данных, веб-приложения. — СПб.: Питер, 2017. — 496 с.: ил. — (Серия «Библиотека программиста»). ISBN 978-5-496-02305-4 Книга «Изучаем Python» — это ускоренный курс, который позволит вам сэкономить время и сразу начать писать работоспособные программы (игры, визуализации данных, веб-приложения и многое другое). Хотите стать программистом? В первой части книги вам предстоит узнать о базовых принципах программирования, познакомиться со списками, словарями, классами и циклами, вы научитесь создавать программы и тестировать код. Во второй части книги вы начнете использовать знания на практике, работая над тремя крупными проектами: создадите собственную «стрелялку» с нарастающей сложностью уровней, займетесь работой с большими наборами данных и освоите их визуализацию, и, наконец, создадите полноценное веб-приложение на базе Django, гарантирующее конфиденциальность пользовательской информации. Если вы решились разобраться в том, что такое программирование, не нужно ждать. Ключ на старт и вперед!
12+ (В соответствии с Федеральным законом от 29 декабря 2010 г. № 436-ФЗ.) ББК 32.973.2-018.1 УДК 004.43
Права на издание получены по соглашению с No Starch Press Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
ISBN 978-1593276034 англ.
© 2016 by Eric Matthes
© Перевод на русский язык ООО Издательство «Питер», 2017 © Издание на русском языке, оформление ООО Издательство «Питер», 2017 © Серия «Вы и ваш ребенок», 2017
Об авторе . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Благодарности . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Введение . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Часть I. Основы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Глава 1. Начало работы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Подготовка среды программирования Решение проблем с установкой . . . . . Запуск программ Python в терминале Итоги . . . . . . . . . . . . . . . . . . . . . . . .
Глава 2. Переменные и простые типы данных . . . . . . . . . . . . . . . . . . 32 Что происходит при запуске hello_world .py Переменные . . . . . . . . . . . . . . . . . . . . . . . Строки . . . . . . . . . . . . . . . . . . . . . . . . . . . Числа . . . . . . . . . . . . . . . . . . . . . . . . . . . . Комментарии . . . . . . . . . . . . . . . . . . . . . . Философия Python . . . . . . . . . . . . . . . . . . Итоги . . . . . . . . . . . . . . . . . . . . . . . . . . . .
32 32 35 41 44 45 47
Глава 3. Списки . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 Что такое список? . . . . . . . . . . . . . . . . . . . . . . . . Индексы начинаются с 0, а не с 1 . . . . . . . . . . . . Упорядочение списка . . . . . . . . . . . . . . . . . . . . . Ошибки индексирования при работе со списками Итоги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Глава 4. Работа со списками . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 Перебор всего списка . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Оглавление Создание числовых списков Работа с частью списка . . . . Кортежи . . . . . . . . . . . . . . . Стиль программирования . . Итоги . . . . . . . . . . . . . . . . .
Глава 5. Команды if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Проверка условий . . . . . . . . . . . . . . . Команды if . . . . . . . . . . . . . . . . . . . . Использование команд if со списками Оформление команд if . . . . . . . . . . . Итоги . . . . . . . . . . . . . . . . . . . . . . . .
. 84 . 89 . 96 . 99 100
Глава 6. Словари . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 Простой словарь . . . Работа со словарями Перебор словаря . . . Вложение . . . . . . . . Итоги . . . . . . . . . . .
101 102 107 113 119
Глава 7. Ввод данных и циклы while . . . . . . . . . . . . . . . . . . . . . . . . . . 120 Как работает функция input() . . . . . . . . . . . . . . . . . . Циклы while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Использование цикла while со списками и словарями Итоги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
120 124 130 134
Глава 8. Функции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 Определение функции . . . . . . . Передача аргументов . . . . . . . . Возвращаемое значение . . . . . . Передача списка . . . . . . . . . . . . Хранение функций в модулях . . Стилевое оформление функций Итоги . . . . . . . . . . . . . . . . . . . .
135 137 142 147 154 158 159
Глава 9. Классы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 Создание и использование класса . . . . . . . . . . . Работа с классами и экземплярами . . . . . . . . . . Наследование . . . . . . . . . . . . . . . . . . . . . . . . . . Импортирование классов . . . . . . . . . . . . . . . . . Импортирование нескольких классов из модуля Стандартная библиотека Python . . . . . . . . . . . . Оформление классов . . . . . . . . . . . . . . . . . . . . Итоги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
161 165 170 176 179 182 184 184
Глава 10. Файлы и исключения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 Чтение из файла . . . Запись в файл . . . . . Исключения . . . . . . Сохранение данных Итоги . . . . . . . . . . .
185 193 195 204 209
Глава 11. Тестирование . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 Тестирование функции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 Тестирование класса . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216 Итоги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
Часть II. Проекты. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224 Программирование игры на языке Python . . . . . . . . . . . . . . . . . . . . . . . . . . 224 Визуализация данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224 Веб-приложения . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
Глава 12. Стреляющий корабль . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226 Планирование проекта . . . . . . . . . . . Установка Pygame . . . . . . . . . . . . . . . Создание проекта игры . . . . . . . . . . . Добавление изображения корабля . . Рефакторинг: модуль game_functions Управление кораблем . . . . . . . . . . . . В двух словах . . . . . . . . . . . . . . . . . . Стрельба . . . . . . . . . . . . . . . . . . . . . Итоги . . . . . . . . . . . . . . . . . . . . . . . .
226 227 231 235 238 240 247 248 255
Глава 13. Осторожно, пришельцы! . . . . . . . . . . . . . . . . . . . . . . . . . . . 256 Анализ проекта . . . . . . . . . . . . . . . . . . . Создание пришельца . . . . . . . . . . . . . . Построение флота . . . . . . . . . . . . . . . . Перемещение флота . . . . . . . . . . . . . . . Уничтожение пришельцев . . . . . . . . . . . Завершение игры . . . . . . . . . . . . . . . . . Определение исполняемых частей игры Итоги . . . . . . . . . . . . . . . . . . . . . . . . . .
256 257 260 266 270 274 280 280
Глава 14. Ведение счета . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281 Добавление кнопки Play Повышение сложности . Подсчет очков . . . . . . . Итоги . . . . . . . . . . . . . .
281 288 291 307
Глава 15. Генерирование данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 Установка matplotlib . . . . . . . . . . . . . . . Построение простого графика . . . . . . . . Случайное блуждание . . . . . . . . . . . . . . Моделирование бросков кубиков в Pygal Итоги . . . . . . . . . . . . . . . . . . . . . . . . . .
309 311 319 327 335
Глава 16. Загрузка данных . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336 Формат CSV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336 Формат JSON . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348 Итоги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361
Глава 17. Работа с API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362 Использование API веб-приложений . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362 Итоги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378
Глава 18. Знакомство с Django . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 380 Подготовка к созданию проекта . Начало работы над приложением Построение других страниц . . . . . Итоги . . . . . . . . . . . . . . . . . . . . .
380 385 398 407
Глава 19. Учетные записи пользователей . . . . . . . . . . . . . . . . . . . . . 408 Редактирование данных . . . . . . . . . . . . . Создание учетных записей пользователей Редактирование данных . . . . . . . . . . . . . Итоги . . . . . . . . . . . . . . . . . . . . . . . . . . .
408 419 428 435
Глава 20. Оформление и развертывание приложения . . . . . . . . . . . 437 Оформление приложения Learning Log . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437 Развертывание Learning Log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448 Итоги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465
Приложение А. Установка Python . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466 Python в системе Linux . . . . . . . . . . . . . . . . . . Python в OS X . . . . . . . . . . . . . . . . . . . . . . . . . Python в Windows . . . . . . . . . . . . . . . . . . . . . . Ключевые слова и встроенные функции Python
466 467 468 469
Приложение Б. Текстовые редакторы . . . . . . . . . . . . . . . . . . . . . . . . 471 Geany . . . . . Sublime Text IDLE . . . . . . Emacs и vim
471 474 476 477
Приложение В. Помощь и поддержка . . . . . . . . . . . . . . . . . . . . . . . . . 478 Первые шаги . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478 Поиск в Интернете . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479 IRC (Internet Relay Chat) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481
Приложение Г. Git и контроль версий . . . . . . . . . . . . . . . . . . . . . . . . . 483 Установка Git . . . . . . . . . . . . . . . . . . Создание проекта . . . . . . . . . . . . . . . Игнорирование файлов . . . . . . . . . . . Инициализация репозитория . . . . . . . Проверка статуса . . . . . . . . . . . . . . . Добавление файлов в репозиторий . . Закрепление . . . . . . . . . . . . . . . . . . . Просмотр журнала . . . . . . . . . . . . . . Второе закрепление . . . . . . . . . . . . . Отмена изменений . . . . . . . . . . . . . . Извлечение предыдущих закреплений Удаление репозитория . . . . . . . . . . .
483 484 484 485 485 486 486 487 487 488 489 490
Эрик Мэтиз (Eric Matthes), преподаватель физики и математики, живет на Аляске и ведет курс Python начального уровня. Эрик пишет программы с пяти лет, а в настоящее время занимается разработкой продуктов, которые исправляют недочеты в системе образования и помогают использовать возможности программных продуктов с открытым кодом в системе образования. В свободное время занимается альпинизмом и проводит время с семьей.
О научном рецензенте Кеннет Лав (Kenneth Love) — преподаватель и программист Python с многолетним стажем. Он выступал с докладами и лекциями на конференциях, занимался профессиональной подготовкой, работал внештатным программистом Python и Django, а в настоящее время ведет занятия в компании дистанционного образования. Кеннет также является одним из создателей пакета django-braces, предоставляющего удобные примеси (mixins) для представлений на базе классов Django. Желающие могут читать его сообщения в Твиттере (@kennethlove).
Моему отцу, который никогда не жалел времени, чтобы ответить на мои вопросы по программированию, и Эверу, который только начинает задавать мне свои вопросы.
Эта книга никогда бы не появилась на свет без великолепных, чрезвычайно профессиональных сотрудников издательства No Starch Press. Билл Поллок (Bill Pollock) предложил мне написать вводный учебник, и я глубоко благодарен ему за это. Тайлер Ортман (Tyler Ortman) помог привести в порядок мои идеи на ранней стадии подготовки чернового варианта. Лиз Чедвик (Liz Chadwick) и Лесли Шен (Leslie Shen) предоставили бесценные отзывы на исходные варианты каждой главы, а Энн Мэри Уокер (Anne Marie Walker) помогла прояснить многие части книги. Райли Хоффман (Riley Hoffman) отвечал на все вопросы, которые возникали у меня в процессе построения полной книги, и терпеливо превращал мою работу в прекрасный завершенный продукт. Также хочу поблагодарить Кеннета Лава (Kenneth Love), научного рецензента книги. Я познакомился с Кеннетом на конференции PyCon, и его энтузиазм в отношении языка и сообщества Python с тех пор неизменно оставался для меня источником профессионального вдохновения. Кеннет вышел за рамки простой проверки фактов; он следил за тем, чтобы книга помогала начинающим программистам сформировать основательное понимание языка Python и программирования в целом. Вместе с тем ответственность за все оставшиеся неточности лежит исключительно на мне. Я хочу поблагодарить своего отца, который познакомил меня с программированием в раннем возрасте и не побоялся, что я сломаю его оборудование. Также хочу сказать спасибо своей жене Эрин за поддержку и помощь во время работы над книгой и своему сыну Эверу, чья любознательность постоянно служит мне примером.
У каждого программиста найдется своя история о том, как он написал свою первую программу. Я начал изучать программирование еще в детстве, когда мой отец работал на Digital Equipment Corporation, одну из ведущих компаний современной эры вычислительной техники. Я написал свою первую программу на компьютере, который был собран моим отцом из набора комплектующих в подвале дома. Компьютер представлял собой системную плату (без корпуса), подключенную к клавиатуре, а в качестве монитора использовалась простейшая электронно-лучевая трубка. Моей первой программой стала игра по отгадыванию чисел, которая выглядела примерно так: Я загадал число! Попробуйте отгадать мое число: 25 Слишком мало! Следующая попытка: 50 Слишком много! Следующая попытка: 42 Верно! Хотите сыграть снова? (да/нет) нет Спасибо за игру!
Никогда не забуду, как доволен я был, когда моя семья играла в написанную мной игру и все работало точно так, как я задумал. Мои ранние переживания имели далеко идущие последствия. Очень приятно построить нечто, предназначенное для конкретной цели; нечто, успешно решающее свою задачу. Программы, которые я пишу сейчас, намного серьезнее моих детских попыток, но чувство удовлетворения, которое я ощущаю от вида работающей программы, остается практически тем же.
Для кого написана эта книга? Цель этой книги — как можно быстрее ввести читателя в курс дела, чтобы тот начал писать на Python работоспособные программы (игры, визуализации данных и веб-приложения), и одновременно заложить основу в области программирования, которая пригодится ему на протяжении всей жизни. Книга написана для людей любого возраста, которые прежде никогда не программировали на Python или вообще никогда не программировали. Если вы хотите быстро изучить азы программирования, чтобы сосредоточиться на интересных проектах, а также проверить свое понимание новых концепций на содержательных задачах — эта книга для вас. Книга также прекрасно подходит для преподавателей, желающих предложить вводный курс программирования, основанный на проектах.
Чему эта книга вас научит? Цель книги — сделать вас хорошим программистом вообще и хорошим программистом Python в частности. Процесс обучения будет эффективным, и вы приобретете много полезных навыков, так как я представлю основательное введение в общие концепции программирования. После того как вы перевернете последнюю страницу, вы будете готовы к знакомству с более серьезными возможностями Python, а изучение вашего следующего языка программирования тоже упростится. В первой части книги будут представлены базовые концепции программирования, которые необходимо знать для написания программ Python. Эти концепции ничем не отличаются от тех, которые рассматриваются в начале изучения почти любого языка программирования. Вы познакомитесь с разными видами данных и возможностями хранения данных в списках и словарях. Вы научитесь создавать коллекции данных и эффективно работать с этими коллекциями. В частности, циклы while и if позволяют выполнять определенные фрагменты кода, если некоторое условие истинно, и выполнять другие фрагменты в противном случае — эти конструкции очень сильно помогают при автоматизации процессов. Вы научитесь получать входные данные от пользователя, чтобы ваши программы стали интерактивными, и выполнять их до тех пор, пока пользователь остается активным. Также вы узнаете, как написать функции для многократного выполнения некоторых частей ваших программ, чтобы вы один раз программировали некоторое действие, а потом могли использовать его столько раз, сколько потребуется. Затем эта концепция будет распространена на более сложное поведение с классами, что позволит даже относительно простым программам реагировать на множество разнообразных ситуаций. Вы научитесь писать программы, корректно обрабатывающие многие типичные ошибки. После знакомства с базовыми концепциями мы напишем несколько коротких программ для решения конкретных задач. Наконец, вы сделаете первые шаги на пути к программированию среднего уровня: вы научитесь писать тесты для своего кода, чтобы вы могли продолжать разработку программ, не беспокоясь о возможном внесении ошибок. Вся информация части I подготовит вас к более сложным и масштабным проектам. В части II знания, полученные в части I, будут применены для построения трех проектов. Вы можете взяться за любые из этих проектов в том порядке, который лучше подходит для вас. В первом проекте (главы 12–14) будет создана игра-«стрелялка» в стиле классического хита Space Invaders, состоящая из многих уровней с нарастающей сложностью. После завершения этого проекта вы будете знать многое из того, что необходимо знать для разработки собственных 2D-игр. Второй проект (главы 15–17) познакомит вас с визуализацией данных. Чтобы разобраться в огромных объемах доступной информации, специалисты по анализу данных применяют различные средства визуализации. Вы будете работать с наборами данных, генерируемыми в программах; наборами данных, загруженными из сетевых источников; и наборами данных, которые загружаются вашей программой автоматически. После завершения этого проекта вы сможете писать программы, обрабатывающие большие наборы данных и строящие визуальные представления сохраненной информации.
В третьем проекте (главы 18–20) будет построено небольшое веб-приложение Learning Log. Этот проект позволяет вести журнал новых идей и концепций, которые вы узнали в ходе изучения конкретной темы. Пользователь приложения сможет вести разные журналы по разным темам, создавать учетные записи и начинать новые журналы. Вы также узнаете, как развернуть свой проект в Интернете, чтобы любой желающий мог работать с ним откуда угодно.
Почему именно Python? Каждый год я задумываюсь над тем, продолжать ли мне работать на Python или же перейти на другой язык — вероятно, более новый в мире программирования. И все же я продолжаю работать на Python по многим причинам. Язык Python невероятно эффективен: ваши программы делают больше, чем многие другие языки, в меньшем объеме кода. Синтаксис Python также позволяет писать «чистый» код. Ваш код будет легко читаться, у вас будет меньше проблем с отладкой и расширением программ по сравнению с другими языками. Python используется для разных целей: для создания игр, построения вебприложений, решений бизнес-задач и разработки внутренних инструментов для всевозможных интересных проектов. Python также широко применяется в научной области для теоретических исследований и решения прикладных задач. Впрочем, одной из самых важных причин для использования Python для меня остается сообщество Python, состоящее из невероятно разных и благожелательных людей. Сообщество играет исключительно важную роль в программировании, потому что программирование не является сугубо индивидуальным делом. Многим из нас, даже самым опытным программистам, приходится обращаться за советом к коллегам, которые уже решали похожие задачи. Существование дружного, доброжелательного сообщества помогает решать задачи, и сообщество Python готово прийти на помощь людям, у которых Python является первым языком программирования. Python — замечательный язык, давайте же браться за дело!
От издательства Ваши замечания, предложения, вопросы отправляйте по адресу электронной почты comp@piter .com (издательство «Питер», компьютерная редакция). Мы будем рады узнать ваше мнение! На веб-сайте издательства http://www .piter .com вы найдете подробную информацию о наших книгах.
Ч а с т ь I . Основы В части I этой книги представлены базовые концепции, необходимые для написания программ на языке Python . Многие из этих концепций встречаются во всех языках программирования, поэтому они пригодятся вам на протяжении всей карьеры программиста . В главе 1 вы установите Python на свой компьютер и запустите свою первую программу, которая выводит на экран сообщение Hello world! . В главе 2 вы научитесь хранить информацию в переменных, работать с текстовыми и числовыми данными . В главах 3 и 4 вы познакомитесь со списками . Списки позволяют хранить любой объем информации в одной переменной, что повышает эффективность работы с данными . Вы сможете работать с сотнями, тысячами и даже миллионами значений всего в нескольких строках кода . В главе 5 будут представлены команды if . С их помощью вы сможете написать код, который делает что-то одно, если некоторое условие истинно, и что-то другое, если условие не выполняется . Глава 6 показывает, как использовать словари Python, связывающие разные виды информации . Словари, как и списки, могут содержать столько информации, сколько вы захотите в них поместить . В главе 7 вы научитесь получать данные от пользователей, чтобы ваши программы стали интерактивными . Также в этой главе описаны циклы while, многократно выполняющие блоки кода, пока некоторое условие остается истинным . В главе 8 вы займетесь написанием функций — именованных блоков кода, которые решают конкретную задачу и запускаются тогда, когда потребуется . В главе 9 представлены классы, предназначенные для моделирования реальных объектов: собак, кошек, людей, машин, ракет и т . д . С их помощью вы сможете представить в своем коде любые сущности, реальные или абстрактные . Глава 10 научит вас работать с файлами и обрабатывать ошибки, чтобы ваши программы не завершались аварийно . Вы узнаете, как сохранить данные перед закрытием программы и снова загрузить их при запуске программы . В этой главе рассматриваются исключения Python; с их помощью вы сможете предвидеть возможные ошибки и организовать их корректную обработку в программах . В главе 11 вы научитесь писать тесты для кода . Тесты проверяют, что ваша программа работает так, как предполагается . В результате вы сможете расширять свои программы, не беспокоясь о возможном внесении новых ошибок . Тестирование — один из первых навыков, отличающих новичка от программиста среднего уровня .
В этой главе вы запустите свою первую программу на языке Python, hello_world . py. Сначала вы проверите, установлен ли Python на вашем компьютере, и если нет — установите его. Также будет установлен текстовый редактор для подготовки программ Python. Текстовые редакторы распознают код Python и выделяют синтаксические конструкции во время работы, упрощая понимание структуры кода разработчиком.
Подготовка среды программирования Поддержка Python слегка отличается в разных операционных системах, поэтому вы должны учитывать некоторые аспекты. В этой главе представлены две основные версии Python, используемые в наше время, и описаны основные действия по настройке Python в вашей системе.
Python 2 и Python 3 Сейчас доступны две версии Python: Python 2 и более новая версия Python 3. Каждый язык программирования развивается с появлением новых идей и технологий, и разработчики Python неустанно трудятся над тем, чтобы сделать язык более мощным и гибким. Многие изменения имеют второстепенный характер и малозаметны на первый взгляд, но в отдельных случаях код, написанный на Python 2, некорректно работает в системах с установленной поддержкой Python 3. В книге я буду указывать на существенные различия между Python 2 и Python 3, так что вы сможете следовать приведенным инструкциям независимо от используемой версии. Если в вашей системе установлены обе версии или вы еще не установили Python, используйте Python 3. Если в вашей системе установлена только версия Python 2 и вы предпочитаете с ходу взяться за написание кода, не желая возиться с установкой, начните с Python 2. Но чем скорее вы перейдете на Python 3, тем лучше — все же полезнее использовать самую новую версию.
Выполнение фрагментов кода Python В поставку Python входит интерпретатор, который выполняется в терминальном окне и позволяет опробовать фрагменты кода Python без сохранения и запуска всей программы.
Глава 1 • Начало работы
В этой книге встречаются фрагменты следующего вида: >>> print(«Hello Python interpreter!») Hello Python interpreter!
Жирным шрифтом выделен текст, который вы вводите и выполняете нажатием клавиши Enter. Большинство примеров в книге представляет собой небольшие самостоятельные программы, которые запускаются из редактора, потому что именно так вы будете писать бульшую часть своего кода. Но в некоторых случаях базовые концепции будут проиллюстрированы серией фрагментов в терминальном сеансе Python для более эффективной демонстрации отдельных концепций. Каждый раз, когда в листинге встречаются три угловые скобки , это означает, что перед вами вывод терминального сеанса. Вскоре мы опробуем возможность программирования в интерпретаторе для вашей системы. Hello World! В мире программирования издавна принято начинать освоение нового языка с программы, выводящей на экран сообщение Hello world! — считается, что это приносит удачу. На языке Python программа Hello World состоит всего из одной строки: print(«Hello world!»)
Даже такая простая программа выполняет вполне конкретную функцию. Если она запускается в вашей системе, то и любая программа, которую вы напишете на Python, тоже должна запускаться нормально. О том, как написать эту программу для вашей конкретной системы, мы поговорим чуть ниже.
Python в разных операционных системах Python является кросс-платформенным языком программирования; это означает, что он работает во всех основных операционных системах. Любая программа на языке Python, написанная вами, должна выполняться на любом современном компьютере с установленной поддержкой Python. Впрочем, способы настройки Python для разных операционных систем слегка отличаются. В этом разделе вы узнаете, как подготовить Python к работе и запустить программу Hello World в вашей системе. Сначала вы проверите, установлена ли поддержка Python в вашей системе, и если нет — установите ее. Затем вы установите простой текстовый редактор и сохраните пустой файл Python с именем hello_world . py. Наконец, вы запустите программу Hello World и устраните любые неполадки. Этот процесс будет описан для всех операционных систем, так что в итоге в вашем распоряжении появится простая и удобная среда программирования на Python.
Python в системе Linux Системы семейства Linux ориентированы на программистов, поэтому поддержка Python уже установлена на большинстве компьютеров Linux. Люди, которые
Подготовка среды программирования
занимаются разработкой и сопровождением Linux, ожидают, что в какой-то момент вы займетесь программированием, и всячески способствуют этому. По этой причине для перехода к программированию вам почти ничего не придется устанавливать, а количество необходимых настроек будет минимальным. Проверка версии Python Откройте терминальное окно, запустив приложение Terminal в вашей системе (в Ubuntu нажмите клавиши Ctrl+Alt+T). Чтобы проверить, установлена ли поддержка Python в вашей системе, введите команду python (со строчной буквы p). На экране появится информация о том, какая версия Python у вас установлена, и приглашение >>>, в котором можно вводить команды Python: $ python Python 2.7.6 (default, Mar 22 2014, 22:59:38) [GCC 4.8.2] on linux2 Type «help», «copyright», «credits» or «license» for more information. >>>
Этот вывод сообщает, что Python 2.7.6 в настоящее время является версией Python по умолчанию, установленной на данном компьютере. Нажмите Ctrl+D или введите exit(), чтобы выйти из приглашения Python и вернуться к приглашению терминала. Чтобы проверить наличие Python 3, возможно, вам придется указать эту версию; итак, даже при том, что в качестве версии по умолчанию в выходных данных указан Python 2.7, попробуйте ввести команду python3: $ python3 Python 3.5.0 (default, Sep 17 2015, 13:05:18) [GCC 4.8.4] on linux Type «help», «copyright», «credits» or «license» for more information. >>>
Из выходных данных видно, что в системе также установлена версия Python 3, так что вы сможете использовать любую из этих версий. Каждый раз, когда вы встречаете команду python в этой книге, вводите вместо нее команду python3. В большинстве дистрибутивов Linux поддержка Python уже установлена, но, если по какой-то причине в вашей системе ее нет или ваша система была укомплектована Python 2, а вы хотите установить Python 3, обращайтесь к приложению А. Установка текстового редактора Geany — простой и удобный текстовый редактор; он легко устанавливается, позволяет запускать практически любые программы прямо из редактора (вместо терминала) и использует цветовое выделение синтаксиса, а код выполняется в терминальном окне. В приложении Б приведена информация о других текстовых редакторах, но я рекомендую использовать Geany, если только у вас нет веских причин для работы в другом редакторе. В большинстве систем Linux установка Geany выполняется одной строкой: $ sudo apt-get install geany
Глава 1 • Начало работы
Если команда не работает, обращайтесь к инструкциям по адресу http://geany.org/ Download/ThirdPartyPackages/. Запуск программы Hello World Чтобы запустить свою первую программу, откройте Geany. Нажмите клавишу Super (она также часто называется клавишей Windows) и найдите Geany в вашей системе. Создайте ярлык, перетащив значок на панель задач или рабочий стол. Создайте папку для своих проектов и присвойте ей имя python_work. (В именах файлов и папок лучше использовать буквы нижнего регистра и символы подчеркивания, потому что это соответствует соглашениям об именах Python.) Вернитесь к Geany и сохраните пустой файл Python (FileSave As) с именем hello_world .py в папке python_work. Расширение .py сообщает Geany, что файл содержит программу Python. Оно также подсказывает Geany, как следует запускать программу и как правильно выделить элементы синтаксиса. После того как файл будет сохранен, введите следующую строку: print(«Hello Python world!»)
Если в системе установлено несколько версий Python, проследите за тем, чтобы в Geany была настроена правильная версия. Откройте окно BuildSet Build Commands. В окне приведены команды Compile и Execute, рядом с каждой из которых располагается команда. Geany предполагает, что правильной командой в каждом случае является python, но, если в системе должна использоваться команда python3, настройку необходимо изменить. Если команда python3 работала в терминальном сеансе, измените команды Compile и Execute так, чтобы в Geany использовался интерпретатор Python 3. Команда Compile должна выглядеть так: python3 -m py_compile «%f»
Команда должна быть введена точно в таком виде без малейших изменений. Проследите за правильностью регистра символов и расстановки пробелов. Команда Execute должна выглядеть так: python3 «%f»
И снова тщательно проверьте пробелы и регистр символов. На рис. 1.1 показано, как эти команды должны выглядеть в меню конфигурации Geany. Теперь выполните программу hello_world .py: выберите команду меню BuildExecute, щелкните на кнопке Execute (с шестеренками) или нажмите клавишу F5. На экране появляется терминальное окно со следующим выводом: Hello Python world! ——————(program exited with code: 0) Press return to continue
Если вы не увидели это сообщение, проверьте каждый символ во введенной строке. Может, вы случайно набрали print с прописной буквы? Пропустили одну или обе
Подготовка среды программирования
Рис. 1.1. Настройка Geany для использования Python 3 в Linux
кавычки или круглые скобки? В языках программирования используется предельно конкретный синтаксис, и при малейшем его нарушении произойдет ошибка. Если программа так и не заработала, обращайтесь к разделу «Решение проблем с установкой» на с. 28. Запуск Python в терминальном сеансе Для выполнения фрагментов кода Python можно открыть терминальное окно и ввести команду python или python3, как мы поступили при проверке версии. Сделайте то же самое, но на этот раз введите в терминальном сеансе следующую строку: >>> print(«Hello Python interpreter!») Hello Python interpreter! >>>
Сообщение выводится прямо в текущем терминальном окне. Вспомните, что интерпретатор Python закрывается комбинацией клавиш Ctrl+D или командой exit().
Python в системе OS X В большинстве систем OS X поддержка Python уже установлена. Даже если вы уверены в том, что Python устанавливать не нужно, вам придется установить текстовый редактор и убедиться в том, что он правильно настроен.
Глава 1 • Начало работы
Проверка наличия Python Откройте терминальное окно (команда ApplicationsUtilitiesTerminal). Также можно нажать Command+пробел, ввести terminal и нажать Enter. Чтобы проверить, установлена ли поддержка Python в вашей системе, введите команду python (со строчной буквы p). На экране появится информация о том, какая версия Python у вас установлена, и приглашение >>>, в котором можно вводить команды Python: $ python Python 2.7.5 (default, Mar 9 2014, 22:15:05) [GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin Type «help», «copyright», «credits», or «license» for more information. >>>
Этот вывод сообщает, что Python 2.7.5 в настоящее время является версией Python по умолчанию, установленной на данном компьютере. Нажмите Ctrl+D или введите exit(), чтобы выйти из приглашения Python и вернуться к приглашению терминала. Чтобы проверить наличие Python 3, попробуйте ввести команду python3. На экране может появиться сообщение об ошибке, но, если из вывода следует, что версия Python 3 в вашей системе установлена, вы сможете использовать ее без необходимости установки. Если команда python3 работает в вашей системе, каждый раз, когда вы встречаете команду python в этой книге, вводите вместо нее команду python3. Если по какой-то причине в вашей системе нет Python или ваша система была укомплектована Python 2, а вы хотите установить Python 3, обращайтесь к приложению А. Запуск Python в терминальном сеансе Для выполнения фрагментов кода Python можно открыть терминальное окно и ввести команду python или python3, как мы поступили при проверке версии. Сделайте то же самое, но на этот раз введите в терминальном сеансе следующую строку: >>> print(«Hello Python interpreter!») Hello Python interpreter! >>>
Сообщение выводится прямо в текущем терминальном окне. Вспомните, что интерпретатор Python закрывается комбинацией клавиш Ctrl+D или командой exit(). Установка текстового редактора Sublime Text — простой и удобный текстовый редактор; он легко устанавливается в OS X, позволяет запускать практически любые программы прямо из редактора (вместо терминала) и использует цветовое выделение синтаксиса, а код выполняется в терминальном окне, встроенном в окно Sublime Text. В приложении Б приведена информация о других текстовых редакторах, но я рекомендую использовать Sublime Text, если только у вас нет веских причин для работы в другом редакторе.
Подготовка среды программирования
Программу установки Sublime Text можно загрузить по адресу http://sublimetext. com/3. Щелкните на ссылке загрузки и найдите программу установки для OS X. Политика лицензирования Sublime Text более чем либеральна: вы можете бесплатно пользоваться редактором сколь угодно долго, но автор требует приобрести лицензию, если программа вам понравилась и вы собираетесь использовать ее в будущем. После того как программа установки будет загружена, откройте ее и перетащите значок Sublime Text в папку Applications. Настройка Sublime Text для Python 3 Если для запуска терминального сеанса Python вместо python используется другая команда, вам придется настроить Sublime Text, чтобы программа знала, где найти правильную версию Python в вашей системе. Чтобы узнать полный путь к интерпретатору Python, введите следующую команду: $ type -a python3 python3 is /usr/local/bin/python3
Теперь откройте Sublime Text и выберите команду ToolsBuild SystemNew Build System. Команда открывает новый конфигурационный файл. Удалите его текущее содержимое и введите следующий код:
«cmd»: [«/usr/local/bin/python3», «-u», «$file»],
Этот код приказывает Sublime Text использовать команду python3 вашей системы для запуска текущего открытого файла. Проследите за тем, чтобы в коде использовался путь, полученный при выполнении команды type -a python3 на предыдущем шаге. Сохраните файл с именем Python3 .sublime-build в каталоге по умолчанию, который Sublime Text открывает при выполнении команды Save. Запуск программы Hello World Чтобы запустить свою первую программу, запустите Sublime Text — откройте папку Applications и сделайте двойной щелчок на значке Sublime Text. Также можно нажать Command+пробел и ввести sublime text в открывшейся панели поиска. Создайте для своих проектов папку с именем python_work. (В именах файлов и папок лучше использовать буквы нижнего регистра и символы подчеркивания, потому что это соответствует соглашениям об именах Python.) Сохраните пустой файл Python (FileSave As) с именем hello_world .py в папке python_work. Расширение .py сообщает Sublime Text, что файл содержит программу Python. Оно также подсказывает Sublime Text, как следует запускать программу и как правильно выделить элементы синтаксиса. После того как файл будет сохранен, введите следующую строку: print(«Hello Python world!»)
Если команда python работает в вашей системе, программу можно запустить командой меню ToolsBuild или комбинацией клавиш Ctrl+B. Если вы настроили
Глава 1 • Начало работы
Sublime Text на использование другой команды вместо python, выберите команду меню ToolsBuild System, а затем Python 3. Тем самым вы назначаете Python 3 версией Python по умолчанию, и в дальнейшем программы можно будет запускать командой ToolsBuild или комбинацией клавиш Command+B. Терминальное окно должно отображаться в нижней части окна Sublime Text со следующим текстом: Hello Python world! [Finished in 0.1s]
Если вы не увидели это сообщение, проверьте каждый символ во введенной строке. Может, вы случайно набрали print с прописной буквы? Пропустили одну или обе кавычки или круглые скобки? В языках программирования используется предельно конкретный синтаксис, и при малейшем его нарушении произойдет ошибка. Если программа так и не заработала, обращайтесь к разделу «Решение проблем с установкой» на с. 28.
Python в системе Windows Windows далеко не всегда включает поддержку Python. Скорее всего, вам придется загрузить и установить Python, а затем загрузить и установить текстовый редактор. Установка Python Для начала проверьте, установлена ли поддержка Python в вашей системе. Откройте окно командной строки: введите command в меню Пуск или щелкните на рабочем столе с нажатой клавишей Shift и выберите команду Open command window here. Введите в окне командной строки команду python в нижнем регистре.
Рис. 1.2. Не забудьте установить флажок Add Python to PATH
Подготовка среды программирования
Если на экране появится приглашение >>>, значит, в системе установлена поддержка Python. Впрочем, скорее всего вместо приглашения появится сообщение об ошибке, в котором говорится, что команда python не опознана системой. В таком случае загрузите программу установки Python для Windows. Откройте страницу http://python.org/downloads/. Вы увидите на ней две кнопки: для загрузки Python 3 и для загрузки Python 2. Щелкните на кнопке Python 3, которая запускает автоматическую загрузку правильного установочного пакета для вашей системы. После того как загрузка файла будет завершена, запустите программу установки. Не забудьте установить флажок Add Python to PATH — это упростит правильную настройку системы. На рис. 1.2 изображено окно мастера установки с активным флажком. Запуск терминального сеанса Настроить текстовый редактор будет несложно, если вы сначала подготовите систему к запуску Python в терминальном сеансе. Откройте окно командной строки и введите команду python в нижнем регистре. Если на экране появится приглашение Python (>>>), значит, система Windows обнаружила установленную версию Python: C:\> python Python 3.5.0 (v3.5.0:374f501f4567, Sep 13 2015, 22:15:05) [MSC v.1900 32 bit (Intel)] on win32 Type «help», «copyright», «credits» or «license» for more information. >>>
Если команда сработала, переходите к следующему разделу «Запуск Python в терминальном сеансе». Однако вывод может выглядеть и так: C:\> python ‘python’ is not recognized as an internal or external command, operable program or batch file.
В этом случае необходимо сообщить Windows, как найти свежеустановленную версию Python. Команда python в вашей системе обычно хранится на диске C; запустите Проводник Windows и откройте диск C. Найдите папку, имя которой начинается с Python, откройте ее и найдите файл python (в нижнем регистре). Например, на моем компьютере существует папка Python35, в которой находится файл с именем python, поэтому путь к команде python в вашей системе имеет вид C:\Python35\python. Если найти файл не удалось, введите строку python в поле поиска в Проводнике Windows — система поиска покажет, где именно хранится команда python в вашей системе. Когда вы решите, что знаете путь к команде, проверьте его: введите этот путь в терминальном окне. Откройте окно командной строки и введите только что найденный полный путь: C:\> C:\Python35\python Python 3.5.0 (v3.5.0:374f501f4567, Sep 13 2015, 22:15:05) [MSC
Глава 1 • Начало работы v.1900 32 bit (Intel)] on win32 Type «help», «copyright», «credits» or «license» for more information. >>>
Если команда успешно работает, то вы знаете, как запустить Python в вашей системе. Запуск Python в терминальном сеансе Введите в терминальном сеансе следующую строку и убедитесь в том, что на экране появился вывод Hello Python world!: >>> print(«Hello Python world!») Hello Python interpreter! >>>
Каждый раз, когда вы захотите выполнить фрагмент кода Python, откройте окно командной строки и запустите терминальный сеанс Python. Чтобы закрыть терминальный сеанс, нажмите Ctrl+Z или введите команду exit(). Установка текстового редактора Geany — простой и удобный текстовый редактор; он легко устанавливается, позволяет запускать практически любые программы прямо из редактора (вместо терминала) и использует цветовое выделение синтаксиса, а код выполняется в терминальном окне. В приложении Б приведена информация о других текстовых редакторах, но я рекомендую использовать Geany, если только у вас нет веских причин для работы в другом редакторе. Программу установки Geany для Windows можно загрузить по адресу http://geany.org/. Щелкните в строке Releases меню Download и найдите пакет geany-1 .25_setup .exe (или что-нибудь в этом роде). Запустите программу и подтвердите все значения по умолчанию. Чтобы запустить свою первую программу, откройте Geany: нажмите клавишу Windows и найдите Geany в вашей системе. Создайте ярлык, перетащив значок на панель задач или рабочий стол. Создайте папку для своих проектов и присвойте ей имя python_work. (В именах файлов и папок лучше использовать буквы нижнего регистра и символы подчеркивания, потому что это соответствует соглашениям об именах Python.) Вернитесь к Geany и сохраните пустой файл Python (FileSave As) с именем hello_world .py в папке python_work. Расширение .py сообщает Geany, что файл содержит программу Python. Оно также подсказывает Geany, как следует запускать программу и как правильно выделить элементы синтаксиса. После того как файл будет сохранен, введите следующую строку: print(«Hello Python world!»)
Если команда python успешно сработала в вашей системе, то дополнительная настройка Geany не нужна; пропустите следующий раздел и переходите к разделу «Запуск программы Hello World» на с. 28. Если для запуска интерпретатора Python
Подготовка среды программирования
пришлось вводить полный путь вида C:\Python35\python, выполните инструкции по настройке Geany для вашей системы, приведенные в следующем разделе. Настройка Geany Чтобы настроить Geany для работы с Python, откройте окно BuildSet Build Commands. В окне приведены команды Compile и Execute, рядом с каждой из которых располагается команда. Команды Compile и Execute начинаются с команды python, записанной символами нижнего регистра, но Geany не знает, где в вашей системе находится исполняемый файл python. К команде нужно добавить путь, который вы ввели в окне командной строки. Добавьте в начало команд Compile и Execute диск и путь к папке, в которой находится файл. Команда Compile должна выглядеть примерно так: C:\Python35\python -m py_compile «%f»
Возможно, в вашей системе путь будет выглядеть немного иначе; проследите за правильностью регистра символов и расстановки пробелов.
Рис. 1.3. Настройка Geany для использования Python 3 в Windows
Команда Execute должна выглядеть примерно так: C:\Python35\python «%f»
Глава 1 • Начало работы
И снова внимательно проверьте пробелы и регистр символов. На рис. 1.3 показано, как эти команды должны выглядеть в меню конфигурации Geany. Завершив настройку команд, нажмите кнопку OK. Запуск программы Hello World Все должно быть готово для успешного выполнения программы. Запустите программу hello_world .py: выберите команду меню BuildExecute, щелкните на кнопке Execute (с шестеренками) или нажмите клавишу F5. На экране появляется терминальное окно со следующим выводом: Hello Python world! ——————(program exited with code: 0) Press return to continue
Если вы не увидели это сообщение, проверьте каждый символ во введенной строке. Может, вы случайно набрали print с прописной буквы? Пропустили одну или обе кавычки или круглые скобки? В языках программирования используется предельно конкретный синтаксис, и при малейшем его нарушении произойдет ошибка. Если программа так и не заработала, возможно, следующий раздел поможет вам в этом.
Решение проблем с установкой Хочется надеяться, что вы успешно настроили среду разработки на своем компьютере. Но если вам так и не удалось запустить программу hello_world .py, возможно, вам помогут следующие полезные советы. Если программа содержит серьезную ошибку, Python выводит данные трассировки. Python анализирует содержимое файла и пытается составить отчет о проблеме. Возможно, трассировка подскажет, что именно мешает выполнению программы. Отойдите от компьютера, отдохните и попробуйте снова. Помните, что синтаксис в программировании очень важен: даже пропущенное двоеточие, неверно расположенная кавычка или непарная скобка могут помешать нормальной работе программы. Перечитайте соответствующие части главы, еще раз проанализируйте, что было сделано, и попробуйте найти ошибку. Начните заново. Вероятно, ничего переустанавливать не придется, но хотя бы попробуйте удалить файл hello_world .py и создать его «с нуля». Попросите кого-нибудь повторить действия, описанные в этой главе, на вашем (или на другом) компьютере. Внимательно понаблюдайте за происходящим. Возможно, вы упустили какую-нибудь мелочь, которую заметят другие. Найдите специалиста, хорошо знающего Python, и попросите его помочь. Вполне может оказаться, что такой специалист есть среди ваших знакомых. Инструкции по настройке среды программирования, приведенные в этой главе, также доступны по адресу https://www.nostarch.com/pythoncrashcourse/. Возможно, сетевая версия будет для вас более удобной.
Запуск программ Python в терминале
Обратитесь за помощью в Интернет. В приложении В перечислены некоторые
ресурсы (форумы, чаты и т. д.), где вы сможете проконсультироваться у людей, уже сталкивавшихся с вашей проблемой. Не стесняйтесь обращаться к опытным программистам. Любой программист в какой-то момент своей жизни заходил в тупик; многие программисты охотно помогут вам правильно настроить вашу систему. Если вы сможете четко объяснить, что вы хотите сделать, что уже пытались и какие результаты получили, скорее всего, кто-нибудь вам поможет. Как упоминалось во введении, сообщество Python доброжелательно относится к новичкам. Python должен нормально работать на любом современном компьютере, и если у вас все же возникли проблемы — обращайтесь за помощью. На первых порах проблемы могут быть весьма неприятными, но с ними стоит разобраться. Когда программа hello_world .py заработает, вы сможете приступить к изучению Python, а ваша работа станет намного более интересной и принесет больше удовольствия.
Запуск программ Python в терминале Большинство программ, написанных вами в текстовом редакторе, будут запускаться прямо из редактора. Тем не менее иногда бывает полезно запускать программы из терминала — например, если вы хотите просто выполнить готовую программу, не открывая ее для редактирования. Это можно сделать в любой системе с установленной поддержкой Python; необходимо лишь знать путь к каталогу, в котором хранится файл программы. Приведенные ниже примеры предполагают, что вы сохранили файл hello_world .py в папке python_work на рабочем столе.
В Linux и OS X Запуск программы Python в терминальном сеансе в системах Linux и OS X осуществляется одинаково. Команда cd (Change Directory) используется для перемещения по файловой системе в терминальном сеансе. Команда ls (LiSt) выводит список всех не-скрытых файлов в текущем каталоге. Откройте новое терминальное окно и введите следующие команды для запуска программы hello_world .py: ~$ cd Desktop/python_work/ ~/Desktop/python_work$ ls hello_world.py ~/Desktop/python_work$ python hello_world.py Hello Python world!
Команда cd используется для перехода к папке python_work , находящейся в папке Desktop . Затем команда ls проверяет, что файл hello_world .py действительно находится в этой папке . Далее файл запускается командой python hello_world .py . Как видите, все просто. По сути вы просто используете команду python (или python3) для запуска программ Python.
Глава 1 • Начало работы
В Windows Команда cd (Change Directory) используется для перемещения по файловой системе в окне командной строки. Команда dir (DIRectory) выводит список всех файлов в текущем каталоге. Откройте новое терминальное окно и введите следующие команды для запуска программы hello_world .py: C:\> cd Desktop\python_work C:\Desktop\python_work> dir hello_world.py C:\Desktop\python_work> python hello_world.py Hello Python world!
Команда cd используется для перехода к папке python_work, находящейся в папке Desktop . Затем команда dir проверяет, что файл hello_world .py действительно находится в этой папке . Далее файл запускается командой python hello_world .py . Если вы еще не настроили свою систему для использования простой команды python, возможно, вам придется использовать более длинную версию этой команды: C:\$ cd Desktop\python_work C:\Desktop\python_work$ dir hello_world.py C:\Desktop\python_work$ C:\Python35\python hello_world.py Hello Python world!
В основном ваши программы будут нормально запускаться прямо из редактора. Со временем ваша работа станет более сложной, и может оказаться, что какие-то программы придется запускать в режиме терминала. УПРАЖНЕНИЯ Упражнения этой главы в основном направлены на самостоятельный поиск информации . Начиная с главы 2, упражнения будут ориентированы на решение задач по изложенному материалу . 1-1 . Python .org: изучите домашнюю страницу Python (http://python .org/) и найдите темы, которые вас заинтересуют . Со временем вы лучше узнаете Python, и другие разделы этого сайта покажутся вам более полезными . 1-2 . Опечатки в Hello World: откройте только что созданный файл hello_world .py . Сделайте где-нибудь намеренную опечатку и снова запустите программу . Удастся ли вам сделать опечатку, которая приводит к ошибке? Поймете ли вы смысл сообщения об ошибке? Удастся ли вам сделать опечатку, которая не приводит к ошибке? Как вы думаете, почему на этот раз выполнение обходится без ошибки? 1-3 . Бесконечное мастерство: если бы вы были программистом с неограниченными возможностями, за какой проект вы бы взялись? Вы сейчас учитесь программировать . Если у вас имеется ясное представление о конечной цели, вы сможете немедленно применить свои новые навыки на практике; попробуйте набросать общие описания тех программ, над которыми вам хотелось бы поработать . Заведите «блокнот идей», к которому вы сможете обращаться каждый раз, когда вы собираетесь начать новый проект . Выделите пару минут и составьте описания трех программ, которые вам хотелось бы создать .
Итоги В этой главе вы познакомились с языком Python и установили поддержку Python в своей системе, если это было необходимо. Также вы установили текстовый редактор, упрощающий работу над кодом Python. Вы научились выполнять фрагменты кода Python в терминальном сеансе и запустили свою первую настоящую программу hello_world .py. Скорее всего, попутно вы кое-что узнали о поиске и исправлении ошибок. В следующей главе рассматриваются структуры данных, с которыми вы будете работать в программах Python. Кроме того, вы научитесь пользоваться переменными Python.
Переменные и простые типы данных
В этой главе представлены разные виды данных, с которыми вы будете работать в своих программах Python. Вы также научитесь хранить данные в переменных и использовать эти переменные в программах.
Что происходит при запуске hello_world .py Давайте повнимательнее разберемся с тем, что же делает Python при запуске hello_world .py. Оказывается, даже для такой простой программы Python проделывает достаточно серьезную работу: hello_world.py print(«Hello Python world!»)
При выполнении этого кода выводится следующий текст: Hello Python world!
Суффикс .py в имени файла hello_world .py указывает, что файл является программой Python. Редактор запускает файл в интерпретаторе Python, который читает программу и определяет, что означает каждое слово в программе. Например, когда интерпретатор обнаруживает слово print, он выводит на экран текст, заключенный в скобки. Во время написания программы редактор выделяет цветом разные части программы. Например, он понимает, что print является именем функции, и выводит это слово синим шрифтом. С другой стороны, “Hello Python world!” не является кодом Python, поэтому этот текст выделяется оранжевым цветом. Этот механизм, называемый цветовым выделением синтаксиса, очень вам пригодится, когда вы возьметесь за самостоятельное программирование.
Переменные Попробуем использовать переменную в программе hello_world .py. Добавьте новую строку в начало файла и измените вторую строку: message = «Hello Python world!» print(message)
Запустите программу и посмотрите, что получится. Программа выводит уже знакомый результат: Hello Python world!
В программу добавилась переменная с именем message . В каждой переменной хранится значение, то есть данные, связанные с переменной. В данном случае значением является текст “Hello Python world!”. Добавление переменной немного усложняет задачу интерпретатора Python. Во время обработки первой строки он связывает текст “Hello Python world!” с переменной message. А когда интерпретатор доберется до второй строки, он выводит на экран значение, связанное с именем message. Давайте немного расширим эту программу hello_world .py, чтобы она выводила второе сообщение. Добавьте в hello_world .py пустую строку, а после нее еще две строки кода: message = «Hello Python world!» print(message) message = «Hello Python Crash Course world!» print(message)
Теперь при выполнении hello_world .py на экране должны появляться две строки: Hello Python world! Hello Python Crash Course world!
Вы можете в любой момент изменить значение переменной в своей программе; Python всегда отслеживает его текущее состояние.
Выбор имен и использование переменных При работе с переменными в языке Python необходимо соблюдать некоторые правила и рекомендации. Нарушение правил приведет к ошибке; рекомендации всего лишь помогают писать более понятный и удобочитаемый код. Работая с переменными, помните о следующем. Имена переменных могут состоять только из букв, цифр и символов подчеркивания. Они могут начинаться с буквы или символа подчеркивания, но не с цифры. Например, переменной можно присвоить имя message_1, но не 1_message. Пробелы в именах переменных запрещены, а для разделения слов в именах переменных используются символы подчеркивания. Например, имя greeting_ message допустимо, а имя greeting message вызовет ошибку. Не используйте имена функций и ключевые слова Python в качестве имен переменных; иначе говоря, не используйте слова, которые зарезервированы в Python для конкретной цели, например слово print (см. раздел «Ключевые слова и встроенные функции Python», с. ). Имена переменных должны быть короткими, но содержательными. Например, имя name лучше n, имя student_name лучше s_n, а имя name_length лучше length_of_persons_name.
Глава 2 • Переменные и простые типы данных
Будьте внимательны при использовании строчной буквы l и прописной буквы O,
потому что они похожи на цифры 1 и 0. Вероятно, вы не сразу научитесь создавать хорошие имена переменных, особенно когда ваши программы станут более сложными и интересными. Но когда вы начнете писать свои программы и читать код, написанный другими разработчиками, ваши имена переменных станут более содержательными. ПРИМЕЧАНИЕ Пока ограничьтесь именами переменных, записанными в нижнем регистре . Использование символов верхнего регистра не приведет к ошибке, и все же пока лучше обойтись без них .
Предотвращение ошибок в именах при использовании переменных Каждый программист совершает ошибки, а большинство программистов ошибается ежедневно. И хотя даже опытный программист не застрахован от ошибок, он знает, как эффективно реагировать на них. Рассмотрим типичную ошибку, которую вы довольно часто будете совершать на первых порах, и выясним, как ее исправить. Для начала напишем код с намеренно внесенной ошибкой. Введите следующий фрагмент (неправильно написанное слово mesage выделено жирным шрифтом): message = «Hello Python Crash Course reader!» print(mesage)
Когда в программе происходит ошибка, интерпретатор Python всеми силами старается помочь вам в поиске причины. Если программа не выполняется нормально, интерпретатор предоставляет данные трассировки — информацию о том, в каком месте кода находился интерпретатор при возникновении проблем. Ниже приведен пример трассировки, которую выдает Python после случайной опечатки в имени переменной: Traceback (most recent call last): File «hello_world.py», line 2, in print(mesage) NameError: name ‘mesage’ is not defined
Строка сообщает, что ошибка произошла в строке 2 файла hello_world .py. Интерпретатор выводит номер строки, чтобы вам было проще найти ошибку , и сообщает тип обнаруженной ошибки . В данном случае была обнаружена ошибка в имени: переменная с указанным именем (mesage) не определена. Другими словами, Python не распознает имя переменной. Обычно такие ошибки возникают в том случае, если вы забыли присвоить значение переменной перед ее использованием или ошиблись при вводе имени. Конечно, в данном примере в имени переменной во второй строке пропущена буква s. Интерпретатор Python не проверяет код на наличие опечаток, но следит за тем,
чтобы имена переменных записывались одинаково. Например, вот что происходит, если имя message будет неправильно записано еще в одном месте кода: mesage = «Hello Python Crash Course reader!» print(mesage)
На этот раз программа выполняется успешно! Hello Python Crash Course reader!
Компьютеры не отличаются гибкостью, но орфография их совершенно не волнует. Как следствие, вам не нужно следить за тем, чтобы в именах переменных идеально соблюдались правила орфографии английского языка. Многие ошибки программирования сводятся к простым опечаткам – случайной замене одного символа в одной строке программы. Если вы потратили много времени на поиск одной из таких ошибок, знайте, что вы не одиноки. Многие опытные и талантливые программисты тратят долгие часы на поиск подобных мелких ошибок. Нечто подобное будет часто происходить в ходе вашей работы – поэтому просто посмейтесь и идите дальше. ПРИМЕЧАНИЕ Как лучше всего освоить новые концепции программирования? Попытайтесь использовать их в своей программе . Если в ходе работы над упражнением вы зайдете в тупик, попробуйте на какоето время заняться чем-нибудь другим . Если это не поможет, перечитайте соответствующую часть этой главы . Если и это не помогло, обращайтесь к рекомендациям из приложения В . УПРАЖНЕНИЯ Напишите отдельную программу для выполнения каждого из следующих упражнений . Сохраните каждую программу в файле, имя которого подчиняется стандартным правилам Python по использованию строчных букв и символов подчеркивания – например, simple_ message .py и simple_messages .py . 2-1 . Простое сообщение: сохраните текстовое сообщение в переменной и выведите его на экран . 2-2 . Простые сообщения: сохраните сообщение в переменной и выведите это сообщение . Затем замените значение переменной другим сообщением и выведите новое сообщение .
Строки Так как многие программы определяют и собирают некие данные, а затем делают с ними что-то полезное, желательно выделить основные разновидности данных. Начнем со строковых данных. На первый взгляд строки достаточно просты, но с ними можно работать многими разными способами. Строка представляет собой простую последовательность символов. Любая последовательность символов, заключенная в кавычки, в Python считается строкой; при этом строки могут быть заключены как в одиночные, так и в двойные кавычки: «This is a string.» ‘This is also a string.’
Глава 2 • Переменные и простые типы данных
Это правило позволяет использовать внутренние кавычки и апострофы в строках: ‘I told my friend, «Python is my favorite language!»‘ «The language ‘Python’ is named after Monty Python, not the snake.» «One of Python’s strengths is its diverse and supportive community.»
Рассмотрим некоторые типичные операции со строками.
Изменение регистра символов в строках Одна из простейших операций, выполняемых со строками, – изменение регистра символов. Взгляните на следующий фрагмент кода и попробуйте определить, что в нем происходит: name.py name = «ada lovelace» print(name.title())
Сохраните файл с именем name .py и запустите его. Вывод программы должен выглядеть так: Ada Lovelace
В этом примере в переменной name сохраняется строка, состоящая из букв нижнего регистра «ada lovelace». За именем переменной в команде print() следует вызов метода title(). Метод представляет собой действие, которое Python выполняет с данными. Точка (.) после name в конструкции name.title() приказывает Python применить метод title() к переменной name. За именем метода всегда следует пара круглых скобок, потому что методам для выполнения их работы часто требуется дополнительная информация. Эта информация указывается в скобках. Функции title() дополнительная информация не нужна, поэтому в круглых скобках ничего нет. Метод title() преобразует первый символ каждого слова в строке к верхнему регистру, тогда как все остальные символы выводятся в нижнем регистре. Например, данная возможность может быть полезна, если в вашей программе входные значения Ada, ADA и ada должны рассматриваться как одно и то же имя, и все они должны отображаться в виде Ada. Для работы с регистром также существуют другие полезные методы. Например, все символы строки можно преобразовать к верхнему или нижнему регистру: name = «Ada Lovelace» print(name.upper()) print(name.lower())
Программа выводит следующий результат: ADA LOVELACE ada lovelace
Метод lower() особенно полезен для хранения данных. Нередко программист не может рассчитывать на то, что пользователи введут все данные с точным со-
блюдением регистра, поэтому строки перед сохранением преобразуются к нижнему регистру. Затем, когда потребуется вывести информацию, используется регистр, наиболее подходящий для каждой строки.
Конкатенация Также часто возникает необходимость в объединении строк. Представьте, что имя и фамилия хранятся в разных переменных и вы хотите объединить их для вывода полного имени: first_name = «ada» last_name = «lovelace» full_name = first_name + » » + last_name print(full_name)
Для объединения строк в Python используется знак «плюс» ( + ). В приведенном примере полное имя строится объединением first_name , пробел и last_name : ada lovelace
Такой способ объединения строк называется конкатенацией. Вы можете использовать конкатенацию для построения сложных сообщений с информацией, хранящейся в переменных. Рассмотрим пример: first_name = «ada» last_name = «lovelace» full_name = first_name + » » + last_name print(«Hello, » + full_name.title() + «!»)
Полное имя используется в точке для вывода приветственного сообщения, а метод title() обеспечивает правильное форматирование имени. Этот фрагмент возвращает простое, хорошо отформатированное сообщение: Hello, Ada Lovelace!
Конкатенацией также можно воспользоваться для построения сообщения, которое затем сохраняется в переменной: first_name = «ada» last_name = «lovelace» full_name = first_name + » » + last_name message = «Hello, » + full_name.title() + «!» print(message)
Этот код также выводит сообщение “Hello, Ada Lovelace!”, но сохранение текста сообщения в переменной существенно упрощает завершающую команду печати .
Глава 2 • Переменные и простые типы данных
Табуляции и разрывы строк В программировании термином «пропуск» (whitespace) называются такие непечатаемые символы, как пробелы, табуляции и символы конца строки. Пропуски структурируют текст, чтобы пользователю было удобнее читать его. Для включения в текст позиции табуляции используется комбинация символов \t, как в точке : >>> print(«Python») Python >>> print(«\tPython») Python
Разрывы строк добавляются с помощью комбинации символов \n: >>> print(«Languages:\nPython\nC\nJavaScript») Languages: Python C JavaScript
Табуляции и разрывы строк могут сочетаться в тексте. Скажем, последовательность «\n\t» приказывает Python начать текст с новой строки, в начале которой располагается табуляция. Следующий пример демонстрирует вывод одного сообщения с разбиением на четыре строки: >>> print(«Languages:\n\tPython\n\tC\n\tJavaScript») Languages: Python C JavaScript
Разрывы строк и табуляции часто встречаются в двух следующих главах, когда наши программы начнут выводить относительно длинный текст.
Удаление пропусков Лишние пропуски могут вызвать путаницу в программах. Для программиста строки ‘python’ и ‘python ‘ внешне неотличимы, но для программы это совершенно разные строки. Python видит лишний пробел в ‘python ‘ и считает, что он действительно важен — до тех пор, пока вы не сообщите о противоположном. Обращайте внимание на пропуски, потому что в программах часто приходится сравнивать строки, чтобы проверить на совпадение их содержимое. Типичный пример — проверка имен пользователей при входе на сайт. Лишние пропуски могут создавать путаницу и в более простых ситуациях. К счастью, Python позволяет легко удалить лишние пропуски из данных, введенных пользователем. Python может искать лишние пропуски у левого и правого края строки. Чтобы убедиться в том, что у правого края (в конце) строки нет пропусков, вызовите метод rstrip().
>>> favorite_language = ‘python ‘ >>> favorite_language ‘python ‘ >>> favorite_language.rstrip() ‘python’ >>> favorite_language ‘python ‘
Значение, хранящееся в переменной favorite_language в точке , содержит лишние пропуски в конце строки. Когда вы приказываете Python вывести это значение в терминальном сеансе, вы видите пробел в конце значения . Когда метод rstrip() работает с переменной favorite_language в точке , этот лишний пробел удаляется. Впрочем, удаление лишь временное: если снова запросить значение favorite_language , мы видим, что строка не отличается от исходной, включая лишний пропуск . Чтобы навсегда исключить пропуск из строки, следует записать усеченное значение обратно в переменную: >>> favorite_language = ‘python ‘ >>> favorite_language = favorite_language.rstrip() >>> favorite_language ‘python’
Сначала пропуски удаляются в конце строки, а потом значение записывается в исходную переменную . Операция изменения значения переменной с последующим его сохранением в исходной переменной часто выполняется в программировании. Так значение переменной может изменяться в ходе выполнения программы или в ответ на действия пользователя. Пропуски также можно удалить у левого края (в начале) строки при помощи метода lstrip(), а метод strip() удаляет пропуски с обоих концов: >>> favorite_language = ‘ python ‘ >>> favorite_language.rstrip() ‘ python’ >>> favorite_language.lstrip() ‘python ‘ >>> favorite_language.strip() ‘python’
В этом примере исходное значение содержит пропуски в начале и в конце . Затем пропуски удаляются у правого края , у левого края и с обоих концов строки . Поэкспериментируйте с функциями удаления пропусков, это поможет вам освоиться с работой со строками. На практике эти функции чаще всего применяются для «очистки» пользовательского ввода перед его сохранением в программе.
Предотвращение синтаксических ошибок в строках Синтаксические ошибки встречаются в программах более или менее регулярно. Синтаксическая ошибка происходит тогда, когда Python не распознает часть вашей программы как действительный код Python. Например, если заключить апостроф
Глава 2 • Переменные и простые типы данных
в одиночные кавычки, случится ошибка. Это происходит из-за того, что Python интерпретирует все символы от первой одиночной кавычки до апострофа как строку. После этого Python пытается интерпретировать остаток текста строки как код Python, что порождает ошибки. Разберемся, как же правильно использовать одиночные или двойные кавычки. Сохраните следующую программу в файле apostrophe .py и запустите ее: apostrophe.py message = «One of Python’s strengths is its diverse community.» print(message)
Апостроф находится в строке, заключенной в двойные кавычки, так что у интерпретатора Python не возникает проблем с правильной интерпретацией следующей строки: One of Python’s strengths is its diverse community.
Однако при использовании одиночных кавычек Python не сможет определить, где должна заканчиваться строка: message = ‘One of Python’s strengths is its diverse community.’ print(message)
Программа выводит следующий результат: File «apostrophe.py», line 1 message = ‘One of Python’s strengths is its diverse community.’ ^ SyntaxError: invalid syntax
Из выходных данных видно, что ошибка происходит в позиции сразу же после второй одиночной кавычки. Эта синтаксическая ошибка указывает, что интерпретатор не распознает какую-то конструкцию как действительный код Python. Ошибки могут возникать по разным причинам; я буду выделять наиболее распространенные источники по мере того, как они будут встречаться нам. Синтаксические ошибки будут часто досаждать вам, пока вы учитесь писать правильный код Python. Кроме того, ошибки этой категории также являются наиболее расплывчатыми и неконкретными, поэтому их особенно трудно находить и исправлять. Если вы зайдете в тупик из-за особенно коварной ошибки, обращайтесь к рекомендациям в приложении В. ПРИМЕЧАНИЕ Функция цветового выделения синтаксиса ускоряет выявление некоторых синтаксических ошибок прямо во время написания программы . Если вы увидите, что код Python выделяется как обычный текст (или обычный текст выделяется как код Python), скорее всего, в вашем файле где-то пропущена кавычка .
Вывод в Python 2 В Python 2 команда print имеет немного иной синтаксис:
>>> python2.7 >>> print «Hello Python 2.7 world!» Hello Python 2.7 world!
В Python 2 выводимый текст не обязательно заключать в круглые скобки. Формально в Python 3 print является функцией, поэтому круглые скобки обязательны. Некоторые команды print в Python 2 содержат круглые скобки, однако их поведение несколько отличается от того, что вы видите в Python 3. Когда вы смотрите на код, написанный на Python 2, с большой вероятностью в одних командах print будут присутствовать круглые скобки, а в других круглых скобок не будет. УПРАЖНЕНИЯ Сохраните код каждого из следующих упражнений в отдельном файле с именем name_ cases .py . Если у вас возникнут проблемы, сделайте перерыв или обратитесь к рекомендациям в приложении В . 2-3 . Личное сообщение: сохраните имя пользователя в переменной и выведите сообщение, предназначенное для конкретного человека . Сообщение должно быть простым, например: “Hello Eric, would you like to learn some Python today?” 2-4 . Регистр символов в именах: сохраните имя пользователя в переменной и выведите его в нижнем регистре, в верхнем регистре и с капитализацией начальных букв каждого слова . 2-5 . Знаменитая цитата: найдите известное высказывание, которое вам понравилось . Выведите текст цитаты с именем автора . Результат должен выглядеть примерно так (включая кавычки): Albert Einstein once said, «A person who never made a mistake never tried anything new.» 2-6 . Знаменитая цитата 2: повторите упражнение 2-5, но на этот раз сохраните имя автора цитаты в переменной famous_person . Затем составьте сообщение и сохраните его в новой переменной с именем message . Выведите свое сообщение . 2-7 . Удаление пропусков: сохраните имя пользователя в переменной . Добавьте в начале и в конце имени несколько пропусков . Проследите за тем, чтобы каждая служебная последовательность , “\t” и “\n”, встречалась по крайней мере один раз . Выведите имя, чтобы были видны пропуски в начале и конце строки . Затем выведите его снова с использованием каждой из функций удаления пропусков: lstrip(), rstrip() и strip() .
Числа Числа очень часто применяются в программировании для ведения счета в играх, представления данных в визуализациях, хранения информации в веб-приложениях и т. д. В Python числовые данные делятся на несколько категорий по способу их использования. Для начала посмотрим, как Python работает с целыми числами, потому что с ними возникает меньше всего проблем.
Целые числа В Python с целыми числами можно выполнять операции сложения (+), вычитания (-), умножения (*) и деления(/).
Глава 2 • Переменные и простые типы данных >>> 5 >>> 1 >>> 6 >>> 1.5
2 + 3 3 — 2 2 * 3 3 / 2
В терминальном сеансе Python просто возвращает результат операции. Для представления операции возведения в степень в Python используется сдвоенный знак умножения: >>> 3 ** 2 9 >>> 3 ** 3 27 >>> 10 ** 6 1000000
В Python также существует определенный порядок операций, что позволяет использовать несколько операций в одном выражении. Круглые скобки используются для изменения порядка операций, чтобы выражение могло вычисляться в нужном порядке. Пример: >>> 2 + 3*4 14 >>> (2 + 3) * 4 20
Пробелы в этих примерах не влияют на то, как Python вычисляет выражения; они просто помогают быстрее найти приоритетные операции при чтении кода.
Вещественные числа В Python числа, имеющие дробную часть, называются вещественными (или «числами с плавающей точкой»). Обычно разработчик может просто пользоваться дробными значениями, не особенно задумываясь об их поведении. Просто введите нужные числа, а Python скорее всего сделает именно то, что вы от него хотите: >>> 0.2 >>> 0.4 >>> 0.2 >>> 0.4
0.1 + 0.1 0.2 + 0.2 2 * 0.1 2 * 0.2
Однако в некоторых ситуациях вдруг оказывается, что результат содержит неожиданно большое количество разрядов в дробной части: >>> 0.2 + 0.1 0.30000000000000004
>>> 3 * 0.1 0.30000000000000004
Нечто подобное может произойти в любом языке; для беспокойства нет причин. Python пытается подобрать как можно более точное представление результата, что иногда бывает нелегко из-за особенностей внутреннего представления чисел в компьютерах. Пока просто не обращайте внимания на «лишние» разряды; вы узнаете, как поступать в подобных ситуациях, когда эта проблема станет актуальной для вас в проектах части II.
Предотвращение ошибок типов с использованием функции str() Часто значение переменной должно использоваться внутри сообщения. Допустим, вы хотите поздравить пользователя с днем рождения. И вы написали для этого следующий код: birthday.py age = 23 message = «Happy » + age + «rd Birthday!» print(message)
Казалось бы, программа должна вывести простое приветствие: Happy 23rd birthday! Но, если запустить ее, появляется сообщение об ошибке: Traceback (most recent call last): File «birthday.py», line 2, in message = «Happy » + age + «rd Birthday!» TypeError: Can’t convert ‘int’ object to str implicitly
На этот раз произошла ошибка типа. Это означает, что Python не понимает, какую информацию вы используете. В данном примере Python видит, что в точке используется переменная с целочисленным значением (int), но не знает, как следует интерпретировать это значение. Дело в том, что переменная может представлять как число 23, так и пару отдельных символов 2 и 3. При таком использовании целых чисел в строках необходимо явно указать, что целое число должно использоваться как строка из символов. Для этого переменная передается функции str(), преобразующей не-строковые значения к строковому виду: age = 23 message = «Happy » + str(age) + «rd Birthday!» print(message)
Теперь Python понимает, что вы хотите преобразовать числовое значение 23 в строку и вывести символы 2 и 3 в составе поздравления. Ожидаемый результат выводится без всяких ошибок: Happy 23rd Birthday!
В большинстве случаев работа с числами в Python проходит достаточно тривиально. Если вы получаете неожиданные результаты, проверьте, правильно ли Python интерпретирует числовые данные – как числовое значение или как строку.
Глава 2 • Переменные и простые типы данных
Целые числа в Python 2 При делении целых чисел Python 2 возвращает несколько иной результат: >>> python2.7 >>> 3 / 2 1
Вместо 1.5 Python возвращает 1. Результатом деления целых чисел в Python 2 становится целое число с потерей остатка. Обратите внимание: результат не округляется, просто остаток от деления пропадает. Чтобы избежать этого поведения в Python 2, проследите за тем, чтобы хотя бы одно из двух чисел было вещественным. В этом случае результат также будет вещественным: >>> 1 >>> 1.5 >>> 1.5 >>> 1.5
3 / 2 3.0 / 2 3 / 2.0 3.0 / 2.0
Такое поведение при делении часто приводит к недоразумениям, когда люди, привыкшие работать с Python 3, начинают использовать Python 2, или наоборот. Если вы используете или пишете код, в котором смешиваются целые и вещественные числа, будьте внимательны. УПРАЖНЕНИЯ 2-8 . Число 8: напишите операции сложения, вычитания, умножения и деления, результатом которых является число 8 . Не забудьте заключить операции в команды print, чтобы проверить результат . Вы должны написать четыре строки кода, которые выглядят примерно так: print(5 + 3) Результатом должны быть четыре строки, в каждой из которых выводится число 8 . 2-9 . Любимое число: сохраните свое любимое число в переменной . Затем при помощи переменной создайте сообщение для вывода этого числа . Выведите это сообщение .
Комментарии Комментарии чрезвычайно полезны в любом языке программирования. До сих пор ваши программы состояли только из кода Python. По мере роста объема и сложности кода в программы следует добавлять комментарии, описывающие общий подход к решаемой задаче, — своего рода заметки, написанные на понятном языке.
Как создаются комментарии? В языке Python признаком комментария является символ «решетка» (#). Интерпретатор Python игнорирует все символы, следующие в коде после # до конца строки. Пример:
comment.py # Say hello to everyone. print(«Hello Python people!»)
Python игнорирует первую строку и выполняет вторую. Hello Python people!
Какие комментарии следует писать? Комментарии пишутся прежде всего для того, чтобы объяснить, что должен делать ваш код и как он работает. В ходе работы над проектом вы понимаете, как работают все его компоненты. Но, если вернуться к проекту спустя некоторое время, скорее всего, некоторые подробности будут забыты. Конечно, всегда можно изучить код и разобраться в том, как должны работать его части, но хорошие комментарии с доступным изложением общих принципов работы кода сэкономят немало времени. Если вы хотите стать профессиональным программистом или участвовать в совместной работе с другими программистами, научитесь писать осмысленные комментарии. В наши дни почти все программы разрабатываются коллективно в группах — либо группами работников одной компании, либо группами энтузиастов, совместно работающих над проектом с открытым кодом. Опытные программисты ожидают увидеть комментарии в коде, поэтому лучше привыкайте добавлять содержательные комментарии прямо сейчас. Написание простых, лаконичных комментариев – одна из самых полезных привычек, необходимых начинающему программисту. Принимая решение о том, нужно ли писать комментарий или нет, спросите себя, пришлось ли вам перебрать несколько вариантов в поисках разумного решения для некоторой задачи; если ответ будет положительным, напишите комментарий по поводу вашего решения. Удалить лишние комментарии позднее намного проще, чем возвращаться и добавлять комментарии в программу. С этого момента я буду использовать комментарии в примерах для пояснения смысла некоторых частей кода. УПРАЖНЕНИЯ 2-10 . Добавление комментариев: выберите две программы из написанных вами и добавьте в каждую хотя бы один комментарий . Если вы не найдете, что написать в комментариях, потому что программы были слишком просты, добавьте свое имя и текущую дату в начало кода . Затем добавьте одно предложение с описанием того, что делает программа .
Философия Python Долгое время язык программирования Perl был краеугольным камнем интернетпрограммирования. На первых порах функционирование многих интерактивных сайтов было основано на сценариях Perl. В то время сообщество Perl руководствовалось девизом: «Это можно сделать несколькими способами». Какое-то время разработчикам нравился такой подход, потому что гибкость, присущая языку, позволяла решать многие задачи разными способами. Подобный подход был до-
Глава 2 • Переменные и простые типы данных
пустим при работе над собственными проектами, но со временем стало ясно, что чрезмерная гибкость усложняет долгосрочное сопровождение крупных проектов. Было слишком трудно, утомительно и долго разбираться в коде и пытаться понять, что же думал другой разработчик при решении сложной задачи. Опытные программисты Python рекомендуют избегать лишних сложностей и применять простые решения там, где это возможно. Философия сообщества Python выражена в очерке Тима Питерса «The Zen of Python». Чтобы просмотреть этот краткий набор принципов написания хорошего кода Python, достаточно ввести команду import this в интерпретаторе. Я не стану воспроизводить все принципы, но приведу несколько строк, чтобы вы поняли, почему они важны для вас как для начинающего программиста Python. >>> import this The Zen of Python, by Tim Peters
Красивое лучше, чем уродливое.
Программисты Python считают, что код может быть красивым и элегантным. В программировании люди занимаются решением задач. Программисты всегда ценили хорошо спроектированные, эффективные и даже красивые решения. Со временем вы больше узнаете о Python, начнете писать больше кода, и когданибудь ваш коллега посмотрит на экран вашего компьютера и скажет: «Ого, какой красивый код!» Простое лучше, чем сложное. Если у вас есть выбор между простым и сложным решением и оба работают, используйте простое решение. Ваш код будет проще в сопровождении, а у вас и других разработчиков будет меньше проблем с обновлением этого кода в будущем. Сложное лучше, чем запутанное. Реальность создает свои сложности; иногда простое решение задачи невозможно. В таком случае используйте самое простое решение, которое работает. Удобочитаемость имеет значение. Даже если ваш код сложен, он должен нормально читаться. Работая над проектом, требующим написания сложного кода, постарайтесь написать содержательные комментарии для этого кода. Должен существовать один — и желательно только один — очевидный способ сделать это. Если предложить двум программистам Python решить одну и ту же задачу, они должны выработать похожие решения. Это не значит, что в программировании нет места для творчества. Наоборот! Но бульшая часть работы программиста заключается в применении небольших, стандартных решений для простых ситуаций в контексте большого, более творческого проекта. Внутренняя организация ваших программ должна выглядеть логично с точки зрения других программистов Python. Сейчас лучше, чем никогда. Вы можете потратить весь остаток жизни на изучение всех тонкостей Python и программирования в целом, но тогда вы никогда не закончите ни один проект.
Не пытайтесь написать идеальный код; напишите код, который работает, а потом решите, стоит ли доработать его для текущего проекта или же перейти на что-то другое. Когда вы перейдете к следующей главе и займетесь изучением более сложных тем, постарайтесь не забывать об этой философии простоты и ясности. Опытные программисты будут с большим уважением относиться к вашему коду, охотнее делиться своим мнением и сотрудничать с вами в интересных проектах. УПРАЖНЕНИЯ 2-11 . Дзен Python: введите команду import this в терминальном сеансе Python и просмотрите другие принципы .
Итоги В этой главе вы научились работать с переменными. Вы узнали, как использовать содержательные имена переменных и как исправлять ошибки в именах и синтаксические ошибки в случае их возникновения. Вы узнали, что такое строки и как выводить их в нижнем/верхнем регистре и с капитализацией всех слов. Мы рассмотрели способы аккуратного оформления вывода с применением пропусков, а также удаления лишних пропусков из разных частей строки. Вы начали работать с целыми и вещественными числами и узнали о некоторых неожиданностях, встречающихся при работе с числовыми данными. Вы научились писать содержательные комментарии, которые упрощают написание кода для вас и его чтение для других разработчиков. В завершение главы была представлена философия максимальной простоты кода. В главе 3 рассматривается хранение наборов данных в переменных, называемых списками. Вы узнаете, как перебрать содержимое списка и обработать хранящуюся в нем информацию.
В этой и следующей главах вы узнаете, что собой представляют списки и как начать работать с элементами списка. Списки позволяют хранить в одном месте взаимосвязанные данные, сколько бы их ни было — несколько элементов или несколько миллионов элементов. Работа со списками относится к числу самых выдающихся возможностей Python, доступных для начинающего программиста. Операции со списками связывают воедино многие важные концепции в программировании.
Что такое список? Список — это набор элементов, следующих в определенном порядке. Вы можете создать список для хранения букв алфавита, цифр от 0 до 9 или имен всех членов вашей семьи. В список можно поместить любую информацию, причем данные в списке даже не обязаны быть как-то связаны друг с другом. Так как список обычно содержит более одного элемента, рекомендуется присваивать спискам имена во множественном числе: letters, digits, names и т. д. В языке Python список обозначается квадратными скобками ( []), а отдельные элементы списка разделяются запятыми. Простой пример списка с названиями моделей велосипедов: bicycles.py bicycles = [‘trek’, ‘cannondale’, ‘redline’, ‘specialized’] print(bicycles)
Если вы прикажете Python вывести список, то на экране появится перечисление элементов списка в квадратных скобках: [‘trek’, ‘cannondale’, ‘redline’, ‘specialized’]
Конечно, вашим пользователям такое представление не подойдет; разберемся, как обратиться к отдельным элементам в списке.
Обращение к элементам списка Списки представляют собой упорядоченные наборы данных, поэтому для обращения к любому элементу списка следует сообщить Python позицию (индекс) нужного
Индексы начинаются с 0, а не с 1
элемента. Чтобы обратиться к элементу в списке, укажите имя списка, за которым следует индекс элемента в квадратных скобках. Например, название первого велосипеда в списке bicycles выводится следующим образом: bicycles = [‘trek’, ‘cannondale’, ‘redline’, ‘specialized’] print(bicycles[0])
Синтаксис обращения к элементу показан в точке . Когда мы запрашиваем один элемент из списка, Python возвращает только этот элемент без квадратных скобок или кавычек: trek
Именно такой результат должны увидеть пользователи — чистый, аккуратно отформатированный вывод. Также можно использовать строковые методы из главы 2 с любым элементом списка. Например, элемент ‘trek’ можно более аккуратно отформатировать при помощи метода title(): bicycles = [‘trek’, ‘cannondale’, ‘redline’, ‘specialized’] print(bicycles[0].title())
Этот пример выдает такой же результат, как и предыдущий, только название ‘Trek’ выводится с прописной буквы.
Индексы начинаются с 0, а не с 1 Python считает, что первый элемент списка находится в позиции 0, а не в позиции 1. Этот принцип встречается в большинстве языков программирования и объясняется особенностями низкоуровневой реализации операций со списками. Если вы получаете неожиданные результаты, определите, не допустили ли вы простую ошибку «смещения на 1». Второму элементу списка соответствует индекс 1. В этой простой схеме индекс любого элемента вычисляется уменьшением на 1 его позиции в списке. Например, чтобы обратиться к четвертому элементу списка, следует запросить элемент с индексом 3. В следующем примере выводятся названия велосипедов с индексами 1 и 3: bicycles = [‘trek’, ‘cannondale’, ‘redline’, ‘specialized’] print(bicycles[1]) print(bicycles[3])
При этом выводится второй и четвертый элементы списка: cannondale specialized
В Python также существует специальный синтаксис для обращения к последнему элементу списка. Если запросить элемент с индексом –1, Python всегда возвращает последний элемент в списке:
Глава 3 • Списки bicycles = [‘trek’, ‘cannondale’, ‘redline’, ‘specialized’] print(bicycles[-1])
Фрагмент вернет значение ‘specialized’. Этот синтаксис весьма полезен, потому что при работе со списками часто требуется обратиться к последним элементам, не зная точное количество элементов в списке. Синтаксис также распространяется на другие отрицательные значения индексов. Индекс –2 возвращает второй элемент от конца списка, индекс –3 — третий элемент от конца и т. д.
Использование отдельных элементов из списка Отдельные значения из списка используются так же, как и любые другие переменные. Например, вы можете воспользоваться конкатенацией для построения сообщения, содержащего значение из списка. Попробуем извлечь название первого велосипеда из списка и составить сообщение, включающее это значение. bicycles = [‘trek’, ‘cannondale’, ‘redline’, ‘specialized’] message = «My first bicycle was a » + bicycles[0].title() + «.» print(message)
В точке программа строит сообщение, содержащее значение из bicycles[0], и сохраняет его в переменной message. Так создается простое предложение с упоминанием первого велосипеда из списка: My first bicycle was a Trek. УПРАЖНЕНИЯ Попробуйте написать несколько коротких программ, чтобы получить предварительное представление о списках Python . Возможно, для упражнений из каждой главы стоит создать отдельную папку, чтобы избежать путаницы . 3-1 . Имена: сохраните имена нескольких своих друзей в списке с именем names . Выведите имя каждого друга, обратившись к каждому элементу списка (по одному за раз) . 3-2 . Сообщения: начните со списка, использованного в упражнении 3-1, но вместо вывода имени каждого человека выведите сообщение . Основной текст всех сообщений должен быть одинаковым, но каждое сообщение должно включать имя адресата . 3-3 . Собственный список: выберите свой любимый вид транспорта (например, мотоциклы или машины) и создайте список с примерами . Используйте свой список для вывода утверждений об элементах типа: «Я хотел бы купить мотоцикл Honda» .
Изменение, добавление и удаление элементов Обычно вы будете создавать динамические списки; это означает, что во время выполнения программы в созданном вами списке будут добавляться и удаляться элементы. Например, вы можете создать игру, в которой игрок должен стрелять по кораблям космических захватчиков. Исходный набор кораблей сохраняется в списке; каждый раз, когда вы сбиваете корабль, он удаляется из списка. Каждый
Индексы начинаются с 0, а не с 1
раз, когда на экране появляется новый враг, он включается в список. Длина списка кораблей будет уменьшаться и увеличиваться по ходу игры.
Изменение элементов в списке Синтаксис изменения элемента напоминает синтаксис обращения к элементу списка. Чтобы изменить элемент, укажите имя списка и индекс изменяемого элемента в квадратных скобках; далее задайте новое значение, которое должно быть присвоено элементу. Допустим, имеется список мотоциклов, и первым элементом списка хранится строка ‘honda’. Как изменить значение первого элемента? motorcycles.py motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’] print(motorcycles) motorcycles[0] = ‘ducati’ print(motorcycles)
В точке определяется исходный список, в котором первый элемент содержит строку ‘honda’. В точке значение первого элемента заменяется строкой ‘ducati’. Из вывода видно, что первый элемент действительно изменился, а остальные элементы списка сохранили прежние значения: [‘honda’, ‘yamaha’, ‘suzuki’] [‘ducati’, ‘yamaha’, ‘suzuki’]
Изменить можно значение любого элемента в списке, не только первого.
Добавление элементов в список Новые элементы могут добавляться в списки по разным причинам — например, для появления на экране новых космических кораблей, включения новых данных в визуализацию или добавления новых зарегистрированных пользователей на построенный вами сайт. Python предоставляет несколько способов добавления новых данных в существующие списки. Присоединение элементов в конец списка Простейший способ добавления новых элементов в список — присоединение элемента в конец списка. Используя список из предыдущего примера, добавим новый элемент ‘ducati’: motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’] print(motorcycles) motorcycles.append(‘ducati’) print(motorcycles)
Глава 3 • Списки
Метод append() в точке присоединяет строку ‘ducati’ в конец списка, другие элементы в списке при этом остаются неизменными: [‘honda’, ‘yamaha’, ‘suzuki’] [‘honda’, ‘yamaha’, ‘suzuki’, ‘ducati’]
Метод append() упрощает динамическое построение списков. Например, вы можете начать с пустого списка и добавлять в него элементы серией команд append(). В следующем примере в пустой список добавляются элементы ‘honda’, ‘yamaha’ и ‘suzuki’: motorcycles = [] motorcycles.append(‘honda’) motorcycles.append(‘yamaha’) motorcycles.append(‘suzuki’) print(motorcycles)
Полученный список выглядит точно так же, как и списки из предыдущих примеров: [‘honda’, ‘yamaha’, ‘suzuki’]
Такой способ построения списков встречается очень часто, потому что данные, которые пользователь захочет сохранить в программе, нередко становятся известными только после запуска программы. Чтобы пользователь мог управлять содержимым списка, начните с определения пустого списка, а затем присоединяйте к нему каждое новое значение. Вставка элементов в список Метод insert() позволяет добавить новый элемент в произвольную позицию списка. Для этого следует указать индекс и значение нового элемента. motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’] motorcycles.insert(0, ‘ducati’) print(motorcycles)
В этом примере в точке значение ‘ducati’ вставляется в начало списка. Метод insert() выделяет свободное место в позиции 0 и сохраняет в нем значение ‘ducati’. Все остальные значения списка при этом сдвигаются на одну позицию вправо: [‘ducati’, ‘honda’, ‘yamaha’, ‘suzuki’]
Удаление элементов из списка Нередко возникает необходимость в удалении одного или нескольких элементов из списка. Например, когда игрок сбивает корабль пришельца, этот корабль нужно удалить из списка активных врагов. Или когда пользователь решает удалить свою учетную запись в созданном вами веб-приложении, этот пользователь должен быть
Индексы начинаются с 0, а не с 1
убран из списка активных пользователей. Элементы удаляются из списка по позиции или по значению. Удаление элемента с использованием команды del Если вам известна позиция элемента, который должен быть удален из списка, воспользуйтесь командой del. motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’] print(motorcycles) del motorcycles[0] print(motorcycles)
В точке вызов del удаляет первый элемент, ‘honda’, из списка motorcycles: [‘honda’, ‘yamaha’, ‘suzuki’] [‘yamaha’, ‘suzuki’]
Команда del позволяет удалить элемент из любой позиции списка, если вам известен его индекс. Например, вот как из списка удаляется второй элемент ‘yamaha’: motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’] print(motorcycles) del motorcycles[1] print(motorcycles)
Второй элемент исчез из списка: [‘honda’, ‘yamaha’, ‘suzuki’] [‘honda’, ‘suzuki’]
В обоих примерах значение, удаленное из списка после использования команды del, становится недоступным. Удаление элемента с использованием метода pop() Иногда значение, удаляемое из списка, должно как-то использоваться. Допустим, вы хотите получить координаты x и y только что сбитого корабля пришельцев, чтобы изобразить взрыв в этой позиции. В веб-приложении пользователь, удаленный из списка активных участников, может быть добавлен в список неактивных и т. д. Метод pop() удаляет последний элемент из списка, но позволяет работать с ним после удаления. Удалим мотоцикл из списка: motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’] print(motorcycles) popped_motorcycle = motorcycles.pop() print(motorcycles) print(popped_motorcycle)
Сначала в точке определяется и выводится содержимое списка motorcycles. В точке значение извлекается из списка и сохраняется в переменной с именем
Глава 3 • Списки
popped_motorcycle. Вывод измененного списка в точке показывает, что значе-
ние было удалено из списка. Затем мы выводим извлеченное значение в точке , демонстрируя, что удаленное из списка значение остается доступным в программе. Из вывода видно, что значение ‘suzuki’, удаленное в конце списка, теперь хранится в переменной popped_motorcycle: [‘honda’, ‘yamaha’, ‘suzuki’] [‘honda’, ‘yamaha’] suzuki
Для чего может понадобиться метод pop()? Представьте, что мотоциклы в списке хранятся в хронологическом порядке в соответствии с датой их покупки. В таком случае команда pop() может использоваться для вывода сообщения о последнем купленном мотоцикле: motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’] last_owned = motorcycles.pop() print(«The last motorcycle I owned was a » + last_owned.title() + «.»)
Программа выводит простое сообщение: The last motorcycle I owned was a Suzuki.
Извлечение элементов из произвольной позиции списка Вызов pop() может использоваться для удаления элемента в произвольной позиции списка; для этого следует указать индекс удаляемого элемента в круглых скобках. motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’] first_owned = motorcycles.pop(0) print(‘The first motorcycle I owned was a ‘ + first_owned.title() + ‘.’)
Сначала первый элемент извлекается из списка в точке , а затем в точке выводится сообщение об этом мотоцикле. Программа выводит простое сообщение о первом мотоцикле: The first motorcycle I owned was a Honda.
Помните, что после каждого вызова pop() элемент, с которым вы работаете, уже не находится в списке. Если вы не уверены в том, какой из двух способов выбрать — команду del или метод pop(), — то простое правило поможет вам определиться. Если вы собираетесь просто удалить элемент из списка, никак не используя его, выбирайте команду del; если же вы намерены использовать элемент после удаления из списка, выбирайте метод pop(). Удаление элементов по значению Иногда позиция удаляемого элемента неизвестна. Если вы знаете только значение элемента, используйте метод remove().
Индексы начинаются с 0, а не с 1
Допустим, из списка нужно удалить значение ‘ducati’: motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’, ‘ducati’] print(motorcycles) motorcycles.remove(‘ducati’) print(motorcycles)
Код в точке приказывает Python определить, в какой позиции списка находится значение ‘ducati’, и удалить этот элемент: [‘honda’, ‘yamaha’, ‘suzuki’, ‘ducati’] [‘honda’, ‘yamaha’, ‘suzuki’]
Метод remove() также может использоваться для работы со значением, которое удаляется из списка. Следующая программа удаляет значение ‘ducati’ и выводит причину удаления: motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’, ‘ducati’] print(motorcycles) too_expensive = ‘ducati’ motorcycles.remove(too_expensive) print(motorcycles) print(«\nA » + too_expensive.title() + » is too expensive for me.»)
После определения списка в точке значение ‘ducati’ сохраняется в переменной с именем too_expensive в точке . Затем эта переменная сообщает Python, какое значение должно быть удалено из списка . В точке значение ‘ducati’ было удалено из списка, но продолжает храниться в переменной too_expensive, что позволяет вывести сообщение с причиной удаления ‘ducati’ из списка мотоциклов: [‘honda’, ‘yamaha’, ‘suzuki’, ‘ducati’] [‘honda’, ‘yamaha’, ‘suzuki’] A Ducati is too expensive for me. ПРИМЕЧАНИЕ Метод remove() удаляет только первое вхождение заданного значения . Если существует вероятность того, что значение встречается в списке более одного раза, используйте цикл для определения того, были ли удалены все вхождения данного значения . О том, как это делать, рассказано в главе 7 . УПРАЖНЕНИЯ Следующие упражнения немного сложнее упражнений из главы 2, но они предоставляют возможность попрактиковаться в выполнении всех описанных операций со списками . 3-4 . Список гостей: если бы вы могли пригласить кого угодно (из живых или умерших) на обед, то кого бы вы пригласили? Создайте список, включающий минимум трех людей, которых вам хотелось бы пригласить на обед . Затем используйте этот список для вывода пригласительного сообщения каждому участнику .
Глава 3 • Списки 3-5 . Изменение списка гостей: вы только что узнали, что один из гостей прийти не сможет, поэтому вам придется разослать новые приглашения . Отсутствующего гостя нужно заменить кем-то другим . •
Начните с программы из упражнения 3-4 . Добавьте в конец программы команду print для вывода имени гостя, который прийти не сможет .
Измените список и замените имя гостя, который прийти не сможет, именем нового приглашенного .
Выведите новый набор сообщений с приглашениями – по одному для каждого участника, входящего в список .
3-6 . Больше гостей: вы решили купить обеденный стол большего размера . Дополнительные места позволяют пригласить на обед еще трех гостей . •
Начните с программы из упражнения 3-4 или 3-5 . Добавьте в конец программы команду print, которая выводит сообщение о расширении списка гостей .
Добавьте вызов insert() для добавления одного гостя в начало списка .
Добавьте вызов insert() для добавления одного гостя в середину списка .
Добавьте вызов append() для добавления одного гостя в конец списка .
Выведите новый набор сообщений с приглашениями – по одному для каждого участника, входящего в список .
3-7 . Сокращение списка гостей: только что выяснилось, что новый обеденный стол привезти вовремя не успеют, и места хватит только для двух гостей . •
Начните с программы из упражнения 3-6 . Добавьте команду для вывода сообщения о том, что на обед приглашаются всего два гостя .
Используйте метод pop() для последовательного удаления гостей из списка до тех пор, пока в списке не останутся только два человека . Каждый раз, когда из списка удаляется очередное имя, выведите для этого человека сообщение о том, что вы сожалеете об отмене приглашения .
Выведите сообщение для каждого из двух человек, остающихся в списке . Сообщение должно подтверждать, что более раннее приглашение остается в силе .
Используйте команду del для удаления двух последних имен, чтобы список остался пустым . Выведите список, чтобы убедиться в том, что в конце работы программы список действительно не содержит ни одного элемента .
Упорядочение списка Нередко список создается в непредсказуемом порядке, потому что порядок получения данных от пользователя не всегда находится под вашим контролем. И хотя во многих случаях такое положение дел неизбежно, часто требуется вывести информацию в определенном порядке. В одних случаях требуется сохранить исходный порядок элементов в списке, в других исходный порядок должен быть изменен. Python предоставляет в распоряжение программиста несколько разных способов упорядочения списка в зависимости от ситуации.
Постоянная сортировка списка методом sort() Метод sort() позволяет относительно легко отсортировать список. Предположим, имеется список машин, и вы хотите переупорядочить эти элементы по алфавиту.
Чтобы упростить задачу, предположим, что все значения в списке состоят из символов нижнего регистра. cars.py cars = [‘bmw’, ‘audi’, ‘toyota’, ‘subaru’] cars.sort() print(cars)
Метод sort() в точке осуществляет постоянное изменение порядка элементов в списке. Названия машин располагаются в алфавитном порядке, и вернуться к исходному порядку уже не удастся: [‘audi’, ‘bmw’, ‘subaru’, ‘toyota’]
Список также можно отсортировать в обратном алфавитном порядке; для этого методу sort() следует передать аргумент reverse=True. В следующем примере список сортируется в порядке, обратном алфавитному: cars = [‘bmw’, ‘audi’, ‘toyota’, ‘subaru’] cars.sort(reverse=True) print(cars)
И снова порядок элементов изменяется на постоянной основе: [‘toyota’, ‘subaru’, ‘bmw’, ‘audi’]
Временная сортировка списка функцией sorted() Чтобы сохранить исходный порядок элементов списка, но временно представить их в отсортированном порядке, можно воспользоваться функцией sorted(). Функция sorted() позволяет представить список в определенном порядке, но не изменяет фактического порядка элементов в списке. Попробуем применить эту функцию к списку машин. cars = [‘bmw’, ‘audi’, ‘toyota’, ‘subaru’] print(«Here is the original list:») print(cars) print(«\nHere is the sorted list:») print(sorted(cars)) print(«\nHere is the original list again:») print(cars)
Сначала список выводится в исходном порядке , а затем в алфавитном . После того как список будет выведен в новом порядке, в точке , мы убеждаемся в том, что список все еще хранится в исходном порядке. Here is the original list: [‘bmw’, ‘audi’, ‘toyota’, ‘subaru’] Here is the sorted list:
Глава 3 • Списки [‘audi’, ‘bmw’, ‘subaru’, ‘toyota’]
Here is the original list again: [‘bmw’, ‘audi’, ‘toyota’, ‘subaru’]
Обратите внимание: после вызова функции sorted() список продолжает храниться в исходном порядке . Функции sorted() также можно передать аргумент reverse=True, чтобы список был представлен в порядке, обратном алфавитному. ПРИМЕЧАНИЕ Если не все значения записаны в нижнем регистре, алфавитная сортировка списка немного усложняется . При определении порядка сортировки появляются разные способы интерпретации прописных букв, и точное определение порядка уже не столь важно (во всяком случае, чтобы отвлекаться на него сейчас) . Впрочем, большинство способов сортировки напрямую следует из того, о чем вы узнали в этом разделе .
Вывод списка в обратном порядке Чтобы переставить элементы списка в обратном порядке, используйте метод reverse(). Скажем, если список машин первоначально хранился в хронологическом порядке даты приобретения, элементы можно легко переупорядочить в обратном хронологическом порядке: cars = [‘bmw’, ‘audi’, ‘toyota’, ‘subaru’] print(cars) cars.reverse() print(cars)
Обратите внимание: метод reverse() не сортирует элементы в обратном алфавитном порядке, а просто переходит к обратному порядку списка: [‘bmw’, ‘audi’, ‘toyota’, ‘subaru’] [‘subaru’, ‘toyota’, ‘audi’, ‘bmw’]
Метод reverse() осуществляет постоянное изменение порядка элементов, но вы можете легко вернуться к исходному порядку, снова применив reverse() к обратному списку.
Определение длины списка Вы можете быстро определить длину списка с помощью функции len(). Список в нашем примере состоит из четырех элементов, поэтому его длина равна 4: >>> cars = [‘bmw’, ‘audi’, ‘toyota’, ‘subaru’] >>> len(cars) 4
Метод len() может пригодиться для определения количества пришельцев, которых необходимо сбить в игре; объема данных, которыми необходимо управлять в визуализации; количества зарегистрированных пользователей на веб-сайте и т. д.
Ошибки индексирования при работе со списками
ПРИМЕЧАНИЕ Python подсчитывает элементы списка, начиная с 1, поэтому при определении длины списка ошибок «смещения на 1» уже быть не должно . УПРАЖНЕНИЯ 3-8 . Повидать мир: вспомните хотя бы пять стран, в которых вам хотелось бы побывать . •
Сохраните названия стран в списке . Проследите за тем, чтобы список не хранился в алфавитном порядке .
Выведите список в исходном порядке . Не беспокойтесь об оформлении списка, просто выведите его как обычный список Python .
Используйте функцию sorted() для вывода списка в алфавитном порядке без изменения списка .
Снова выведите список, чтобы показать, что он по-прежнему хранится в исходном порядке .
Используйте функцию sorted() для вывода списка в обратном алфавитном порядке без изменения порядка исходного списка .
Снова выведите список, чтобы показать, что исходный порядок не изменился .
Измените порядок элементов вызовом reverse() . Выведите список, чтобы показать, что элементы следуют в другом порядке .
Измените порядок элементов повторным вызовом reverse() . Выведите список, чтобы показать, что список вернулся к исходному порядку .
Отсортируйте список в алфавитном порядке вызовом sort() . Выведите список, чтобы показать, что элементы следуют в другом порядке .
Вызовите sort() для перестановки элементов списка в обратном алфавитном порядке . Выведите список, чтобы показать, что порядок элементов изменился .
3-9 . Количество гостей: в одной из программ из упражнений с 3-4 по 3-7 используйте len() для вывода сообщения с количеством людей, приглашенных на обед . 3-10 . Все функции: придумайте информацию, которую можно было бы хранить в списке . Например, создайте список гор, рек, стран, городов, языков… словом, чего угодно . Напишите программу, которая создает список элементов, а затем вызывает каждую функцию, упоминавшуюся в этой главе, хотя бы один раз .
Ошибки индексирования при работе со списками Когда программист только начинает работать со списками, он часто допускает одну характерную ошибку. Допустим, имеется список с тремя элементами, и программа запрашивает четвертый элемент: motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’] print(motorcycles[3])
В этом случае происходит ошибка индексирования: Traceback (most recent call last): File «motorcycles.py», line 3, in print(motorcycles[3]) IndexError: list index out of range
Глава 3 • Списки
Python пытается вернуть элемент с индексом 3. Однако при поиске по списку ни один элемент motorcycles не обладает индексом 3. Из-за смещения индексов на 1 эта ошибка весьма распространена. Люди думают, что третьим элементом является элемент с индексом 3, потому что они начинают отсчет с 1. Но для Python третьим является элемент с индексом 2, потому что индексирование начинается с 0. Ошибка индексирования означает, что Python не может понять, какой индекс запрашивается в программе. Если в вашей программе происходит ошибка индексирования, попробуйте уменьшить запрашиваемый индекс на 1. Затем снова запустите программу и проверьте правильность результатов. Помните, что для обращения к последнему элементу в списке используется индекс –1. Этот способ работает всегда, даже если размер списка изменился с момента последнего обращения к нему: motorcycles = [‘honda’, ‘yamaha’, ‘suzuki’] print(motorcycles[-1])
Индекс –1 всегда возвращает последний элемент списка, в данном случае значение ‘suzuki’: ‘suzuki’
Этот синтаксис порождает ошибку только в одном случае — при попытке получить последний элемент пустого списка: motorcycles = [] print(motorcycles[-1])
В списке motorcycles нет ни одного элемента, поэтому Python снова выдает ошибку индексирования: Traceback (most recent call last): File «motorcyles.py», line 3, in print(motorcycles[-1]) IndexError: list index out of range ПРИМЕЧАНИЕ Если в вашей программе произошла ошибка индексирования и вы не знаете, как с ней справиться, попробуйте вывести список или хотя бы его длину . Возможно, ваш список выглядит совсем не так, как вы думаете, особенно если его содержимое динамически определялось программой . Фактическое состояние списка или точное количество элементов в нем поможет вам выявить логические ошибки такого рода . УПРАЖНЕНИЯ 3-11 . Намеренная ошибка: если ни в одной из предшествующих программ вы еще не сталкивались с ошибками индексирования, попробуйте создать такую ошибку искусственно . Измените индекс в одной из программ, чтобы вызвать ошибку индексирования . Не забудьте исправить ошибку перед тем, как закрыть программу .
Итоги В этой главе вы узнали, что представляют собой списки и как работать с отдельными элементами в списках. Вы научились определять списки, добавлять и удалять элементы, выполнять сортировку (постоянную или временную для отображения). Также вы узнали, как определить длину списка и как избежать ошибок индексирования при работе со списком. В главе 4 рассматриваются приемы более эффективной работы со списками. Перебор всех элементов списка всего в нескольких строках кода, даже если список содержит тысячи или миллионы элементов, сокращает объем программы.
Работа со списками
В главе 3 вы научились создавать простые списки и работать с отдельными элементами списков. В этой главе вы узнаете, как перебрать весь список всего в нескольких строках кода (независимо от длины списка). Механизм перебора позволяет выполнить одно действие или набор действий с каждым элементом в списке. С его помощью вы сможете эффективно работать со списками любой длины, даже состоящими из тысяч и миллионов элементов.
Перебор всего списка Типичная задача из области программирования — перебрать все элементы списка и выполнить с каждым элементом одну и ту же операцию. Например, в компьютерной игре все экранные объекты могут смещаться на одинаковую величину, или в списке чисел к каждому элементу может применяться одна и та же статистическая операция. А может быть, вам нужно вывести все заголовки из списка статей на сайте. В ситуациях, требующих применения одного действия к каждому элементу списка, можно воспользоваться циклами for. Допустим, имеется список с именами фокусников, и вы хотите вывести каждое имя из списка. Конечно, можно обратиться к каждому элементу по отдельности, но такой подход создает ряд проблем. Во-первых, для очень длинных списков все сведется к однообразным повторениям. Во-вторых, при любом изменении длины списка в программу придется вносить изменения. Цикл for решает обе проблемы: Python будет следить за всеми техническими деталями в своей внутренней реализации. В следующем примере цикл for используется для вывода имен фокусников: magicians.py magicians = [‘alice’, ‘david’, ‘carolina’] for magician in magicians: print(magician)
Все начинается с определения списка , как и в главе 3. В точке определяется цикл for. Эта строка приказывает Python взять очередное имя из списка и сохранить его в переменной magician. В точке выводится имя, только что сохраненное в переменной magician. Затем строки и повторяются для каждого имени
Перебор всего списка
в списке. Этот код можно описать так: «Для каждого фокусника в списке вывести его имя». Результат представляет собой простой перечень имен из списка: alice david carolina
Подробнее о циклах Концепция циклов очень важна, потому что она представляет один из основных способов автоматизации повторяющихся задач компьютером. Например, в простом цикле, использованном в magicians .py, Python сначала читает первую строку цикла: for magician in magicians:
Эта строка означает, что нужно взять первое значение из списка magicians и сохранить его в переменной magician. Первое значение в списке — ‘alice’. Затем Python читает следующую строку: print(magician)
Python выводит текущее значение magician, которое все еще равно ‘alice’. Так как в списке еще остались другие значения, Python возвращается к первой строке цикла: for magician in magicians:
Python берет следующее значение из списка — ‘david’ — и сохраняет его в magician. Затем выполняется строка: print(magician)
Python снова выводит текущее значение magician; теперь это строка ‘david’. Весь цикл повторяется еще раз с последним значением в списке, ‘carolina’. Так как других значений в списке не осталось, Python переходит к следующей строке в программе. В данном случае после цикла for ничего нет, поэтому программа просто завершается. Помните, что все действия повторяются по одному разу для каждого элемента в списке независимо от их количества. Если список содержит миллион элементов, Python повторит эти действия миллион раз — обычно это происходит очень быстро. Также помните, что при написании собственных циклов for временной переменной для текущего значения из списка можно присвоить любое имя. Однако на практике рекомендуется выбирать осмысленное имя, описывающее отдельный элемент списка. Несколько примеров: for cat in cats: for dog in dogs: for item in list_of_items:
Выполнение этого правила поможет вам проследить за тем, какие действия выполняются с каждым элементом в цикле for. В зависимости от того, какое число ис-
Глава 4 • Работа со списками
пользуется — одиночное и множественное, вы сможете понять, работает ли данная часть кода с отдельным элементом из списка или со всем списком.
Более сложные действия в циклах for В цикле for с каждым элементом списка может выполняться практически любое действие. Дополним предыдущий пример, чтобы программа выводила для каждого фокусника отдельное сообщение: magicians = [‘alice’, ‘david’, ‘carolina’] for magician in magicians: print(magician.title() + «, that was a great trick!»)
Единственное отличие этого кода от предыдущего заключается в том, что в точке для каждого фокусника строится сообщение с его именем. При первом проходе цикла переменная magician содержит значение ‘alice’, поэтому Python начинает первое сообщение с имени ‘Alice’. При втором проходе сообщение будет начинаться с имени ‘David’, а при третьем — с имени ‘Carolina’: Alice, that was a great trick! David, that was a great trick! Carolina, that was a great trick!
Тело цикла for может содержать сколько угодно строк кода. Каждая строка с начальным отступом после строки for magician in magicians считается находящейся в цикле и выполняется по одному разу для каждого значения в списке. Таким образом, с каждым значением в списке можно выполнить любые операции на ваше усмотрение. Включим в сообщение для каждого фокусника вторую строку: magicians = [‘alice’, ‘david’, ‘carolina’] for magician in magicians: print(magician.title() + «, that was a great trick!») print(«I can’t wait to see your next trick, » + magician. title() + «.\n»)
Так как обе команды print снабжены отступами, каждая строка будет выполнена по одному разу для каждого фокусника в списке. Символ новой строки («\n») во второй команде print вставляет пустую строку после каждого прохода цикла. В результате будет создан набор сообщений, аккуратно сгруппированных для каждого фокусника в списке: Alice, that was a great trick! I can’t wait to see your next trick, Alice. David, that was a great trick! I can’t wait to see your next trick, David. Carolina, that was a great trick! I can’t wait to see your next trick, Carolina.
Перебор всего списка
Тело цикла for может содержать сколько угодно строк кода. На практике часто требуется выполнить в цикле for несколько разных операций для каждого элемента списка.
Выполнение действий после цикла for Что происходит после завершения цикла for? Обычно программа выводит сводную информацию или переходит к другим операциям. Каждая строка кода после цикла for, не имеющая отступа, выполняется без повторения. Допустим, вы хотите вывести сообщение для всей группы фокусников и поблагодарить их за превосходное представление. Чтобы вывести общее сообщение после всех отдельных сообщений, разместите его после цикла for без отступа: magicians = [‘alice’, ‘david’, ‘carolina’] for magician in magicians: print(magician.title() + «, that was a great trick!») print(«I can’t wait to see your next trick, » + magician.title() + «.\n») print(«Thank you, everyone. That was a great magic show!»)
Первые две команды print повторяются по одному разу для каждого фокусника в списке, как было показано ранее. Но поскольку строка отступа не имеет, это сообщение выводится только один раз: Alice, that was a great trick! I can’t wait to see your next trick, Alice. David, that was a great trick! I can’t wait to see your next trick, David. Carolina, that was a great trick! I can’t wait to see your next trick, Carolina. Thank you, everyone. That was a great magic show!
При обработке данных в циклах for завершающее сообщение позволяет подвести итог операции, выполненной со всем набором данных. Например, цикл for может инициализировать игру, перебирая список персонажей и изображая каждого персонажа на экране. После цикла выполняется блок без отступа, который выводит кнопку Начать игру после того, как все персонажи появятся на экране.
Предотвращение ошибок с отступами В Python связь одной строки кода с предшествующей строкой обозначается отступами. В приведенных примерах строки, выводившие сообщения для отдельных фокусников, были частью цикла, потому что они были снабжены отступами. Применение отступов в Python сильно упрощает чтение кода. Фактически отступы заставляют разработчика писать аккуратно отформатированный код с четкой визуальной структурой. В более длинных программах Python могут встречаться
Глава 4 • Работа со списками
блоки кода с отступами нескольких разных уровней. Эти уровни способствуют пониманию общей структуры программы. Когда разработчики только начинают писать код, работа которого зависит от правильности отступов, в их коде нередко встречаются распространенные ошибки. Например, иногда они расставляют отступы в коде, в котором эти отступы не нужны, или наоборот — забывают ставить отступы в блоках, где это необходимо. Несколько примеров помогут вам избежать подобных ошибок в будущем и успешно исправлять их, когда они встретятся в ваших программах. Итак, рассмотрим несколько типичных ошибок при использовании отступов.
Пропущенный отступ Строка после команды for в цикле всегда должна снабжаться отступом. Если вы забудете поставить отступ, Python напомнит вам об этом: magicians.py magicians = [‘alice’, ‘david’, ‘carolina’] for magician in magicians: print(magician)
Команда print в точке должна иметь отступ, но здесь его нет. Когда Python ожидает увидеть блок с отступом, но не находит его, появляется сообщение с указанием номера строки: File «magicians.py», line 3 print(magician) ^ IndentationError: expected an indented block
Обычно для устранения подобных ошибок достаточно поставить отступ в строке (или строках), следующей непосредственно после команды for.
Пропущенные отступы в других строках Иногда цикл выполняется без ошибок, но не выдает ожидаемых результатов. Такое часто происходит, когда вы пытаетесь выполнить несколько операций в цикле, но забываете снабдить отступом некоторые из строк. Например, вот что происходит, если вы забудете снабдить отступом вторую строку в цикле: magicians = [‘alice’, ‘david’, ‘carolina’] for magician in magicians: print(magician.title() + «, that was a great trick!») print(«I can’t wait to see your next trick, » + magician.title() + «.\n»)
Команда print в точке должна быть снабжена отступом, но, поскольку Python находит хотя бы одну строку с отступом после команды for, сообщение об ошибке не выдается. В результате первая команда print будет выполнена для каждого
Перебор всего списка
элемента в списке, потому что в ней есть отступ. Вторая команда print отступа не имеет, поэтому она будет выполнена только один раз после завершения цикла. Так как последним значением magician является строка ‘carolina’, второе сообщение будет выведено только с этим именем: Alice, that was a great trick! David, that was a great trick! Carolina, that was a great trick! I can’t wait to see your next trick, Carolina.
Это пример логической ошибки. Код имеет действительный синтаксис, но он не приводит к желаемому результату, потому что проблема кроется в его логике. Если некоторое действие должно повторяться для каждого элемента в списке, но выполняется только один раз, проверьте, не нужно ли добавить отступы в строке или нескольких строках кода.
Лишние отступы Если вы случайно поставите отступ в строке, в которой он не нужен, Python сообщит об этом: hello_world.py message = «Hello Python world!» print(message)
Отступ команды print в точке не нужен, потому что эта строка не подчинена предшествующей; Python сообщает об ошибке: File «hello_world.py», line 2 print(message) ^ IndentationError: unexpected indent
Чтобы избежать непредвиденных ошибок с отступами, используйте их только там, где для этого существуют конкретные причины. В тех программах, которые вы пишете на этой стадии изучения Python, отступы нужны только в строках действий, повторяемых для каждого элемента в цикле for.
Лишние отступы после цикла Если вы случайно снабдите отступом код, который должен выполняться после завершения цикла, то этот код будет выполнен для каждого элемента. Иногда Python выводит сообщение об ошибке, но часто дело ограничивается простой логической ошибкой. Например, что произойдет, если случайно снабдить отступом строку с выводом завершающего приветствия для группы фокусников? magicians = [‘alice’, ‘david’, ‘carolina’] for magician in magicians: print(magician.title() + «, that was a great trick!»)
Глава 4 • Работа со списками print(«I can’t wait to see your next trick, » + magician.title() + «.\n») print(«Thank you everyone, that was a great magic show!»)
Так как строка имеет отступ, сообщение будет продублировано для каждого фокусника в списке : Alice, that was a great trick! I can’t wait to see your next trick, Alice. Thank you everyone, that was a great magic show! David, that was a great trick! I can’t wait to see your next trick, David. Thank you everyone, that was a great magic show! Carolina, that was a great trick! I can’t wait to see your next trick, Carolina. Thank you everyone, that was a great magic show!
Это еще один пример логической ошибки, наподобие описанной в разделе «Пропущенные отступы в других строках» на с. 66. Python не знает, что вы пытаетесь сделать в своем коде, поэтому он просто выполняет весь код, не нарушающий правил синтаксиса. Если действие, которое должно выполняться один раз, выполняется многократно, проверьте, нет ли лишнего отступа в соответствующей строке кода.
Пропущенное двоеточие Двоеточие в конце команды for сообщает Python, что следующая строка является началом цикла. magicians = [‘alice’, ‘david’, ‘carolina’] for magician in magicians print(magician)
Если вы случайно забудете поставить двоеточие, как в примере , произойдет синтаксическая ошибка, так как полученная команда нарушает правила языка. И хотя такие ошибки легко исправляются, найти их бывает достаточно трудно. Вы не поверите, сколько времени тратят программисты на поиск подобных «односимвольных» ошибок. Поиск таких ошибок усложняется еще и тем, что человек склонен видеть то, что он ожидает увидеть. УПРАЖНЕНИЯ 4-1 . Пицца: вспомните по крайней мере три ваши любимые разновидности пиццы . Сохраните их в списке и используйте цикл for для вывода всех названий . •
Измените цикл for так, чтобы вместо простого названия пиццы выводилось сообщение, включающее это название . Таким образом, для каждого элемента должна выводиться строка с простым текстом вида «I like pepperoni pizza» .
Добавьте в конец программы (после цикла for) строку с завершающим сообщением . Таким образом, вывод должен состоять из трех (и более) строк с названиями пиццы и дополнительного сообщения, скажем, «I really love pizza!» .
Создание числовых списков
4-2 . Животные: создайте список из трех (и более) животных, обладающих общей характеристикой . Используйте цикл for для вывода названий всех животных . •
Измените программу так, чтобы вместо простого названия выводилось сообщение, включающее это название, например «A dog would make a great pet» .
Добавьте в конец программы строку с описанием общего свойства . Например, можно вывести сообщение «Any of these animals would make a great pet!» .
Создание числовых списков Необходимость хранения наборов чисел возникает в программах по многим причинам. Например, в компьютерной игре могут храниться координаты каждого персонажа на экране, таблицы рекордов и т. д. В программах визуализации данных пользователь почти всегда работает с наборами чисел: температурой, расстоянием, численностью населения, широтой/долготой и другими числовыми данными. Списки идеально подходят для хранения наборов чисел, а Python предоставляет специальные средства для эффективной работы с числовыми списками. Достаточно один раз понять, как эффективно пользоваться этими средствами, и ваш код будет хорошо работать даже в том случае, если список содержит миллионы элементов.
Функция range() Функция range() упрощает построение числовых последовательностей. Например, с ее помощью можно легко вывести серию чисел: numbers.py for value in range(1,5): print(value)
И хотя на первый взгляд может показаться, что он должен вывести числа от 1 до 5, на самом деле число 5 не выводится: 1 2 3 4
В этом примере range() выводит только числа от 1 до 4. Перед вами еще одно проявление «смещения на 1», часто встречающегося в языках программирования. При выполнении функции range() Python начинает отсчет от первого переданного значения и прекращает его при достижении второго. Так как на втором значении происходит остановка, конец интервала (5 в данном случае) не встречается в выводе. Чтобы вывести числа от 1 до 5, используйте вызов range(1,6): for value in range(1,6): print(value)
На этот раз вывод начинается с 1 и завершается 5:
Глава 4 • Работа со списками 1 2 3 4 5
Если ваша программа при использовании range() выводит не тот результат, на который вы рассчитывали, попробуйте увеличить конечное значение на 1.
Использование range() для создания числового списка Если вы хотите создать числовой список, преобразуйте результаты range() в список при помощи функции list(). Если заключить вызов range() в list(), то результат будет представлять собой список с числовыми элементами. В примере из предыдущего раздела числовая последовательность просто выводилась на экран. Тот же набор чисел можно преобразовать в список вызовом list(): numbers = list(range(1,6)) print(numbers)
Результат: [1, 2, 3, 4, 5]
Функция range() также может генерировать числовые последовательности, пропуская числа в заданном диапазоне. Например, построение списка четных чисел от 1 до 10 происходит так: even_numbers.py even_numbers = list(range(2,11,2)) print(even_numbers)
В этом примере функция range() начинает со значения 2, а затем увеличивает его на 2. Приращение 2 последовательно применяется до тех пор, пока не будет достигнуто или пройдено конечное значение 11, после чего выводится результат: [2, 4, 6, 8, 10]
С помощью функции range() можно создать практически любой диапазон чисел. Например, как бы вы создали список квадратов всех целых чисел от 1 до 10? В языке Python операция возведения в степень обозначается двумя звездочками (**). Один из возможных вариантов выглядит так: squares.py squares = [] for value in range(1,11): square = value**2 squares.append(square) print(squares)
Создание числовых списков
Сначала в точке создается пустой список с именем squares. В точке вы приказываете Python перебрать все значения от 1 до 10 при помощи функции range(). В цикле текущее значение возводится во вторую степень, а результат сохраняется в переменной square в точке . В точке каждое новое значение square присоединяется к списку squares. Наконец, после завершения цикла список квадратов выводится в точке : [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Чтобы сделать код более компактным, можно опустить временную переменную square и присоединять каждое новое значение прямо к списку: squares = [] for value in range(1,11): squares.append(value**2) print(squares)
Конструкция выполняет ту же работу, что и строки и в squares .py. Каждое значение в цикле возводится во вторую степень, а затем немедленно присоединяется к списку квадратов. При создании более сложных списков можно использовать любой из двух подходов. Иногда использование временной переменной упрощает чтение кода; в других случаях оно приводит лишь к напрасному удлинению кода. Сначала сосредоточьтесь на написании четкого и понятного кода, который делает именно то, что нужно, — и только потом переходите к анализу кода и поиску более эффективных решений.
Простая статистика с числовыми списками Некоторые функции Python предназначены для работы с числовыми списками. Например, вы можете легко узнать минимум, максимум и сумму числового списка: >>> >>> 0 >>> 9 >>> 45
digits = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] min(digits) max(digits) sum(digits)
ПРИМЕЧАНИЕ В примерах этой главы используются короткие списки чисел, но это делается только для того, чтобы данные помещались на странице . Примеры также будут работать и в том случае, если список содержит миллионы чисел .
Генераторы списков Описанный выше способ генерирования списка squares состоял из трех или четырех строк кода. Генератор списка (list comprehension) позволяет сгенериро-
Глава 4 • Работа со списками
вать тот же список всего в одной строке. Генератор списка объединяет цикл for и создание новых элементов в одну строку и автоматически присоединяет к списку все новые элементы. Учебники не всегда рассказывают о генераторах списка начинающим программистам, но я привожу этот материал, потому что вы с большой вероятностью встретите эту конструкцию, как только начнете просматривать код других разработчиков. В следующем примере список квадратов, знакомый вам по предыдущим примерам, строится с использованием генератора списка: squares.py squares = [value**2 for value in range(1,11)] print(squares)
Чтобы использовать этот синтаксис, начните с содержательного имени списка, например squares. Затем откройте квадратные скобки и определите выражение для значений, которые должны быть сохранены в новом списке. В данном примере это выражение value**2, которое возводит значение во вторую степень. Затем напишите цикл for для генерирования чисел, которые должны передаваться выражению, и закройте квадратные скобки. Цикл for в данном примере — for value in range(1,11) — передает значения с 1 до 10 выражению value**2. Обратите внимание на отсутствие двоеточия в конце команды for. Результатом будет уже знакомый вам список квадратов: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Чтобы успешно писать собственные генераторы списков, необходим определенный опыт. Тем не менее, как только вы освоитесь с созданием обычных списков, вы оцените возможности генераторов. Когда после очередного трех-четырехстрочного блока вам это надоест, подумайте о написании собственных генераторов списков. УПРАЖНЕНИЯ 4-3 . Считаем до 20: используйте цикл for для вывода чисел от 1 до 20 включительно . 4-4 . Миллион: создайте список чисел от 1 до 1 000 000, затем воспользуйтесь циклом for для вывода чисел . (Если вывод занимает слишком много времени, остановите его нажатием Ctrl+C или закройте окно вывода .) 4-5 . Суммирование миллиона чисел: создайте список чисел от 1 до 1 000 000, затем воспользуйтесь функциями min() и max() и убедитесь в том, что список действительно начинается с 1 и заканчивается 1 000 000 . Вызовите функцию sum() и посмотрите, насколько быстро Python сможет просуммировать миллион чисел . 4-6 . Нечетные числа: воспользуйтесь третьим аргументом функции range() для создания списка нечетных чисел от 1 до 20 . Выведите все числа в цикле for . 4-7 . Тройки: создайте список чисел, кратных 3, в диапазоне от 3 до 30 . Выведите все числа своего списка в цикле for . 4-8 . Кубы: результат возведения числа в третью степень называется кубом . Например, куб 2 записывается в языке Python в виде 2**3 . Создайте список первых 10 кубов (то есть кубов всех целых чисел от 1 до 10) и выведите значения всех кубов в цикле for . 4-9 . Генератор кубов: используйте конструкцию генератора списка для создания списка первых 10 кубов .
Работа с частью списка
Работа с частью списка В главе 3 вы узнали, как обращаться к отдельным элементам списка, а в этой главе мы занимались перебором всех элементов списка. Также можно работать с конкретным подмножеством элементов списка; в Python такие подмножества называются срезами (slices).
Создание среза Чтобы создать срез списка, следует задать индексы первого и последнего элементов, с которыми вы намереваетесь работать. Как и в случае с функцией range(), Python останавливается на элементе, предшествующем второму индексу. Скажем, чтобы вывести первые три элемента списка, запросите индексы с 0 по 3, и вы получите элементы 0, 1 и 2. В следующем примере используется список игроков команды: players.py players = [‘charles’, ‘martina’, ‘michael’, ‘florence’, ‘eli’] print(players[0:3])
В точке выводится срез списка, включающий только первых трех игроков. Вывод сохраняет структуру списка, но включает только первых трех игроков: [‘charles’, ‘martina’, ‘michael’]
Подмножество может включать любую часть списка. Например, чтобы ограничиться вторым, третьим и четвертым элементами списка, срез начинается с индекса 1 и заканчивается на индексе 4: players = [‘charles’, ‘martina’, ‘michael’, ‘florence’, ‘eli’] print(players[1:4])
На этот раз срез начинается с элемента ‘martina’ и заканчивается элементом ‘florence’: [‘martina’, ‘michael’, ‘florence’]
Если первый индекс среза не указан, то Python автоматически начинает срез от начала списка: players = [‘charles’, ‘martina’, ‘michael’, ‘florence’, ‘eli’] print(players[:4])
Без начального индекса Python берет элементы от начала списка: [‘charles’, ‘martina’, ‘michael’, ‘florence’]
Аналогичный синтаксис работает и для срезов, включающих конец списка. Например, если вам нужны все элементы с третьего до последнего, начните с индекса 2 и не указывайте второй индекс: players = [‘charles’, ‘martina’, ‘michael’, ‘florence’, ‘eli’] print(players[2:])
Глава 4 • Работа со списками
Python возвращает все элементы с третьего до конца списка: [‘michael’, ‘florence’, ‘eli’]
Этот синтаксис позволяет вывести все элементы от любой позиции до конца списка независимо от его длины. Вспомните, что отрицательный индекс возвращает элемент, находящийся на заданном расстоянии от конца списка; следовательно, вы можете получить любой срез от конца списка. Например, чтобы отобрать последних трех игроков, используйте срез players[-3:]: players = [‘charles’, ‘martina’, ‘michael’, ‘florence’, ‘eli’] print(players[-3:])
Программа выводит имена трех последних игроков, причем продолжает работать даже при изменении размера списка.
Перебор содержимого среза Если вы хотите перебрать элементы, входящие в подмножество элементов, используйте срез в цикле for. В следующем примере программа перебирает первых трех игроков и выводит их имена: players = [‘charles’, ‘martina’, ‘michael’, ‘florence’, ‘eli’] print(«Here are the first three players on my team:») for player in players[:3]: print(player.title())
Вместо того чтобы перебирать весь список игроков , Python ограничивается первыми тремя именами: Here are the first three players on my team: Charles Martina Michael
Срезы приносят огромную пользу во многих ситуациях. Например, при создании компьютерной игры итоговый счет игрока может добавляться в список после окончания текущей партии. После этого программа может получить три лучших результата игрока, отсортировав список по уменьшению и получив срез, включающий только три элемента. При работе с данными срезы могут использоваться для обработки данных блоками заданного размера. Или при построении веб-приложения срезы могут использоваться для постраничного вывода информации так, чтобы на каждой странице выводился соответствующий объем информации.
Копирование списка Часто разработчик берет существующий список и создает на его основе совершенно новый. Посмотрим, как работает копирование списков, и рассмотрим одну ситуацию, в которой копирование списка может принести пользу.
Работа с частью списка
Чтобы скопировать список, создайте срез, включающий весь исходный список без указания первого и второго индекса ([:] ). Эта конструкция создает срез, который начинается с первого элемента и завершается последним; в результате создается копия всего списка. Представьте, что вы создали список своих любимых блюд и теперь хотите создать отдельный список блюд, которые нравятся вашему другу. Пока вашему другу нравятся все блюда из нашего списка, поэтому вы можете создать другой список простым копированием нашего: foods.py my_foods = [‘pizza’, ‘falafel’, ‘carrot cake’] friend_foods = my_foods[:] print(«My favorite foods are:») print(my_foods) print(«\nMy friend’s favorite foods are:») print(friend_foods)
В точке создается список блюд с именем my_foods. В точке создается другой список с именем friend_foods. Чтобы создать копию my_foods, программа запрашивает срез my_foods без указания индексов, и мы сохраняем копию в friend_foods. При выводе обоих списков становится видно, что они содержат одинаковые данные: My favorite foods are: [‘pizza’, ‘falafel’, ‘carrot cake’] My friend’s favorite foods are: [‘pizza’, ‘falafel’, ‘carrot cake’]
Чтобы доказать, что речь в действительности идет о двух разных списках, добавим новое блюдо в каждый список: my_foods = [‘pizza’, ‘falafel’, ‘carrot cake’] friend_foods = my_foods[:] my_foods.append(‘cannoli’) friend_foods.append(‘ice cream’) print(«My favorite foods are:») print(my_foods) print(«\nMy friend’s favorite foods are:») print(friend_foods)
В точке исходные элементы my_foods копируются в новый список friend_foods, как было сделано в предыдущем примере. Затем в каждый список добавляется новый элемент: ‘cannoli’ в my_foods, и ‘ice cream’ в friend_foods. После этого вывод двух списков наглядно показывает, что каждое блюдо находится в соответствующем списке.
Глава 4 • Работа со списками
My favorite foods are: [‘pizza’, ‘falafel’, ‘carrot cake’, ‘cannoli’] My friend’s favorite foods are: [‘pizza’, ‘falafel’, ‘carrot cake’, ‘ice cream’]
Вывод в точке показывает, что элемент ‘cannoli’ находится в списке my_foods, а элемент ‘ice cream’ в этот список не входит. В точке видно, что ‘ice cream’ входит в список friend_foods, а элемент ‘cannoli’ в этот список не входит. Если бы эти два списка просто совпадали, то их содержимое уже не различалось бы. Например, вот что происходит при попытке копирования списка без использования среза: my_foods = [‘pizza’, ‘falafel’, ‘carrot cake’] # This doesn’t work: friend_foods = my_foods my_foods.append(‘cannoli’) friend_foods.append(‘ice cream’) print(«My favorite foods are:») print(my_foods) print(«\nMy friend’s favorite foods are:») print(friend_foods)
Вместо того чтобы сохранять копию my_foods в friend_foods в точке , мы задаем friend_foods равным my_foods. Этот синтаксис в действительности сообщает Python, что новая переменная friend_foods должна быть связана со списком, уже хранящимся в my_foods, поэтому теперь обе переменные связаны с одним списком. В результате при добавлении элемента ‘cannoli’ в my_foods этот элемент также появляется в friend_foods. Аналогичным образом элемент ‘ice cream’ появляется в обоих списках, хотя на первый взгляд он был добавлен только в friend_foods. Вывод показывает, что оба списка содержат одинаковые элементы, а это совсем не то, что требовалось: My favorite foods are: [‘pizza’, ‘falafel’, ‘carrot cake’, ‘cannoli’, ‘ice cream’] My friend’s favorite foods are: [‘pizza’, ‘falafel’, ‘carrot cake’, ‘cannoli’, ‘ice cream’] ПРИМЕЧАНИЕ Если какие-то подробности в этом примере кажутся непонятными, не огорчайтесь . В двух словах, если при работе с копией списка происходит что-то непредвиденное, убедитесь в том, что список копируется с использованием среза, как это делается в нашем первом примере . УПРАЖНЕНИЯ 4-10 . Срезы: добавьте в конец одной из программ, написанных в этой главе, фрагмент, который делает следующее .
Выводит сообщение «The first three items in the list are:», а затем использует срез для вывода первых трех элементов из списка .
Выводит сообщение «Three items from the middle of the list are:», а затем использует срез для вывода первых трех элементов из середины списка .
Выводит сообщение «The last three items in the list are:», а затем использует срез для вывода последних трех элементов из списка .
4-11 . Моя пицца, твоя пицца: начните с программы из упражнения 4-1 . Создайте копию списка с видами пиццы, присвойте ему имя friend_pizzas . Затем сделайте следующее . •
Добавьте новую пиццу в исходный список .
Добавьте другую пиццу в список friend_pizzas .
Докажите, что в программе существуют два разных списка . Выведите сообщение «My favorite pizzas are:», а затем первый список в цикле for . Выведите сообщение «My friend’s favorite pizzas are:», а затем второй список в цикле for . Убедитесь в том, что каждая новая пицца находится в соответствующем списке .
4-12 . Больше циклов: во всех версиях foods .py из этого раздела мы избегали использования цикла for при выводе для экономии места . Выберите версию foods .py и напишите два цикла for для вывода каждого списка .
Кортежи Списки хорошо подходят для хранения наборов элементов, которые могут изменяться на протяжении жизненного цикла программы. Например, возможность модификации списков жизненно необходима при работе со списками пользователей сайта или списками персонажей игры. Однако в некоторых ситуациях требуется создать список элементов, который не может изменяться. Кортежи (tuples) предоставляют именно такую возможность. В языке Python значения, которые не могут изменяться, называются неизменяемыми (immutable), а неизменяемый список называется кортежем.
Определение кортежа Кортеж выглядит как список, не считая того, что вместо квадратных скобок используются круглые скобки. После определения кортежа вы можете обращаться к его отдельным элементам по индексам точно так же, как это делается при работе со списком. Допустим, имеется прямоугольник, который в программе всегда должен иметь строго определенные размеры. Чтобы гарантировать неизменность размеров, можно объединить размеры в кортеж: dimensions.py dimensions = (200, 50) print(dimensions[0]) print(dimensions[1])
В точке определяется кортеж dimensions, при этом вместо квадратных скобок используются круглые. В точке каждый элемент кортежа выводится по отдельности с использованием того же синтаксиса, который использовался для обращения к элементу списка:
Глава 4 • Работа со списками 200 50
Посмотрим, что произойдет при попытке изменения одного из элементов в кортеже dimensions: dimensions = (200, 50) dimensions[0] = 250
Код в точке пытается изменить первое значение, но Python возвращает ошибку типа. По сути, так как мы пытаемся изменить кортеж, а эта операция недопустима для объектов этого типа, Python сообщает о невозможности присваивания нового значения элементу в кортеже: Traceback (most recent call last): File «dimensions.py», line 3, in dimensions[0] = 250 TypeError: ‘tuple’ object does not support item assignment
И это хорошо, потому что мы хотим, чтобы Python сообщал о попытке изменения размеров прямоугольника в программе, выдавая сообщение об ошибке.
Перебор всех значений в кортеже Для перебора всех значений в кортеже используется цикл for, как и при работе со списками: dimensions = (200, 50) for dimension in dimensions: print(dimension)
Python возвращает все элементы кортежа по аналогии с тем, как это делается со списком: 200 50
Замена кортежа Элементы кортежа не могут изменяться, но вы можете присвоить новое значение переменной, в которой хранится кортеж. Таким образом, для изменения размеров прямоугольника следует переопределить весь кортеж: dimensions = (200, 50) print(«Original dimensions:») for dimension in dimensions: print(dimension) dimensions = (400, 100) print(«\nModified dimensions:») for dimension in dimensions: print(dimension)
Блок в точке определяет исходный кортеж и выводит исходные размеры. В точке в переменной dimensions сохраняется новый кортеж, после чего в точке выводятся новые размеры. На этот раз Python не выдает сообщений об ошибках, потому что замена значения переменной является допустимой операцией: Original dimensions: 200 50 Modified dimensions: 400 100
По сравнению со списками структуры данных кортежей относительно просты. Используйте их для хранения наборов значений, которые не должны изменяться на протяжении жизненного цикла программы. УПРАЖНЕНИЯ 4-13 . Шведский стол: меню «шведского стола» в ресторане состоит всего из пяти пунктов . Придумайте пять простых блюд и сохраните их в кортеже . •
Используйте цикл for для вывода всех блюд, предлагаемых рестораном .
Попробуйте изменить один из элементов и убедитесь в том, что Python отказывается вносить изменения .
Ресторан изменяет меню, заменяя два элемента другими блюдами . Добавьте блок кода, который заменяет кортеж, и используйте цикл for для вывода каждого элемента обновленного меню .
Стиль программирования Итак, вы постепенно начинаете писать более длинные программы, и вам стоит познакомиться с некоторыми рекомендациями по стилевому оформлению кода. Не жалейте времени на то, чтобы ваш код читался как можно проще. Понятный код помогает следить за тем, что делает ваша программа, и упрощает изучение вашего кода другими разработчиками. Программисты Python выработали ряд соглашений по стилю, чтобы весь код имел хотя бы отдаленно похожую структуру. Научившись писать «чистый» код Python, вы сможете понять общую структуру кода Python, написанного любым другим программистом, соблюдающим те же рекомендации. Если вы рассчитываете когда-нибудь стать профессиональным программистом, привыкайте соблюдать эти рекомендации уже сейчас, чтобы выработать полезную привычку.
Рекомендации по стилю Когда кто-нибудь хочет внести изменения в язык Python, он пишет документ PEP (Python Enhancement Proposal). Одним из самых старых PEP является документ PEP 8 с рекомендациями по стилевому оформлению кода. PEP 8 имеет довольно
Глава 4 • Работа со списками
большую длину, но бульшая часть документа посвящена более сложным программным структурам, нежели те, которые встречались вам до настоящего момента. Руководство по стилю Python было написано с учетом того факта, что код читается чаще, чем пишется. Вы пишете свой код один раз, а потом начинаете читать его, когда переходите к отладке. При расширении функциональности программы вы снова тратите время на чтение своего кода. А когда вашим кодом начинают пользоваться другие программисты, они тоже читают его. Выбирая между написанием кода, который проще пишется, и кодом, который проще читается, программисты Python почти всегда рекомендуют второй вариант. Следующие советы помогут вам с самого начала писать чистый, понятный код.
Отступы PEP 8 рекомендует обозначать уровень отступа четырьмя пробелами. Использование четырех пробелов упрощает чтение программы и при этом оставляет достаточно места для нескольких уровней отступов в каждой строке. В программах форматирования текста для создания отступов часто используются табуляции вместо пробелов. Такой способ хорошо работает в текстовых процессорах, но интерпретатор Python приходит в замешательство, когда табуляции смешиваются с пробелами. В каждом текстовом редакторе имеется параметр конфигурации, который заменяет нажатие клавиши табуляции заданным количеством пробелов. Конечно, клавиша табуляции удобна, но вы должны проследить за тем, чтобы редактор вставлял в документ пробелы вместо табуляций. Смешение табуляций и пробелов в файле может создать проблемы, сильно затрудняющие диагностику. Если вы думаете, что в программе табуляции смешались с пробелами, помните, что в большинстве редакторов существует возможность преобразования всех табуляций в пробелы.
Длина строк Многие программисты Python рекомендуют ограничивать длину строк 80 символами. Исторически эта рекомендация появилась из-за того, что в большинстве компьютеров в одной строке терминального окна помещалось всего 79 символов. В настоящее время на экранах помещаются куда более длинные строки, но для применения стандартной длины строки в 79 символов существуют и другие причины. Профессиональные программисты часто открывают на одном экране сразу несколько файлов; стандартная длина строки позволяет видеть все строки в двух или трех файлах, открытых на экране одновременно. PEP 8 также рекомендует ограничивать комментарии 72 символами на строку, потому что некоторые служебные программы, автоматически генерирующие документацию в больших проектах, добавляют символы форматирования в начале каждой строки комментария. Рекомендации PEP 8 по выбору длины строки не являются незыблемыми, и некоторые программисты предпочитают ограничение в 99 символов. Пока вы учитесь,
длина строки в коде не так важна, но учтите, что при совместной работе в группах почти всегда соблюдаются рекомендации PEP 8. В большинстве редакторов можно установить визуальный ориентир (обычно вертикальную линию на экране), показывающий, где проходит граница. ПРИМЕЧАНИЕ В приложении Б показано, как настроить текстовый редактор, чтобы он всегда вставлял четыре пробела при нажатии клавиши табуляции и отображал вертикальную линию для соблюдения ограничения длины строки 79 символами .
Пустые строки Пустые строки применяются для визуальной группировки частей программы. Используйте пустые строки для структурирования файлов, но не злоупотребляйте ими. Примеры, приведенные в книге, помогут вам выработать нужный баланс. Например, если в программе пять строк кода создают список, а затем следующие три строки что-то делают с этим списком, два фрагмента уместно разделить пустой строкой. Тем не менее между ними не стоит вставлять три или четыре пустые строки. Пустые строки не влияют на работу кода, но отражаются на его удобочитаемости. Интерпретатор Python использует горизонтальные отступы для интерпретации смысла кода, но игнорирует вертикальные интервалы.
Другие рекомендации PEP 8 содержит много других рекомендаций по стилю, но эти рекомендации в основном относятся к программам более сложным, чем те, которые вы пишете на данный момент. По мере изучения более сложных возможностей Python я буду приводить соответствующие фрагменты рекомендаций PEP 8.
УПРАЖНЕНИЯ 4-14 . Просмотрите исходное руководство по стилю PEP 8 по адресу https://python .org/dev/ peps/pep-0008/ . Пока вы будете пользоваться им относительно редко, но просмотреть его будет интересно . 4-15 . Анализ кода: выберите три программы, написанные в этой главе, и измените каждую в соответствии с рекомендациями PEP 8 . •
Используйте четыре пробела для каждого уровня отступов . Настройте текстовый редактор так, чтобы он вставлял четыре пробела при каждом нажатии клавиши табуляции, если это не было сделано ранее (за инструкциями обращайтесь к приложению Б) .
Используйте менее 80 символов в каждой строке . Настройте редактор так, чтобы он отображал вертикальную черту в позиции 80-го символа .
Не злоупотребляйте пустыми строками в файлах программ .
Глава 4 • Работа со списками
Итоги В этой главе вы научились эффективно работать с элементами списка. Вы узнали, как работать со списком в цикле for, как Python использует отступы для определения структуры программы и как избежать некоторых типичных ошибок при использовании отступов. Вы научились создавать простые числовые списки, а также изучили некоторые операции с числовыми списками. Вы узнали, как создать срез списка для работы с подмножеством элементов и как правильно копировать списки с использованием среза. Глава завершается описанием кортежей, до определенной степени защищающих наборы значений, которые не должны изменяться, и рекомендациями по стилевому оформлению вашего кода (сложность которого со временем только возрастает) для упрощения его чтения. В главе 5 мы займемся обработкой различных условий с использованием команд if. Вы научитесь группировать относительно сложные наборы проверок для обработки именно той ситуации или информации, которая вам нужна. Также в этой главе будет рассматриваться использование команд if при переборе элементов списка для выполнения действий с элементами, выбранными по некоторому условию.
Программисту часто приходится проверять наборы условий и принимать решения в зависимости от этих условий. Команда if в языке Python позволяет проверить текущее состояние программы и выбрать дальнейшие действия в зависимости от результатов проверки. В этой главе вы научитесь писать условные проверки для любых интересующих вас условий. Мы начнем с простых команд if, а затем перейдем к более сложным сериям команд if для проверки комбинированных условий. Затем эта концепция будет применена к спискам; вы узнаете, как написать цикл, который выполняет с большинством элементов списка одну операцию, и о том, что для некоторых элементов с конкретными значениями применяется особая обработка.
Простой пример Следующий короткий пример показывает, как правильно организовать обработку специальных ситуаций с использованием if. Допустим, у вас имеется список машин, и вы хотите вывести название каждой машины. Названия большинства машин должны записываться с капитализацией (первая буква в верхнем регистре, остальные в нижнем). С другой стороны, значение ‘bmw’ должно записываться в верхнем регистре. Следующий код перебирает список названий машин и ищет в нем значение ‘bmw’. Для всех элементов, содержащих значение ‘bmw’, значение выводится в верхнем регистре: cars.py cars = [‘audi’, ‘bmw’, ‘subaru’, ‘toyota’] for car in cars: if car == ‘bmw’: print(car.upper()) else: print(car.title())
Цикл в этом примере сначала проверяет, содержит ли car значение ‘bmw’. Если проверка дает положительный результат, то значение выводится в верхнем регистре. Если car содержит все что угодно, кроме ‘bmw’, то при выводе значения применяется капитализация: Audi BMW
Глава 5 • Команды if Subaru Toyota
В этом примере объединяются несколько концепций, о которых вы узнаете в этой главе. Для начала рассмотрим основные конструкции, применяемые для проверки условий в программах.
Проверка условий В каждой команде if центральное место занимает выражение, результатом которого является логическая истина (True) или логическая ложь (False); это выражение называется условием. В зависимости от результата проверки Python решает, должен ли выполняться код в команде if. Если результат условия равен True, то Python выполняет код, следующий за командой if.
Проверка равенства Во многих условиях текущее значение переменной сравнивается с конкретным значением, интересующим вас. Простейшее условие проверяет, равно ли значение переменной конкретной величине: >>> car = ‘bmw’ >>> car == ‘bmw’ True
В строке переменной car присваивается значение ‘bmw’; операция выполняется одним знаком =, как вы уже неоднократно видели. Строка проверяет, равно ли значение car строке ‘bmw’; для проверки используется двойной знак равенства (==). Этот оператор возвращает True, если значения слева и справа от оператора равны; если же значения не совпадают, оператор возвращает False. В нашем примере значения совпадают, поэтому Python возвращает True. Если car принимает любое другое значение вместо ‘bmw’, проверка возвращает False: >>> car = ‘audi’ >>> car == ‘bmw’ False
Одиночный знак равенства выполняет операцию; код можно прочитать в форме «Присвоить car значение ‘audi’». С другой стороны, двойной знак равенства, как в строке , задает вопрос: «Значение car равно ‘bmw’?» Такое применение знаков равенства встречается во многих языках программирования.
Проверка равенства без учета регистра В языке Python проверка равенства выполняется с учетом регистра. Например, два значения с разным регистром символов равными не считаются:
>>> car = ‘Audi’ >>> car == ‘audi’ False
Если регистр символов важен, такое поведение приносит пользу. Но если проверка должна выполняться на уровне символов без учета регистра, преобразуйте значение переменной к нижнему регистру перед выполнением сравнения: >>> car = ‘Audi’ >>> car.lower() == ‘audi’ True
Условие возвращает True независимо от регистра символов ‘Audi’, потому что проверка теперь выполняется без учета регистра. Функция lower() не изменяет значения, которое изначально хранилось в car, так что сравнение не отражается на исходной переменной: >>> car = ‘Audi’ >>> car.lower() == ‘audi’ True >>> car ‘Audi’
В точке строка ‘Audi’ сохраняется в переменной car. В точке значение car приводится к нижнему регистру и сравнивается со значением строки ‘audi’, также записанным в нижнем регистре. Две строки совпадают, поэтому Python возвращает True. Вывод в точке показывает, что значение, хранящееся в car, не изменилось в результате проверки. Веб-сайты устанавливают определенные правила для данных, вводимых пользователями подобным образом. Например, сайт может использовать проверку условия, чтобы убедиться в том, что имя каждого пользователя уникально (а не совпадает с именем другого пользователя, отличаясь от него только регистром символов). Когда кто-то указывает новое имя пользователя, это имя преобразуется к нижнему регистру и сравнивается с версиями всех существующих имен в нижнем регистре. Во время такой проверки имя ‘John’ будет отклонено, если в системе уже используется любая разновидность ‘john’.
Проверка неравенства Если вы хотите проверить, что два значения различны, используйте комбинацию из восклицательного знака и знака равенства (!=). Восклицательный знак представляет отрицание, как и во многих языках программирования. Для знакомства с оператором неравенства мы воспользуемся другой командой if. В переменной хранится заказанное дополнение к пицце; если клиент не заказал анчоусы (anchovies), программа выводит сообщение: toppings.py requested_topping = ‘mushrooms’ if requested_topping != ‘anchovies’: print(«Hold the anchovies!»)
Глава 5 • Команды if
Строка сравнивает значение requested_topping со значением ‘anchovies’. Если эти два значения не равны, Python возвращает True и выполняет код после команды if. Если два значения равны, Python возвращает False и не выполняет код после команды if. Так как значение requested_topping отлично от ‘anchovies’, команда print будет выполнена: Hold the anchovies!
В большинстве условных выражений, которые вы будете использовать в программах, будет проверяться равенство, но иногда проверка неравенства оказывается более эффективной.
Сравнения чисел Проверка числовых значений достаточно прямолинейна. Например, следующий код проверяет, что переменная age равна 18: >>> age = 18 >>> age == 18 True
Также можно проверить условие неравенства двух чисел. Например, следующий код выводит сообщение, если значение переменной answer отлично от ожидаемого: magic_ number.py answer = 17 if answer != 42: print(«That is not the correct answer. Please try again!»)
Условие выполняется, потому что значение answer (17) не равно 42. Так как условие истинно, блок с отступом выполняется: That is not the correct answer. Please try again!
В условные команды также можно включать всевозможные математические сравнения: меньше, меньше или равно, больше, больше или равно: >>> age >>> age True >>> age True >>> age False >>> age False
Все эти математические сравнения могут использоваться в условиях if, что повышает точность формулировки интересующих вас условий.
Проверка нескольких условий Иногда требуется проверить несколько условий одновременно. Например, в некоторых случаях для выполнения действия бывает нужно, чтобы истинными были
сразу два условия; в других случаях достаточно, чтобы истинным было хотя бы одно из двух условий. Ключевые слова and и or помогут вам в подобных ситуациях. Использование and для проверки нескольких условий Чтобы проверить, что два условия истинны одновременно, объедините их ключевым словом and; если оба условия истинны, то и все выражение тоже истинно. Если хотя бы одно (или оба) условия ложны, то и результат всего выражения равен False. Например, чтобы убедиться в том, что каждому из двух людей больше 21 года, используйте следующую проверку: >>> age_0 >>> age_1 >>> age_0 False >>> age_1 >>> age_0 True
= 22 = 18 >= 21 and age_1 >= 21 = 22 >= 21 and age_1 >= 21
В точке определяются две переменные, age_0 и age_1. В точке программа проверяет, что оба значения равны 21 и более. Левое условие выполняется, а правое нет, поэтому все условное выражение дает результат False. В точке переменной age_1 присваивается значение 22. Теперь значение age_1 больше 21; обе проверки проходят, а все условное выражение дает истинный результат. Чтобы код лучше читался, отдельные условия можно заключить в круглые скобки, но это не обязательно. С круглыми скобками проверка может выглядеть так: (age_0 >= 21) and (age_1 >= 21)
Использование or для проверки нескольких условий Ключевое слово or тоже позволяет проверить несколько условий, но результат общей проверки является истинным в том случае, когда истинно хотя бы одно или оба условия. Ложный результат достигается только в том случае, если оба отдельных условия ложны. Вернемся к примеру с возрастом, но на этот раз проверим, что хотя бы одна из двух переменных больше 21: >>> age_0 >>> age_1 >>> age_0 True >>> age_0 >>> age_0 False
= 22 = 18 >= 21 or age_1 >= 21 = 18 >= 21 or age_1 >= 21
Как и в предыдущем случае, в точке определяются две переменные. Так как условие для age_0 в точке истинно, все выражение также дает истинный результат. Затем значение age_0 уменьшается до 18. При проверке оба условия оказываются ложными, и общий результат всего выражения тоже ложен.
Глава 5 • Команды if
Проверка вхождения значений в список Иногда бывает важно проверить, содержит ли список некоторое значение, прежде чем выполнять действие. Например, перед завершением регистрации нового пользователя на сайте можно проверить, существует ли его имя в списке имен действующих пользователей, или в картографическом проекте определить, входит ли передаваемое место в список известных мест на карте. Чтобы узнать, присутствует ли заданное значение в списке, воспользуйтесь ключевым словом in . Допустим, вы пишете программу для пиццерии. Вы создали список дополнений к пицце, заказанных клиентом, и хотите проверить, входят ли некоторые дополнения в этот список. >>> requested_toppings = [‘mushrooms’, ‘onions’, ‘pineapple’] >>> ‘mushrooms’ in requested_toppings True >>> ‘pepperoni’ in requested_toppings False
В точках и ключевое слово in приказывает Python проверить, входят ли значения ‘mushrooms’ и ‘pepperoni’ в список requested_toppings. Это весьма полезно, потому что вы можете создать список значений, критичных для вашей программы, а затем легко проверить, присутствует ли проверяемое значение в списке.
Проверка отсутствия значения в списке В других случаях программа должна убедиться в том, что значение не входит в список. Для этого используется ключевое слово not. Для примера рассмотрим список пользователей, которым запрещено писать комментарии на форуме. Прежде чем разрешить пользователю отправку комментария, можно проверить, не был ли пользователь включен в «черный список»: banned_users.py banned_users = [‘andrew’, ‘carolina’, ‘david’] user = ‘marie’ if user not in banned_users: print(user.title() + «, you can post a response if you wish.»)
Строка достаточно четко читается: если пользователь не входит в «черный список» banned_users, то Python возвращает True и выполняет строку с отступом. Пользователь ‘marie’ в этот список не входит, поэтому программа выводит соответствующее сообщение: Marie, you can post a response if you wish.
Логические выражения В процессе изучения программирования вы рано или поздно услышите термин «логическое выражение». По сути это всего лишь другое название для проверки
условия. Результат логического выражения равен True или False, как и результат условного выражения после его вычисления. Логические выражения часто используются для проверки некоторых условий — например, запущена ли компьютерная игра или разрешено ли пользователю редактирование некоторой информации на сайте: game_active = True can_edit = False
Логические выражения предоставляют эффективные средства для контроля состояния программы или определенного условия, играющего важную роль в вашей программе. УПРАЖНЕНИЯ 5-1 . Проверка условий: напишите последовательность условий . Выведите описание каждой проверки и ваш прогноз относительно ее результата . Код должен выглядеть примерно так: car = ‘subaru’ print(«Is car == ‘subaru’? I predict True.») print(car == ‘subaru’) print(«\nIs car == ‘audi’? I predict False.») print(car == ‘audi’) •
Внимательно просмотрите результаты . Убедитесь в том, что вы понимаете, почему результат каждой строки равен True или False .
Создайте как минимум 10 условий . Не менее 5 должны давать результат True, а не менее 5 других — результат False .
5-2 . Больше условий: количество условий не ограничивается 10 . Попробуйте написать другие условия и включить их в conditional_tests .py . Программа должна выдавать по крайней мере один истинный и один ложный результат для следующих видов проверок . •
Проверка равенства и неравенства строк .
Проверки с использованием функции lower() .
Числовые проверки равенства и неравенства, условий «больше», «меньше», «больше или равно», «меньше или равно» .
Проверки с ключевым словом and и or .
Проверка вхождения элемента в список .
Проверка отсутствия элемента в списке .
Команды if Когда вы поймете, как работают проверки условий, можно переходить к написанию команд if. Существуют несколько разновидностей команд if, и выбор варианта зависит от количества проверяемых условий. Примеры команд if уже встречались вам при обсуждении проверки условий, но сейчас эта тема будет рассмотрена более подробно.
Глава 5 • Команды if
Простые команды if Простейшая форма команды if состоит из одного условия и одного действия: if условие: действие
В первой строке размещается условие, а в блоке с отступом — практически любое действие. Если условие истинно, то Python выполняет код в блоке после команды if, а если ложно, этот код игнорируется. Допустим, имеется переменная, представляющая возраст человека. Следующий код проверяет, что этот возраст достаточен для голосования: voting.py age = 19 if age >= 18: print(«You are old enough to vote!»)
В точке Python проверяет, что значение переменной age больше или равно 18. В таком случае выполняется команда print в строке с отступом: You are old enough to vote!
Отступы в командах if играют ту же роль, что и в циклах for. Если условие истинно, то все строки с отступом после команды if выполняются, а если ложно — весь блок с отступом игнорируется. Блок команды if может содержать сколько угодно строк. Добавим еще одну строку для вывода дополнительного сообщения в том случае, если возраст достаточен для голосования: age = 19 if age >= 18: print(«You are old enough to vote!») print(«Have you registered to vote yet?»)
Условие выполняется, а обе команды print снабжены отступом, поэтому выводятся оба сообщения: You are old enough to vote! Have you registered to vote yet?
Если значение age меньше 18, программа ничего не выводит.
Команды if-else Часто в программе необходимо выполнить одно действие в том случае, если условие истинно, и другое действие, если оно ложно. С синтаксисом if-else это возможно. Блок if-else в целом похож на команду if, но секция else определяет действие или набор действий, выполняемых при неудачной проверке.
В следующем примере выводится то же сообщение, которое выводилось ранее, если возраст достаточен для голосования, но на этот раз при любом другом возрасте выводится другое сообщение: age = 17 if age >= 18: print(«You are old enough to vote!») print(«Have you registered to vote yet?») else: print(«Sorry, you are too young to vote.») print(«Please register to vote as soon as you turn 18!»)
Если условие истинно, то выполняется первый блок с командами print. Если же условие ложно, выполняется блок else в точке . Так как значение age на этот раз меньше 18, условие оказывается ложным, и выполняется код в блоке else: Sorry, you are too young to vote. Please register to vote as soon as you turn 18!
Этот код работает, потому что существуют всего две возможные ситуации: возраст либо достаточен для голосования, либо недостаточен. Структура if -else хорошо подходит для тех ситуаций, в которых Python всегда выполняет только одно из двух возможных действий. В подобных простых цепочках if-else всегда выполняется одно из двух возможных действий.
Цепочки if-elif-else Нередко в программе требуется проверять более двух возможных ситуаций; для таких ситуаций в Python предусмотрен синтаксис if-elif-else. Python выполняет только один блок в цепочке if-elif-else. Все условия проверяются по порядку до тех пор, пока одно из них не даст истинный результат. Далее выполняется код, следующий за этим условием, а все остальные проверки Python пропускает. Во многих реальных ситуациях существуют более двух возможных результатов. Представьте себе парк аттракционов, который взимает разную плату за вход для разных возрастных групп: Для посетителей младше 4 лет вход бесплатный. Для посетителей от 4 до 18 лет билет стоит $5. Для посетителей от 18 лет и старше билет стоит $10.
Как использовать команду if для определения платы за вход? Следующий код определяет, к какой возрастной категории относится посетитель, и выводит сообщение со стоимостью билета: amusement_park.py age = 12 if age = 65: price = 5 print(«Your admission cost is $» + str(price) + «.»)
Блок elif в точке назначает цену $5, если возраст посетителя равен 65 и выше; смысл такого кода более понятен, чем у обобщенного блока else. С таким изменением выполнение каждого блока возможно только при истинности конкретного условия. Блок else «универсален»: он обрабатывает все условия, не подходящие ни под одну конкретную проверку if или elif, причем в эту категорию иногда могут попасть недействительные или даже вредоносные данные. Если у вас имеется завершающее конкретное условие, лучше используйте завершающий блок elif и опустите блок else. В этом случае вы можете быть уверены в том, что ваш код будет выполняться только в правильных условиях.
Глава 5 • Команды if
Проверка нескольких условий Цепочки if-elif-else эффективны, но они подходят только в том случае, если истинным должно быть только одно условие. Как только Python находит выполняющееся условие, все остальные проверки пропускаются. Такое поведение достаточно эффективно, потому что оно позволяет проверить одно конкретное условие. Однако иногда бывает важно проверить все условия, представляющие интерес. В таких случаях следует применять серии простых команд if без блоков elif или else. Такое решение уместно, когда истинными могут быть сразу несколько условий и вы хотите отреагировать на все истинные условия. Вернемся к примеру с пиццей. Если кто-то закажет пиццу с двумя дополнениями, программа должна обработать оба дополнения: toppings.py requested_toppings = [‘mushrooms’, ‘extra cheese’] if ‘mushrooms’ in print(«Adding if ‘pepperoni’ in print(«Adding if ‘extra cheese’ print(«Adding
requested_toppings: mushrooms.») requested_toppings: pepperoni.») in requested_toppings: extra cheese.»)
print(«\nFinished making your pizza!»)
Обработка начинается в точке со списка, содержащего заказанные дополнения. Команды if в точке и проверяют, включает ли заказ конкретные дополнения — грибы и пепперони, и если включает — выводят подтверждающее сообщение. Проверка в точке реализована простой командой if, а не elif или else, поэтому условие будет проверяться независимо от того, было ли предыдущее условие истинным или ложным. Код в точке проверяет, была ли заказана дополнительная порция сыра, независимо от результата первых двух проверок. Эти три независимых условия проверяются при каждом выполнении программы. Так как в этом коде проверяются все возможные варианты дополнений, в заказ будут включены два дополнения из трех: Adding mushrooms. Adding extra cheese. Finished making your pizza!
Если бы в программе использовался блок if-elif-else, код работал бы неправильно, потому что он прерывал работу после обнаружения первого истинного условия. Вот как это выглядело бы: requested_toppings = [‘mushrooms’, ‘extra cheese’] if ‘mushrooms’ in requested_toppings: print(«Adding mushrooms.») elif ‘pepperoni’ in requested_toppings: print(«Adding pepperoni.»)
elif ‘extra cheese’ in requested_toppings: print(«Adding extra cheese.») print(«\nFinished making your pizza!»)
Первое же проверяемое условие (для ‘mushrooms’) оказывается истинным. Однако значения ‘extra cheese’ и ‘pepperoni’ после этого не проверяются, потому что в цепочках if-elif-else после обнаружения первого истинного условия все остальные условия пропускаются. В результате в пиццу будет включено только первое из заказанных дополнений: Adding mushrooms. Finished making your pizza!
Итак, если вы хотите, чтобы в программе выполнялся только один блок кода, — используйте цепочку if-elif-else. Если же выполняться должны несколько блоков, используйте серию независимых команд if. УПРАЖНЕНИЯ 5-3 . Цвета 1: представьте, что в вашей компьютерной игре только что был подбит корабль пришельцев . Создайте переменную с именем alien_color и присвойте ей значение ‘green’, ‘yellow’ или ‘red’ . •
Напишите команду if для проверки того, что переменная содержит значение ‘green’ . Если условие истинно, выведите сообщение о том, что игрок только что заработал 5 очков .
Напишите одну версию программы, в которой условие if выполняется, и другую версию, в которой оно не выполняется . (Во второй версии никакое сообщение выводиться не должно .)
5-4 . Цвета 2: выберите цвет, как это было сделано в упражнении 5-3, и напишите цепочку if-else . •
Напишите команду if для проверки того, что переменная содержит значение ‘green’ . Если условие истинно, выведите сообщение о том, что игрок только что заработал 5 очков .
Если переменная содержит любое другое значение, выведите сообщение о том, что игрок только что заработал 10 очков .
Напишите одну версию программы, в которой выполняется блок if, и другую версию, в которой выполняется блок else .
5-5 . Цвета 3: преобразуйте цепочку if-else из упражнения 5-4 в цепочку if-elif-else . •
Если переменная содержит значение ‘green’, выведите сообщение о том, что игрок только что заработал 5 очков .
Если переменная содержит значение ‘yellow’, выведите сообщение о том, что игрок только что заработал 10 очков .
Если переменная содержит значение ‘red’, выведите сообщение о том, что игрок только что заработал 15 очков .
Напишите три версии программы и проследите за тем, чтобы для каждого цвета пришельца выводилось соответствующее сообщение .
5-6 . Периоды жизни: напишите цепочку if-elif-else для определения периода жизни человека . Присвойте значение переменной age, а затем выведите сообщение . •
Если значение меньше 2 — младенец .
Глава 5 • Команды if •
Если значение больше или равно 2, но меньше 4 — малыш .
Если значение больше или равно 4, но меньше 13 — ребенок .
Если значение больше или равно 13, но меньше 20 — подросток .
Если значение больше или равно 20, но меньше 65 — взрослый .
Если значение больше или равно 65 — пожилой человек .
5-7 . Любимый фрукт: составьте список своих любимых фруктов . Напишите серию независимых команд if для проверки того, присутствуют ли некоторые фрукты в списке . •
Создайте список трех своих любимых фруктов и назовите его favorite_fruits .
Напишите пять команд if . Каждая команда должна проверять, входит ли определенный тип фрукта в список . Если фрукт входит в список, блок if должен выводить сообщение вида «You really like bananas!» .
Использование команд if со списками Объединение команд if со списками открывает ряд интересных возможностей. Например, вы можете отслеживать специальные значения, для которых необходима особая обработка по сравнению с другими значениями в списке, или эффективно управлять изменяющимися условиями — например, наличием некоторых блюд в ресторане. Также объединение команд if со списками помогает продемонстрировать, что ваш код корректно работает во всех возможных ситуациях.
Проверка специальных значений Эта глава началась с простого примера, показывающего, как обрабатывать особые значения (такие, как ‘bmw’), которые должны выводиться в другом формате по сравнению с другими значениями в списке. Теперь, когда вы лучше разбираетесь в проверках условий и командах if, давайте повнимательнее рассмотрим процесс поиска и обработки особых значений в списке. Вернемся к примеру с пиццерией. Программа выводит сообщение каждый раз, когда пицца снабжается дополнением в процессе приготовления. Код этого действия можно записать чрезвычайно эффективно: нужно создать список дополнений, заказанных клиентом, и использовать цикл для перебора всех заказанных дополнений: toppings.py requested_toppings = [‘mushrooms’, ‘green peppers’, ‘extra cheese’] for requested_topping in requested_toppings: print(«Adding » + requested_topping + «.») print(«\nFinished making your pizza!»)
Вывод достаточно тривиален, поэтому код сводится к простому циклу for: Adding mushrooms. Adding green peppers. Adding extra cheese. Finished making your pizza!
Использование команд if со списками
А если в пиццерии вдруг кончится зеленый перец? Команда if в цикле for может правильно обработать эту ситуацию: requested_toppings = [‘mushrooms’, ‘green peppers’, ‘extra cheese’] for requested_topping in requested_toppings: if requested_topping == ‘green peppers’: print(«Sorry, we are out of green peppers right now.») else: print(«Adding » + requested_topping + «.») print(«\nFinished making your pizza!»)
На этот раз программа проверяет каждый заказанный элемент перед добавлением его к пицце. В точке программа проверяет, заказал ли клиент зеленый перец, и если заказал — выводит сообщение о том, что этого дополнения нет. Блок else в точке гарантирует, что все другие дополнения будут включены в заказ. Из выходных данных видно, что все заказанные дополнения обрабатываются правильно: Adding mushrooms. Sorry, we are out of green peppers right now. Adding extra cheese. Finished making your pizza!
Проверка наличия элементов в списке Для всех списков, с которыми мы работали до сих пор, действовало одно простое предположение: мы считали, что в каждом списке есть хотя бы один элемент. Скоро мы предоставим пользователю возможность вводить информацию, хранящуюся в списке, поэтому мы уже не можем предполагать, что при каждом выполнении цикла в списке есть хотя бы один элемент. В такой ситуации перед выполнением цикла for будет полезно проверить, есть ли в списке хотя бы один элемент. Проверим, есть ли элементы в списке заказанных дополнений, перед изготовлением пиццы. Если список пуст, программа предлагает пользователю подтвердить, что он хочет базовую пиццу без дополнений. Если список не пуст, пицца готовится так же, как в предыдущих примерах: requested_toppings = [] if requested_toppings: for requested_topping in requested_toppings: print(«Adding » + requested_topping + «.») print(«\nFinished making your pizza!») else: print(«Are you sure you want a plain pizza?»)
На этот раз мы начинаем с пустого списка заказанных дополнений в точке . Вместо того чтобы сразу переходить к циклу for, программа выполняет проверку
Глава 5 • Команды if
в точке . Когда имя списка используется в условии if, Python возвращает True, если список содержит хотя бы один элемент; если список пуст, возвращается значение False. Если requested_toppings проходит проверку условия, выполняется тот же цикл for, который мы использовали в предыдущем примере. Если же условие ложно, то программа выводит сообщение, которое предлагает клиенту подтвердить, действительно ли он хочет получить базовую пиццу без дополнений . В данном примере список пуст, поэтому выводится сообщение: Are you sure you want a plain pizza?
Если в списке есть хотя бы один элемент, в выходные данные включается каждое заказанное дополнение.
Множественные списки Посетители способны заказать что угодно, особенно когда речь заходит о дополнениях к пицце. Что если клиент захочет положить на пиццу картофель фри? Списки и команды if позволят вам убедиться в том, что входные данные имеют смысл, прежде чем обрабатывать их. Давайте проверим наличие нестандартных дополнений перед тем, как готовить пиццу. В следующем примере определяются два списка. Первый список содержит перечень доступных дополнений, а второй — список дополнений, заказанных клиентом. На этот раз каждый элемент из requested_toppings проверяется по списку доступных дополнений перед добавлением в пиццу: available_toppings = [‘mushrooms’, ‘olives’, ‘green peppers’, ‘pepperoni’, ‘pineapple’, ‘extra cheese’] requested_toppings = [‘mushrooms’, ‘french fries’, ‘extra cheese’] for requested_topping in requested_toppings: if requested_topping in available_toppings: print(«Adding » + requested_topping + «.») else: print(«Sorry, we don’t have » + requested_topping + «.») print(«\nFinished making your pizza!»)
В точке определяется список доступных дополнений к пицце. Стоит заметить, что если в пиццерии используется постоянный ассортимент дополнений, этот список можно реализовать в виде кортежа. В точке создается список дополнений, заказанных клиентом. Обратите внимание на необычный заказ ‘french fries’. В точке программа перебирает список заказанных дополнений. Внутри цикла программа сначала проверяет, что каждое заказанное дополнение присутствует в списке доступных дополнений . Если дополнение доступно, оно добавляется в пиццу. Если заказанное дополнение не входит в список, выполняется блок else . Блок else выводит сообщение о том, что дополнение недоступно. С этим синтаксисом программа выдает четкий, содержательный вывод:
Оформление команд if
Adding mushrooms. Sorry, we don’t have french fries. Adding extra cheese. Finished making your pizza!
Всего в нескольких строках кода нам удалось эффективно решить вполне реальную проблему! УПРАЖНЕНИЯ 5-8 . Hello Admin: создайте список из пяти и более имен пользователей, включающий имя ‘admin’ . Представьте, что вы пишете код, который выводит приветственное сообщение для каждого пользователя после его входа на сайт . Переберите элементы списка и выведите сообщение для каждого пользователя . •
Для пользователя с именем ‘admin’ выведите особое сообщение — например: «Hello admin, would you like to see a status report?»
В остальных случаях выводите универсальное приветствие — например: «Hello Eric, thank you for logging in again» .
5-9 . Без пользователей: добавьте в hello_admin .py команду if, которая проверит, что список пользователей не пуст . •
Если список пуст, выведите сообщение: «We need to find some users!»
Удалите из списка все имена пользователей и убедитесь в том, что программа выводит правильное сообщение .
5-10 . Проверка имен пользователей: выполните следующие действия для создания программы, моделирующей проверку уникальности имен пользователей . •
Создайте список current_users, содержащий пять и более имен пользователей .
Создайте другой список new_users, содержащий пять и более имен пользователей . Убедитесь в том, что одно или два новых имени также присутствуют в списке current_ users .
Переберите список new_users и для каждого имени в этом списке проверьте, было ли оно использовано ранее . Если имя уже использовалось, выведите сообщение о том, что пользователь должен выбрать новое имя . Если имя не использовалось, выведите сообщение о его доступности .
Проследите за тем, чтобы сравнение выполнялось без учета регистра символов . Если имя ‘John’ уже используется, в регистрации имени ‘JOHN’ следует отказать .
5-11 . Порядковые числительные: порядковые числительные в английском языке заканчиваются суффиксом th (кроме 1st, 2nd и 3rd) . •
Сохраните числа от 1 до 9 в списке .
Переберите элементы списка .
Используйте цепочку if-elif-else в цикле для вывода правильного окончания числительного для каждого числа . Программа должна выводить числительные «1st 2nd 3rd 4th 5th 6th 7th 8th 9th», причем каждый результат должен располагаться в отдельной строке .
Оформление команд if Во всех примерах этой главы применялись правила стилевого оформления. В PEP 8 приведена только одна рекомендация, касающаяся проверки условий: заключать
Глава 5 • Команды if
операторы сравнения (такие, как ==, >=, = self.odometer_reading: self.odometer_reading = mileage else: print(«You can’t roll back an odometer!»)
Теперь update_odometer() проверяет новое значение перед изменением атрибута. Если новое значение mileage больше или равно текущего, self.odometer_reading, показания одометра можно обновить новым значением . Если же новое значение меньше текущего, вы получите предупреждение о недопустимости обратной подкрутки . Изменение значения атрибута с приращением Иногда значение атрибута требуется изменить с заданным приращением (вместо того чтобы присваивать атрибуту произвольное новое значение). Допустим,
Работа с классами и экземплярами
вы купили подержанную машину и проехали на ней 100 миль. Следующий метод получает величину приращения и прибавляет ее к текущим показаниям одометра: class Car(): . def update_odometer(self, mileage): —snip-
def increment_odometer(self, miles): «»»Увеличивает показания одометра с заданным приращением.»»» self.odometer_reading += miles
my_used_car = Car(‘subaru’, ‘outback’, 2013) print(my_used_car.get_descriptive_name()) my_used_car.update_odometer(23500) my_used_car.read_odometer() my_used_car.increment_odometer(100) my_used_car.read_odometer()
Новый метод increment_odometer() в точке получает расстояние в милях и прибавляет его к self.odometer_reading . В точке создается экземпляр my_used_car. Мы инициализируем показания его одометра значением 23 500; для этого вызывается метод update_odometer(), которому передается значение 23500 . В точке вызывается метод increment_odometer(), которому передается значение 100, чтобы увеличить показания одометра на 100 миль, пройденные с момента покупки: 2013 Subaru Outback This car has 23500 miles on it. This car has 23600 miles on it.
При желании можно легко усовершенствовать этот метод, чтобы он отклонял отрицательные приращения; тем самым вы предотвратите обратную подкрутку одометра. ПРИМЕЧАНИЕ Подобные методы управляют обновлением внутренних значений экземпляров (таких, как показания одометра), однако любой пользователь, имеющий доступ к программному коду, сможет напрямую задать атрибуту любое значение . Эффективная схема безопасности должна уделять особое внимание таким подробностям, не ограничиваясь простейшими проверками . УПРАЖНЕНИЯ 9-4 . Посетители: начните с программы из упражнения 9-1 (с . 165) . Добавьте атрибут number_served со значением по умолчанию 0; он представляет количество обслуженных посетителей . Создайте экземпляр с именем restaurant . Выведите значение number_served, потом измените и выведите снова . Добавьте метод с именем set_number_served(), позволяющий задать количество обслуженных посетителей . Вызовите метод с новым числом, снова выведите значение .
Глава 9 • Классы
Добавьте метод с именем increment_number_served(), который увеличивает количество обслуженных посетителей на заданную величину . Вызовите этот метод с любым числом, которое могло бы представлять количество обслуженных клиентов — скажем, за один день . 9-5 . Попытки входа: добавьте атрибут login_attempts в класс User из упражнения 9-3 (с . 165) . Напишите метод increment_login_attempts(), увеличивающий значение login_ attempts на 1 . Напишите другой метод с именем reset_login_attempts(), обнуляющий значение login_attempts . Создайте экземпляр класса User и вызовите increment_login_attempts() несколько раз . Выведите значение login_attempts, чтобы убедиться в том, что значение было изменено правильно, а затем вызовите reset_login_attempts() . Снова выведите login_attempts и убедитесь в том, что значение обнулилось .
Наследование Работа над новым классом не обязана начинаться с нуля. Если класс, который вы пишете, представляет собой специализированную версию ранее написанного класса, вы можете воспользоваться наследованием. Один класс, наследующий от другого, автоматически получает все атрибуты и методы первого класса. Исходный класс называется родителем, а новый класс — потомком. Класс-потомок наследует атрибуты и методы родителя, но при этом также может определять собственные атрибуты и методы.
Метод __init__() класса-потомка Первое, что делает Python при создании экземпляра класса-потомка, — присваивает значения всем атрибутам класса-родителя. Для этого методу __init__() класса-потомка необходима помощь со стороны родителя. Например, попробуем построить модель электромобиля. Электромобиль представляет собой специализированную разновидность автомобиля, поэтому новый класс ElectricCar можно создать на базе класса Car, написанного ранее. Тогда нам останется добавить в него код атрибутов и поведения, относящегося только к электромобилям. Начнем с создания простой версии класса ElectricCar, который делает все, что делает класс Car: electric_car.py class Car(): «»»Простая модель автомобиля.»»» def __init__(self, make, model, year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ‘ ‘ + self.make + ‘ ‘ + self.model return long_name.title()
def read_odometer(self): print(«This car has » + str(self.odometer_reading) + » miles on it.») def update_odometer(self, mileage): if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(«You can’t roll back an odometer!») def increment_odometer(self, miles): self.odometer_reading += miles class ElectricCar(Car): «»»Представляет аспекты машины, специфические для электромобилей.»»» def __init__(self, make, model, year): «»»Инициализирует атрибуты класса-родителя.»»» super().__init__(make, model, year) my_tesla = ElectricCar(‘tesla’, ‘model s’, 2016) print(my_tesla.get_descriptive_name())
В точке строится экземпляр Car. При создании класса-потомка класс-родитель должен быть частью текущего файла, а его определение должно предшествовать определению класса-потомка в файле. В точке определяется класс-потомок ElectricCar. В определении потомка имя класса-родителя заключается в круглые скобки. Метод __init__() в точке получает информацию, необходимую для создания экземпляра Car. Функция super() в строке — специальная функция, которая помогает Python связать потомка с родителем. Эта строка приказывает Python вызвать метод __init__() класса, являющегося родителем ElectricCar, в результате чего экземпляр ElectricCar получает все атрибуты класса-родителя. Имя super происходит из распространенной терминологии: класс-родитель называется суперклассом, а класс-потомок — субклассом. Чтобы проверить, правильно ли сработало наследование, попробуем создать электромобиль с такой же информацией, которая передается при создании обычного экземпляра Car. В точке мы создаем экземпляр класса ElectricCar и сохраняем его в my_tesla. Эта строка вызывает метод __init__(), определенный в ElectricCar, который в свою очередь приказывает Python вызвать метод __init__(), определенный в классе-родителе Car. При вызове передаются аргументы ‘tesla’, ‘model s’ и 2016. Кроме __init__() класс еще не содержит никаких атрибутов или методов, специфических для электромобилей. Пока мы просто убеждаемся в том, что класс электромобиля содержит все поведение, присущее классу автомобиля: 2016 Tesla Model S
Экземпляр ElectricCar работает так же, как экземпляр Car; можно переходить к определению атрибутов и методов, специфических для электромобилей.
Глава 9 • Классы
Наследование в Python 2 .7 В Python 2.7 наследование реализовано немного иначе. Класс ElectricCar будет выглядеть примерно так: class Car(object): def __init__(self, make, model, year): . class ElectricCar(Car): def __init__(self, make, model, year): super(ElectricCar, self).__init__(make, model, year) .
Функция super() должна получать два аргумента: ссылку на класс-потомок и объект self. Эти аргументы необходимы для того, чтобы Python мог правильно связать родителя с потомком. Если вы используете наследование в Python 2.7, убедитесь в том, что родитель также определяется с синтаксисом object.
Определение атрибутов и методов класса-потомка После создания класса-потомка, наследующего от класса-родителя, можно переходить к добавлению новых атрибутов и методов, необходимых для того, чтобы потомок отличался от родителя. Добавим атрибут, специфический для электромобилей (например, мощность аккумулятора), и метод для вывода информации об этом атрибуте: class Car(): . class ElectricCar(Car): «»»Представляет аспекты машины, специфические для электромобилей.»»» def __init__(self, make, model, year): «»» Инициализирует атрибуты класса-родителя. Затем инициализирует атрибуты, специфические для электромобиля. «»» super().__init__(make, model, year) self.battery_size = 70
def describe_battery(self): «»»Выводит информацию о мощности аккумулятора.»»» print(«This car has a » + str(self.battery_size) + «-kWh battery.»)
my_tesla = ElectricCar(‘tesla’, ‘model s’, 2016) print(my_tesla.get_descriptive_name()) my_tesla.describe_battery()
В точке добавляется новый атрибут self.battery_size, которому присваивается исходное значение — скажем, 70. Этот атрибут будет присутствовать во всех экземплярах, созданных на основе класса ElectricCar (но не во всяком экземпляре Car). Также добавляется метод с именем describe_battery(), который выводит инфор-
мацию об аккумуляторе в точке . При вызове этого метода выводится описание, которое явно относится только к электромобилям: 2016 Tesla Model S This car has a 70-kWh battery.
Возможности специализации класса ElectricCar беспредельны. Вы можете добавить сколько угодно атрибутов и методов, чтобы моделировать электромобиль с любой нужной точностью. Атрибуты или методы, которые могут принадлежать любой машине (а не только электромобилю), должны добавляться в класс Car вместо ElectricCar. Тогда эта информация будет доступна всем пользователям класса Car, а класс ElectricCar будет содержать только код информации и поведения, специфических для электромобилей.
Переопределение методов класса-родителя Любой метод родительского класса, который в моделируемой ситуации делает не то, что нужно, можно переопределить. Для этого в классе-потомке определяется метод с тем же именем, что и у метода класса-родителя. Python игнорирует метод родителя и обращает внимание только на метод, определенный в потомке. Допустим, в классе Car имеется метод fill_gas_tank(). Для электромобилей заправка бензином бессмысленна, поэтому этот метод логично переопределить. Например, это можно сделать так: def ElectricCar(Car): . def fill_gas_tank(): «»»У электромобилей нет бензобака.»»» print(«This car doesn’t need a gas tank!»)
И если кто-то попытается вызвать метод fill_gas_tank() для электромобиля, Python игнорирует метод fill_gas_tank() класса Car и выполнит вместо него этот код. С применением наследования потомок сохраняет те аспекты родителя, которые вам нужны, и переопределяет все ненужное.
Экземпляры как атрибуты При моделировании явлений реального мира в программах классы нередко дополняются все большим количеством подробностей. Списки атрибутов и методов растут, и через какое-то время файлы становятся длинными и громоздкими. В такой ситуации часть одного класса нередко можно записать в виде отдельного класса. Большой код разбивается на меньшие классы, которые работают во взаимодействии друг с другом. Например, при дальнейшей доработке класса ElectricCar может оказаться, что в нем появилось слишком много атрибутов и методов, относящихся к аккумулятору. В таком случае можно остановиться и переместить все эти атрибуты и методы в отдельный класс с именем Battery. Затем экземпляр Battery становится атрибутом класса ElectricCar:
Глава 9 • Классы
class Car(): . class Battery(): «»»Простая модель аккумулятора электромобиля.»»»
def __init__(self, battery_size=70): «»»Инициализирует атрибуты аккумулятора.»»» self.battery_size = battery_size def describe_battery(self): «»»Выводит информацию о мощности аккумулятора.»»» print(«This car has a » + str(self.battery_size) + «-kWh battery.»)
class ElectricCar(Car): «»»Представляет аспекты машины, специфические для электромобилей.»»» def __init__(self, make, model, year): «»» Инициализирует атрибуты класса-родителя. Затем инициализирует атрибуты, специфические для электромобиля. «»» super().__init__(make, model, year) self.battery = Battery() my_tesla = ElectricCar(‘tesla’, ‘model s’, 2016) print(my_tesla.get_descriptive_name()) my_tesla.battery.describe_battery()
В точке определяется новый класс с именем Battery, который не наследует ни от одного из других классов. Метод __init__() в точке получает один параметр battery_size, кроме self. Если значение не предоставлено, этот необязательный параметр задает battery_size значение 70. Метод describe_battery() также перемещен в этот класс . Затем в класс ElectricCar добавляется атрибут с именем self.battery . Эта строка приказывает Python создать новый экземпляр Battery (со значением battery_ size по умолчанию, равным 70, потому что значение не задано) и сохранить его в атрибуте self.battery. Это будет происходить при каждом вызове __init__(); теперь любой экземпляр ElectricCar будет иметь автоматически создаваемый экземпляр Battery. Программа создает экземпляр электромобиля и сохраняет его в переменной my_ tesla. Когда потребуется вывести описание аккумулятора, необходимо обратиться к атрибуту battery: my_tesla.battery.describe_battery()
Эта строка приказывает Python обратиться к экземпляру my_tesla , найти его атрибут battery и вызвать метод describe_battery(), связанный с экземпляром Battery из атрибута. Результат выглядит так же, как и в предыдущей версии: 2016 Tesla Model S This car has a 70-kWh battery.
Казалось бы, новый вариант требует большой дополнительной работы, но теперь аккумулятор можно моделировать с любой степенью детализации без загромождения класса ElectricCar. Добавим в Battery еще один метод, который выводит запас хода на основании мощности аккумулятора: class Car(): . class Battery(): .
def get_range(self): «»»Выводит приблизительный запас хода для аккумулятора.»»» if self.battery_size == 70: range = 240 elif self.battery_size == 85: range = 270 message = «This car can go approximately » + str(range) message += » miles on a full charge.» print(message)
class ElectricCar(Car): . my_tesla = ElectricCar(‘tesla’, ‘model s’, 2016) print(my_tesla.get_descriptive_name()) my_tesla.battery.describe_battery() my_tesla.battery.get_range()
Новый метод get_range() в точке проводит простой анализ. Если мощность равна 70, то get_range() устанавливает запас хода 240 миль, а при мощности 85 kWh запас хода равен 270 милям. Затем программа выводит это значение. Когда вы захотите использовать этот метод, его придется вызывать через атрибут battery в точке . Результат сообщает запас хода машины в зависимости от мощности аккумулятора: 2016 Tesla Model S This car has a 70-kWh battery. This car can go approximately 240 miles on a full charge.
Моделирование объектов реального мира Занявшись моделированием более сложных объектов — таких, как электромобили, — вы столкнетесь со множеством интересных вопросов. Является ли запас хода электромобиля свойством аккумулятора или машины? Если вы описываете только одну машину, вероятно, можно связать метод get_range() с классом Battery. Но, если моделируется целая линейка машин от производителя, вероятно, метод get_range() правильнее будет переместить в класс ElectricCar. Метод get_range() по-прежнему будет проверять мощность аккумулятора перед определением запаса хода, но он будет сообщать запас хода для той машины, с которой он связан.
Глава 9 • Классы
Также возможно связать метод get_range() с аккумулятором, но передавать ему параметр (например, car_model). Метод get_range() будет определять запас хода на основании мощности аккумулятора и модели автомобиля. Если вы начнете ломать голову над такими вопросами, это означает, что вы мыслите на более высоком логическом уровне, не ограничиваясь уровнем синтаксиса. Вы думаете уже не о Python, а о том, как представить реальный мир в коде. И, достигнув этой точки, вы поймете, что однозначно правильного или неправильного подхода к моделированию реальных ситуаций часто не существует. Некоторые методы эффективнее других, но для того, чтобы найти наиболее эффективную реализацию, необходим практический опыт. Если ваш код работает именно так, как вы хотели, — значит, у вас все получается! Не огорчайтесь, если окажется, что вы по несколько раз переписываете свои классы для разных решений. На пути к написанию точного, эффективного кода все программисты проходят через этот процесс. УПРАЖНЕНИЯ 9-6 . Киоск с мороженым: киоск с мороженым — особая разновидность ресторана . Напишите класс IceCreamStand, наследующий от класса Restaurant из упражнения 9-1 (с . 165) или упражнения 9-4 (с . 169) . Подойдет любая версия класса; просто выберите ту, которая вам больше нравится . Добавьте атрибут с именем flavors для хранения списка сортов мороженого . Напишите метод, который выводит этот список . Создайте экземпляр IceCreamStand и вызовите этот метод . 9-7 . Администратор: администратор — особая разновидность пользователя . Напишите класс с именем Admin, наследующий от класса User из упражнения 9-3 (с . 165) или упражнения 9-5 (с . 170) . Добавьте атрибут privileges для хранения списка строк вида «разрешено добавлять сообщения», «разрешено удалять пользователей», «разрешено банить пользователей» и т . д . Напишите метод show_privileges() для вывода набора привилегий администратора . Создайте экземпляр Admin и вызовите свой метод . 9-8 . Привилегии: напишите класс Privileges . Класс должен содержать всего один атрибут privileges со списком строк из упражнения 9-7 . Переместите метод show_privileges() в этот класс . Создайте экземпляр Privileges как атрибут класса Admin . Создайте новый экземпляр Admin и используйте свой метод для вывода списка привилегий . 9-9 . Обновление аккумулятора: используйте окончательную версию программы electric_car .py из этого раздела . Добавьте в класс Battery метод с именем upgrade_battery() . Этот метод должен проверять размер аккумулятора и устанавливать мощность равной 85, если она имеет другое значение . Создайте экземпляр электромобиля с аккумулятором по умолчанию, вызовите get_range(), а затем вызовите get_range() во второй раз после вызова upgrade_battery() . Убедитесь в том, что запас хода увеличился .
Импортирование классов С добавлением новой функциональности в классы файлы могут стать слишком длинными, даже при правильном использовании наследования. В соответствии с общей философией Python файлы не должны загромождаться лишними подробностями. Для этого Python позволяет хранить классы в модулях и импортировать нужные классы в основную программу.
Импортирование одного класса Начнем с создания модуля, содержащего только класс Car. При этом возникает неочевидный конфликт имен: в этой главе уже был создан файл с именем car .py, но этот модуль тоже должен называться car .py, потому что в нем содержится код класса Car. Мы решим эту проблему, сохранив класс Car в модуле с именем car .py , заменив им файл car .py , который использовался ранее. В дальнейшем любой программе, использующей этот модуль, придется присвоить более конкретное имя файла — например, my_car .py. Ниже приведен файл car .py с кодом класса Car: car.py «»»Класс для представления автомобиля.»»» class Car(): «»»Простая модель автомобиля.»»» def __init__(self, make, model, year): «»»Инициализирует атрибуты описания автомобиля.»»» self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): «»»Возвращает аккуратно отформатированное описание.»»» long_name = str(self.year) + ‘ ‘ + self.make + ‘ ‘ + self.model return long_name.title() def read_odometer(self): «»»Выводит пробег машины в милях.»»» print(«This car has » + str(self.odometer_reading) + » miles on it.») def update_odometer(self, mileage): «»» Устанавливает на одометре заданное значение. При попытке обратной подкрутки изменение отклоняется. «»» if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(«You can’t roll back an odometer!») def increment_odometer(self, miles): «»»Увеличивает показания одометра с заданным приращением.»»» self.odometer_reading += miles
В точке включается строка документации уровня модуля с кратким описанием содержимого модуля. Пишите строки документации для каждого созданного вами модуля. Теперь мы создадим отдельный файл с именем my_car .py. Этот файл импортирует класс Car и создает экземпляр этого класса: my_car.py from car import Car
Глава 9 • Классы
my_new_car = Car(‘audi’, ‘a4’, 2016) print(my_new_car.get_descriptive_name()) my_new_car.odometer_reading = 23 my_new_car.read_odometer()
Команда import в точке приказывает Python открыть модуль car и импортировать класс Car. Теперь мы можем использовать класс Car так, как если бы он был определен в этом файле. Результат остается тем же, что и в предыдущей версии: 2016 Audi A4 This car has 23 miles on it.
Импортирование классов повышает эффективность программирования. Представьте, каким длинным получился бы файл этой программы, если бы в него был включен весь класс Car. Перемещая класс в модуль и импортируя этот модуль, вы получаете ту же функциональность, но основной файл программы при этом остается чистым и удобочитаемым. Бульшая часть логики также может храниться в отдельных файлах; когда ваши классы работают так, как положено, вы можете забыть об этих файлах и сосредоточиться на высокоуровневой логике основной программы.
Хранение нескольких классов в модуле В одном модуле можно хранить сколько угодно классов, хотя все эти классы должны быть каким-то образом связаны друг с другом. Оба класса Battery и ElectricCar используются для представления автомобилей, поэтому мы добавим их в модуль car .py: car.py «»»Классы для представления машин с бензиновым и электродвигателем.»»» class Car(): . class Battery(): «»»Простая модель аккумулятора электромобиля.»»» def __init__(self, battery_size=60): «»»Инициализация атрибутов аккумулятора.»»» self.battery_size = battery_size def describe_battery(self): «»»Выводит информацию о мощности аккумулятора.»»» print(«This car has a » + str(self.battery_size) + «-kWh battery.») def get_range(self): «»»Выводит приблизительный запас хода для аккумулятора.»»» if self.battery_size == 70: range = 240 elif self.battery_size == 85: range = 270
Импортирование нескольких классов из модуля
message = «This car can go approximately » + str(range) message += » miles on a full charge.» print(message) class ElectricCar(Car): «»»Представляет аспекты машины, специфические для электромобилей.»»» def __init__(self, make, model, year): «»» Инициализирует атрибуты класса-родителя. Затем инициализирует атрибуты, специфические для электромобиля. «»» super().__init__(make, model, year) self.battery = Battery()
Теперь вы можете создать новый файл с именем my_electric_car .py, импортировать класс ElectricCar и создать новый экземпляр электромобиля: my_electric_car.py from car import ElectricCar my_tesla = ElectricCar(‘tesla’, ‘model s’, 2016) print(my_tesla.get_descriptive_name()) my_tesla.battery.describe_battery() my_tesla.battery.get_range()
Программа выводит тот же результат, что и в предыдущем случае, хотя бульшая часть ее логики скрыта в модуле: 2016 Tesla Model S This car has a 70-kWh battery. This car can go approximately 240 miles on a full charge.
Импортирование нескольких классов из модуля В файл программы можно импортировать столько классов, сколько потребуется. Если вы захотите создать обычный автомобиль и электромобиль в одном файле, потребуется импортировать оба класса, Car и ElectricCar: my_cars.py from car import Car, ElectricCar my_beetle = Car(‘volkswagen’, ‘beetle’, 2016) print(my_beetle.get_descriptive_name()) my_tesla = ElectricCar(‘tesla’, ‘roadster’, 2016) print(my_tesla.get_descriptive_name())
Чтобы импортировать несколько классов из модуля, разделите их имена запятыми . После того как необходимые классы будут импортированы, вы можете создать столько экземпляров каждого класса, сколько потребуется.
Глава 9 • Классы
В этом примере создается обычный автомобиль Volkswagen Beetle и электромобиль Tesla Roadster : 2016 Volkswagen Beetle 2016 Tesla Roadster
Импортирование всего модуля Также возможно импортировать весь модуль, а потом обращаться к нужным классам с использованием точечной записи. Этот способ прост, а полученный код легко читается. Так как каждый вызов, создающий экземпляр класса, включает имя модуля, в программе не будет конфликтов с именами, используемыми в текущем файле. my_cars.py import car my_beetle = car.Car(‘volkswagen’, ‘beetle’, 2016) print(my_beetle.get_descriptive_name()) my_tesla = car.ElectricCar(‘tesla’, ‘roadster’, 2016) print(my_tesla.get_descriptive_name())
В точке импортируется весь модуль car , после чего программа обращается к нужным классам с использованием синтаксиса имя_модуля. имя_класса. В точке снова создается экземпляр Volkswagen Beetle, а в точке — экземпляр Tesla Roadster.
Импортирование всех классов из модуля Для импортирования всех классов из модуля используется следующий синтаксис: from имя_модуля import *
Использовать этот способ не рекомендуется по двум причинам. Прежде всего, бывает полезно прочитать команды import в начале файла и получить четкое представление о том, какие классы используются в программе, а при таком подходе неясно, какие классы из модуля нужны программе. Также возможны конфликты с именами в файле. Если вы случайно импортируете класс с именем, уже присутствующим в файле, в программе могут возникнуть коварные ошибки. Почему я привожу описание этого способа? Хотя использовать его не рекомендуется, скорее всего, вы встретите его в коде других разработчиков. Итак, если вам нужно импортировать большое количество классов из модуля, лучше импортировать весь модуль и воспользоваться синтаксисом имя_модуля. имя_класса. Хотя вы не видите перечень всех используемых классов в начале файла, по крайней мере ясно видно, где модуль используется в программе. Также предотвращаются потенциальные конфликты имен, которые могут возникнуть при импортировании каждого класса в модуле.
Импортирование нескольких классов из модуля
Импортирование модуля в модуль Иногда классы приходится распределять по нескольким модулям, чтобы избежать чрезмерного разрастания одного файла и хранения несвязанных классов в одном модуле. При хранении классов в нескольких модулях может оказаться, что один класс из одного модуля зависит от класса из другого модуля. В таких случаях необходимый класс можно импортировать в первый модуль. Допустим, класс Car хранится в одном модуле, а классы ElectricCar и Battery — в другом. Мы создадим новый модуль с именем electric_car .py (он заменит файл electric_car .py , созданный ранее) и скопируем в него только классы Battery и ElectricCar: electric_car.py «»»Набор классов для представления электромобилей.»»» from car import Car class Battery(): . class ElectricCar(Car): .
Классу ElectricCar необходим доступ к классу-родителю Car, поэтому класс Car импортируется прямо в модуль в точке . Если вы забудете вставить эту команду, при попытке создания экземпляра ElectricCar произойдет ошибка. Также необходимо обновить модуль Car, чтобы он содержал только класс Car: car.py «»»Простая модель автомобиля.»»» class Car(): .
Теперь вы можете импортировать классы из каждого модуля по отдельности и создать ту разновидность машины, которая вам нужна: my_cars.py from car import Car from electric_car import ElectricCar my_beetle = Car(‘volkswagen’, ‘beetle’, 2016) print(my_beetle.get_descriptive_name()) my_tesla = ElectricCar(‘tesla’, ‘roadster’, 2016) print(my_tesla.get_descriptive_name())
В точке класс Car импортируется из своего модуля, а класс ElectricCar — из своего. После этого создаются экземпляры обоих разновидностей. Вывод показывает, что экземпляры были созданы правильно: 2016 Volkswagen Beetle 2016 Tesla Roadster
Глава 9 • Классы
Выработка рабочего процесса Как видите, Python предоставляет много возможностей структурирования кода в крупных проектах. Вы должны знать все эти возможности, чтобы найти лучшие способы организации своих проектов, а также лучше понимать код других разработчиков. На первых порах постарайтесь поддерживать простую структуру своего кода. Попробуйте разместить весь код в одном файле и, только когда все заработает, переместите классы в отдельные модули. Если вам нравится схема взаимодействия между модулями и файлами, попробуйте сохранить классы в модулях в начале работы над проектом. Найдите подход, при котором у вас получается работоспособный код, и двигайтесь дальше. УПРАЖНЕНИЯ 9-10 . Импортирование класса Restaurant: возьмите последнюю версию класса Restaurant и сохраните ее в модуле . Создайте отдельный файл, импортирующий класс Restaurant . Создайте экземпляр Restaurant и вызовите один из методов Restaurant, чтобы показать, что команда import работает правильно . 9-11 . Импортирование класса Admin: начните с версии класса из упражнения 9-8 (с . 176) . Сохраните классы User, Privileges и Admin в одном модуле . Создайте отдельный файл, создайте экземпляр Admin и вызовите метод show_privileges(), чтобы показать, что все работает правильно . 9-12 . Множественные модули: сохраните класс User в одном модуле, а классы Privileges и Admin в другом модуле . В отдельном файле создайте экземпляр Admin и вызовите метод show_privileges(), чтобы показать, что все работает правильно .
Стандартная библиотека Python Стандартная библиотека Python представляет собой набор модулей, включаемых в каждую установленную копию Python. Сейчас вы уже примерно понимаете, как работают классы, и можете начать использовать модули, написанные другими программистами. Чтобы использовать любую функцию или класс из стандартной библиотеки, достаточно включить простую команду import в начало файла. Для примера рассмотрим класс OrderedDict из модуля collections. Как вы уже знаете, словари позволяют связывать информационные фрагменты, но они не отслеживают порядок добавления пар «ключ—значение». Если вы хотите создать словарь, но при этом сохранить порядок добавления пар «ключ—значение», воспользуйтесь классом OrderedDict из модуля collections. Экземпляры класса OrderedDict ведут себя практически так же, как и словари, если не считать того, что они отслеживают порядок добавления пар «ключ—значение». Вернемся к примеру favorite_languages .py из главы 6. На этот раз программа будет отслеживать порядок, в котором участники отвечают на опрос: favorite_languages.py from collections import OrderedDict favorite_languages = OrderedDict()
Стандартная библиотека Python
favorite_languages[‘jen’] = ‘python’ favorite_languages[‘sarah’] = ‘c’ favorite_languages[‘edward’] = ‘ruby’ favorite_languages[‘phil’] = ‘python’ for name, language in favorite_languages.items(): print(name.title() + «‘s favorite language is » + language.title() + «.»)
Сначала программа импортирует класс OrderedDict из модуля collections в точке . В точке создается экземпляр класса OrderedDict, который сохраняется в favorite_languages. Обратите внимание на отсутствие фигурных скобок; вызов OrderedDict() создает пустой упорядоченный словарь и сохраняет его в favorite_ languages. Затем пары из имени и языка последовательно добавляются в словарь . Теперь при переборе favorite_languages в точке данные всегда будут выдаваться в порядке их добавления: Jen’s favorite language is Python. Sarah’s favorite language is C. Edward’s favorite language is Ruby. Phil’s favorite language is Python.
Это чрезвычайно полезный класс, объединяющий основное преимущество списков (сохранение исходного порядка) с главной особенностью словарей (связывание двух видов информации). Когда вы займетесь моделированием реальных ситуаций, может возникнуть ситуация, в которой упорядоченный словарь окажется именно тем, что вам необходимо. А по мере изучения стандартной библиотеки вы узнаете о других полезных модулях, которые помогут вам в решении типичных задач. ПРИМЕЧАНИЕ Модули также можно загружать из внешних источников . Соответствующие примеры встретятся вам в части II, в которой для завершения работы над проектами мы будем использовать внешние модули . УПРАЖНЕНИЯ 9-13 . Переработка с OrderedDict Rewrite: начните с упражнения 6-4 (с . 113), в котором стандартный словарь используется для представления глоссария . Перепишите программу с использованием класса OrderedDict и убедитесь в том, что порядок вывода совпадает с порядком добавления пар «ключ—значение» в словарь . 9-14 . Кубики: модуль random содержит функции для генерирования случайных чисел разными способами . Функция randint() возвращает целое число в заданном диапазоне . Следующий код возвращает число от 1 до 6: from random import randint x = randint(1, 6) Создайте класс Die с одним атрибутом с именем sides, который содержит значение по умолчанию 6 . Напишите метод roll_die() для вывода случайного числа от 1 до количества сторон кубика . Создайте экземпляр, моделирующий 6-гранный кубик, и имитируйте 10 бросков . Создайте модели 10- и 20-гранного кубика . Имитируйте 10 бросков каждого кубика . 9-15 . Модуль недели: для знакомства со стандартной библиотекой Python отлично подойдет сайт Python Module of the Week . Откройте сайт http://pymotw .com/ и просмотрите оглавление . Найдите модуль, который покажется вам интересным, и прочитайте про него или изучите документацию по модулям collections и random .
Глава 9 • Классы
Оформление классов В стилевом оформлении классов есть несколько моментов, о которых стоит упомянуть отдельно, особенно с усложнением ваших программ. Имена классов должны записываться в «верблюжьей» схеме: первая буква каждого слова записывается в верхнем регистре, слова не разделяются пробелами. Имена экземпляров и модулей записываются в нижнем регистре с разделением слов символами подчеркивания. Каждый класс должен иметь строку документации, следующую сразу же за определением класса. Строка документации должна содержать краткое описание того, что делает класс, и в ней должны соблюдаться те же соглашения по форматированию, которые вы использовали при написании строк документации в функциях. Каждый модуль также должен содержать строку документации с описанием возможных применений классов в модуле. Пустые строки могут использоваться для структурирования кода, но злоупотреблять ими не стоит. В классах можно разделять методы одной пустой строкой, а в модулях для разделения классов можно использовать две пустые строки. Если вам потребуется импортировать модуль из стандартной библиотеки и модуль из библиотеки, написанной вами, начните с команды import для модуля стандартной библиотеки. Затем добавьте пустую строку и команду import для модуля, написанного вами. В программах с несколькими командами import выполнение этого соглашения поможет понять, откуда берутся разные модули, использованные в программе.
Итоги В этой главе вы узнали, как написать собственные классы. Вы научились хранить информацию в классе с использованием атрибутов и наделять свои классы нужным поведением. Вы узнали, как написать методы __init__() для создания экземпляров ваших классов с нужными значениями атрибутов и как изменять атрибуты экземпляров напрямую и через методы. Также было показано, что наследование может упростить создание логически связанных классов и что экземпляры одного класса могут использоваться как атрибуты другого класса для упрощения кода классов. Вы узнали, что хранение классов в модулях и импортирование необходимых классов в файлы, где они будут использоваться, улучшает организацию проектов. Вы познакомились со стандартной библиотекой Python и рассмотрели пример, основанный на классе OrderedDict из модуля collections. Наконец, вы научились оформлять свои классы с использованием общепринятых соглашений Python. В главе 10 вы научитесь работать с файлами и сохранять результаты работы, выполненной в программе. Также будут рассмотрены исключения — экземпляры специального класса Python, предназначенного для передачи информации о возникающих ошибках.
Файлы и исключения
Вы уже овладели основными навыками, необходимыми для создания хорошо структурированных и удобных в использовании программ; теперь пора подумать о том, как сделать ваши программы еще более удобными и полезными. В этой главе вы научитесь работать с файлами, чтобы ваши программы могли быстро анализировать большие объемы данных. В этой главе вы научитесь обрабатывать ошибки, чтобы возникновение аномальных ситуаций не приводило к аварийному завершению ваших программ. Мы рассмотрим исключения — специальные объекты, которые создаются для управления ошибками, возникающими во время выполнения программ Python. Также будет описан модуль json, позволяющий сохранять пользовательские данные, чтобы они не терялись при завершении работы программы. Работа с файлами и сохранение данных упрощают использование ваших программ. Пользователь сам выбирает, какие данные и когда нужно вводить. Он может запустить вашу программу, выполнить некоторую работу, потом закрыть программу и позднее продолжить работу с того момента, на котором он прервался. Умение обрабатывать исключения поможет справиться с такими ситуациями, как отсутствие нужных файлов, а также с другими проблемами, приводящими к сбою программ. Обработка исключений повысит устойчивость ваших программ при работе с некорректными данными — появившимися как из-за случайных ошибок, так и из-за злонамеренных попыток взлома ваших программ. Материал, представленный в этой главе, сделает ваши программы более практичными, удобными и надежными.
Чтение из файла Гигантские объемы данных доступны в текстовых файлах. В них могут храниться погодные данные, социально-экономическая информация, литературные произведения и многое другое. Чтение из файла особенно актуально для приложений, предназначенных для анализа данных, но оно также может пригодиться в любой ситуации, требующей анализа или изменения информации, хранящейся в файле. Например, программа может читать содержимое текстового файла и переписывать его с форматированием, рассчитанным на отображение информации в браузере. Работа с информацией в текстовом файле начинается с чтения данных в память. Вы можете прочитать все содержимое файла или же читать данные по строкам.
Глава 10 • Файлы и исключения
Чтение всего файла Для начала нам понадобится файл с несколькими строками текста. Пусть это будет файл с числом «пи» с точностью до 30 знаков, по 10 знаков на строку: pi_digits.txt 3.1415926535 8979323846 2643383279
Чтобы опробовать эти примеры, либо введите данные в редакторе и сохраните файл с именем pi_digits .txt, либо загрузите файл из ресурсов книги на странице https:// www.nostarch.com/pythoncrashcourse/. Сохраните файл в каталоге, в котором будут храниться программы этой главы. Следующая программа открывает этот файл, читает его и выводит содержимое на экран: file_reader.py with open(‘pi_digits.txt’) as file_object: contents = file_object.read() print(contents)
В первой строке этой программы многое заслуживает вашего внимания. Начнем с функции open(). Чтобы выполнить любые операции с файлом — даже просто вывести его содержимое, — сначала необходимо открыть файл. Функция open() получает один аргумент: имя открываемого файла. Python ищет файл с указанным именем в каталоге, в котором находится файл текущей программы. В данном примере выполняется программа file_reader .py, поэтому Python ищет файл pi_digits .txt в каталоге, в котором хранится file_reader .py. Функция open() возвращает объект, представляющий файл. В данном случае open(‘pi_digits.txt’) возвращает объект, представляющий файл pi_digits .txt. Python сохраняет этот объект в переменной file_object, с которой мы будем работать позднее в программе. Конструкция с ключевым словом with закрывает файл после того, как надобность в нем отпадет. Обратите внимание: в этой программе есть вызов open(), но нет вызова close(). Файлы можно открывать и закрывать явными вызовами open() и close(); но если из-за ошибки в программе команда close() останется невыполненной, то файл не будет закрыт. На первый взгляд это не страшно, но некорректное закрытие файлов может привести к потере или порче данных. А если функция close() будет вызвана слишком рано, программа попытается работать с закрытым (то есть недоступным) файлом, что приведет к новым ошибкам. Не всегда можно заранее определить, когда нужно закрывать файл, но с приведенной конструкцией Python сделает это за вас. Вам остается лишь открыть файл и работать с ним так, как требуется, надеясь на то, что Python закроет его автоматически в правильный момент. После того как в программе появится объект, представляющий файл pi_digits .txt, во второй строке программы используется метод read(), который читает все содержимое файла и сохраняет его содержимое в одной длинной строке в переменной
Чтение из файла
contents. При выводе значения contents на экране появляется все содержимое
3.1415926535 8979323846 2643383279
Единственное различие между выводом и исходным файлом — лишняя пустая строка в конце вывода. Откуда она взялась? Метод read() возвращает ее при чтении, если достигнут конец файла. Если вы хотите удалить лишнюю пустую строку, включите вызов rstrip() в команду print: with open(‘pi_digits.txt’) as file_object: contents = file_object.read() print(contents.rstrip())
Напомним, что метод rstrip() удаляет все пропуски в конце строки. Теперь вывод точно соответствует содержимому исходного файла: 3.1415926535 8979323846 2643383279
Пути к файлам Если передать функции open() простое имя файла, такое как pi_digits .txt, Python ищет файл в том каталоге, в котором находится файл, выполняемый в настоящий момент (то есть файл программы .py). В некоторых случаях (в зависимости от того, как организованы ваши рабочие файлы) открываемый файл может и не находиться в одном каталоге с файлом программы. Например, файл программы может находиться в каталоге python_work; в каталоге python_work создается другой каталог с именем text_files для текстовых файлов, с которыми работает программа. И хотя папка text_files находится в python_work, простая передача open() имени файла из text_files не подойдет, потому что Python проведет поиск файла в python_work и на этом остановится; поиск не будет продолжен во вложенном каталоге text_files. Чтобы открыть файлы из каталога, отличного от того, в котором хранится файл программы, необходимо указать путь — то есть приказать Python искать файлы в конкретном месте файловой системы. Так как каталог text_files находится в python_work, для открытия файла из text_files можно воспользоваться относительным путем. Относительный путь приказывает Python искать файлы в каталоге, который задается относительно каталога, в котором находится текущий файл программы. В системе Linux и OS X это выглядит так: with open(‘text_files/имя_файла.txt’) as file_object:
Эта строка означает, что файл .txt следует искать в каталоге text_files; она предполагает, что каталог text_files находится в python_work (так оно и есть). В системах Windows в путях к файлам вместо слеша (/) используется обратный слеш (\): with open(‘text_files\имя_файла.txt’) as file_object:
Глава 10 • Файлы и исключения
Также можно точно определить местонахождение файла в вашей системе независимо от того, где хранится выполняемая программа. Такие пути называются абсолютными и используются в том случае, если относительный путь не работает. Например, если каталог text_files находится не в python_work, а в другом каталоге (скажем, в каталоге с именем other_files), то передать open() путь ‘text_files/ filename.txt’ не получится, потому что Python будет искать указанный каталог только внутри python_work. Чтобы объяснить Python, где следует искать файл, необходимо записать полный путь. Абсолютные пути обычно длиннее относительных, поэтому их лучше сохранять в переменных, которые затем передаются open(). В Linux и OS X абсолютные пути выглядят так: file_path = ‘/home/ehmatthes/other_files/text_files/имя_файла.txt’ with open(file_path) as file_object:
В Windows они выглядят так: file_path = ‘C:\Users\ehmatthes\other_files\text_files\имя_файла.txt’ with open(file_path) as file_object:
С абсолютными путями вы сможете читать файлы из любого каталога вашей системы. Пока будет проще хранить файлы в одном каталоге с файлами программ или в каталогах, вложенных в каталог с файлами программ (таких как text_files из рассмотренного примера). ПРИМЕЧАНИЕ Иногда в системах семейства Windows слеш в пути к файлам интерпретируется правильно . Если вы используете Windows, но не получаете ожидаемых результатов, попробуйте использовать символы обратного слеша .
Чтение по строкам В процессе чтения файла часто бывает нужно обработать каждую строку. Возможно, вы ищете некую информацию в файле или собираетесь каким-то образом изменить текст, например при чтении файла с метеорологическими данными вы обрабатываете каждую строку, у которой в описании погоды встречается слово «солнечно». Или, допустим, в новостях вы ищете каждую строку с тегом заголовка и заменяете ее специальными элементами форматирования. Для последовательной обработки каждой строки в файле можно воспользоваться циклом for: file_reader.py filename = ‘pi_digits.txt’ with open(filename) as file_object: for line in file_object: print(line)
В точке имя файла, из которого читается информация, сохраняется в переменной filename. Это стандартный прием при работе с файлами: так как переменная
Чтение из файла
filename не представляет конкретный файл (это всего лишь строка, которая сообщает Python, где найти файл), вы сможете легко заменить ‘pi_digits.txt’ именем другого файла, с которым вы собираетесь работать. После вызова open()
объект, представляющий файл и его содержимое, сохраняется в переменной file_object . Мы снова используем синтаксис with, чтобы поручить Python открывать и закрывать файл в нужный момент. Для просмотра содержимого все строки файла перебираются в цикле for по объекту файла . На этот раз пустых строк оказывается еще больше: 3.1415926535 8979323846 2643383279
Пустые строки появляются из-за того, что каждая строка в текстовом файле завершается невидимым символом новой строки. Команда print добавляет свой символ новой строки при каждом вызове, поэтому в результате каждая строка завершается двумя символами новой строки: один прочитан из файла, а другой добавлен командой print. Вызов rstrip() в команде print удаляет лишние пустые строки: filename = ‘pi_digits.txt’ with open(filename) as file_object: for line in file_object: print(line.rstrip())
Теперь вывод снова соответствует содержимому файла: 3.1415926535 8979323846 2643383279
Создание списка строк по содержимому файла При использовании with объект файла, возвращаемый вызовом open(), доступен только в пределах содержащего его блока with. Если вы хотите, чтобы содержимое файла оставалось доступным за пределами блока with, сохраните строки файла в списке внутри блока и в дальнейшем работайте с полученным списком. Одни части файла можно обработать немедленно и отложить другие для обработки в будущем. В следующем примере строки pi_digits .txt сохраняются в списке в блоке with, после чего выводятся за пределами этого блока: filename = ‘pi_digits.txt’ with open(filename) as file_object: lines = file_object.readlines()
Глава 10 • Файлы и исключения
for line in lines: print(line.rstrip())
В точке метод readlines() последовательно читает каждую строку из файла и сохраняет ее в списке. Список сохраняется в переменной lines, с которой можно продолжить работу после завершения блока with. В точке в простом цикле for выводятся все элементы списка lines. Так как каждый элемент lines соответствует ровно одной строке файла, вывод точно соответствует его содержимому.
Работа с содержимым файла После того как файл будет прочитан в память, вы сможете обрабатывать данные так, как считаете нужным. Для начала попробуем построить одну строку со всеми цифрами из файла без промежуточных пропусков: pi_string.py filename = ‘pi_digits.txt’ with open(filename) as file_object: lines = file_object.readlines() pi_string = » for line in lines: pi_string += line.rstrip() print(pi_string) print(len(pi_string))
Сначала программа открывает файл и сохраняет каждую строку цифр в списке — точно так же, как это делалось в предыдущем примере. В точке создается переменная pi_string для хранения цифр числа «пи». Далее следует цикл, который добавляет к pi_string каждую серию цифр, из которой удаляется символ новой строки . В точке программа выводит строку и ее длину: 3.1415926535 36
Переменная pi_string содержит пропуски, которые присутствовали в начале каждой строки цифр. Чтобы удалить их, достаточно использовать strip() вместо rstrip(): filename = ‘pi_30_digits.txt’ with open(filename) as file_object: lines = file_object.readlines() pi_string = » for line in lines: pi_string += line.strip() print(pi_string) print(len(pi_string))
Чтение из файла
В итоге мы получаем строку, содержащую значение «пи» с точностью до 30 знаков. Длина строки равна 32 символам, потому что в нее также включается начальная цифра 3 и точка: 3.141592653589793238462643383279 32 ПРИМЕЧАНИЕ Читая данные из текстового файла, Python интерпретирует весь текст в файле как строку . Если вы читаете из текстового файла число и хотите работать с ним в числовом контексте, преобразуйте его в целое число функцией int() или в вещественное число функцией float() .
Большие файлы: миллион цифр До настоящего момента мы ограничивались анализом текстового файла, который состоял всего из трех строк, но код этих примеров будет работать и с намного большими файлами. Начиная с текстового файла, содержащего значение «пи» до 1 000 000 знаков (вместо 30), вы сможете создать одну строку, которая содержит все эти цифры. Изменять программу вообще не придется — достаточно передать ей другой файл. Также мы ограничимся выводом первых 50 цифр, чтобы не пришлось ждать, пока в терминале не прокрутится миллион знаков: pi_string.py filename = ‘pi_million_digits.txt’ with open(filename) as file_object: lines = file_object.readlines() pi_string = » for line in lines: pi_string += line.strip() print(pi_string[:52] + «. «) print(len(pi_string))
Из выходных данных видно, что строка действительно содержит значение «пи» с точностью до 1 000 000 знаков: 3.14159265358979323846264338327950288419716939937510. 1000002
Python не устанавливает никаких ограничений на длину данных, с которыми вы можете работать. Она ограничивается разве что объемом памяти вашей системы. ПРИМЕЧАНИЕ Для запуска этой программы (и многих других примеров, приведенных ниже) необходимо загрузить ресурсы по адресу https://www .nostarch .com/pythoncrashcourse/ .
Глава 10 • Файлы и исключения
Проверка дня рождения Меня всегда интересовало, не встречается ли мой день рождения среди цифр числа «пи»? Воспользуемся только что созданной программой для проверки того, входит ли запись дня рождения пользователя в первый миллион цифр. Для этого можно записать день рождения в виде строки из цифр и посмотреть, присутствует ли эта строка в pi_string: filename = ‘pi_million_digits.txt’ with open(filename) as file_object: lines = file_object.readlines() pi_string = » for line in lines: pi_string += line.rstrip() birthday = input(«Enter your birthday, in the form mmddyy: «) if birthday in pi_string: print(«Your birthday appears in the first million digits of pi!») else: print(«Your birthday does not appear in the first million digits of pi.»)
В точке программа запрашивает день рождения пользователя, а затем в точке проверяет вхождение этой строки в pi_string. Пробуем: Enter your birthdate, in the form mmddyy: 120372 Your birthday appears in the first million digits of pi!
Оказывается, мой день рождения встречается среди цифр «пи»! После того как данные будут прочитаны из файла, вы сможете делать с ними все, что сочтете нужным. УПРАЖНЕНИЯ 10-1 . Изучение Python: откройте пустой файл в текстовом редакторе и напишите несколько строк текста о возможностях Python . Каждая строка должна начинаться с фразы: «In Python you can…» Сохраните файл под именем learning_python .txt в каталоге, использованном для примеров этой главы . Напишите программу, которая читает файл и выводит текст три раза: с чтением всего файла, с перебором строк объекта файла и с сохранением строк в списке с последующим выводом списка вне блока with . 10-2 . Изучение C: метод replace() может использоваться для замены любого слова в строке другим словом . В следующем примере слово ‘dog’ заменяется словом ‘cat’: >>> message = «I really like dogs.» >>> message.replace(‘dog’, ‘cat’) ‘I really like cats.’ Прочитайте каждую строку из только что созданного файла learning_python .txt и замените слово Python названием другого языка, например C . Выведите каждую измененную строку на экран .
Запись в файл Один из простейших способов сохранения данных — запись в файл. Текст, записанный в файл, останется доступным и после закрытия терминала с выводом вашей программы. Вы сможете проанализировать результаты после завершения программы или передать свои файлы другим. Вы также сможете написать программы, которые снова читают сохраненный текст в память и работают с ним.
Запись в пустой файл Чтобы записать текст в файл, необходимо вызвать open() со вторым аргументом, который сообщает Python, что вы собираетесь записывать данные в файл. Чтобы увидеть, как это делается, напишем простое сообщение и сохраним его в файле (вместо того чтобы просто вывести на экран): write_message.py filename = ‘programming.txt’ with open(filename, ‘w’) as file_object: file_object.write(«I love programming.»)
При вызове open() в этом примере передаются два аргумента . Первый аргумент, как и прежде, содержит имя открываемого файла. Второй аргумент ‘w’ сообщает Python, что файл должен быть открыт в режиме записи. Файлы можно открывать в режиме чтения (‘r’), записи (‘w’), присоединения (‘a’) или в режиме, допускающем как чтение, так и запись в файл (‘r+’). Если аргумент режима не указан, Python по умолчанию открывает файл в режиме только для чтения. Если файл, открываемый для записи, еще не существует, функция open() автоматически создает его. Будьте внимательны, открывая файл в режиме записи (‘w’): если файл существует, то Python уничтожит его данные перед возвращением объекта файла. В точке метод write() используется с объектом файла для записи строки в файл. Программа не выводит данные на терминал, но, открыв файл programming .txt, вы увидите в нем одну строку: programming.txt I love programming.
Этот файл ничем не отличается от любого другого текстового файла на вашем компьютере. Его можно открыть, записать в него новый текст, скопировать/вставить текст и т. д. ПРИМЕЧАНИЕ Python может записывать в текстовые файлы только строковые данные . Если вы захотите сохранить в текстовом файле числовую информацию, данные придется предварительно преобразовать в строки функцией str() .
Глава 10 • Файлы и исключения
Многострочная запись Функция write() не добавляет символы новой строки в записываемый текст. А это означает, что если вы записываете сразу несколько строк без включения символов новой строки, полученный файл может выглядеть не так, как вы рассчитывали: filename = ‘programming.txt’ with open(filename, ‘w’) as file_object: file_object.write(«I love programming.») file_object.write(«I love creating new games.»)
Открыв файл programming .txt, вы увидите, что две строки «склеились»: I love programming.I love creating new games.
Если включить символы новой строки в команды write(), текст будет состоять из двух строк: filename = ‘programming.txt’ with open(filename, ‘w’) as file_object: file_object.write(«I love programming.\n») file_object.write(«I love creating new games.\n»)
Результат выглядит так: I love programming. I love creating new games.
Для форматирования вывода также можно использовать пробелы, символы табуляции и пустые строки по аналогии с тем, как это делалось с выводом на терминал.
Присоединение данных к файлу Если вы хотите добавить в файл новые данные вместо того, чтобы перезаписывать существующее содержимое, откройте файл в режиме присоединения. В этом случае Python не уничтожает содержимое файла перед возвращением объекта файла. Все строки, выводимые в файл, будут добавлены в конец файла. Если файл еще не существует, то Python автоматически создаст пустой файл. Изменим программу write_message .py и дополним существующий файл programming .txt новыми данными: write_message.py filename = ‘programming.txt’ with open(filename, ‘a’) as file_object: file_object.write(«I also love finding meaning in large datasets.\n») file_object.write(«I love creating apps that can run in a browser.\n»)
В точке аргумент ‘a’ используется для открытия файла в режиме присоединения (вместо перезаписи существующего файла). В точке записываются две новые строки, которые добавляются к содержимому programming .txt:
programming.txt I I I I
love love also love
programming. creating new games. love finding meaning in large datasets. creating apps that can run in a browser.
В результате к исходному содержимому файла добавляется новый текст. УПРАЖНЕНИЯ 10-3 . Гость: напишите программу, которая запрашивает у пользователя его имя . Введенный ответ сохраняется в файле с именем guest .txt . 10-4 . Гостевая книга: напишите цикл while, который в цикле запрашивает у пользователей имена . При вводе каждого имени выведите на экран приветствие и добавьте строку с сообщением в файл с именем guest_book .txt . Проследите за тем, чтобы каждое сообщение размещалось в отдельной строке файла . 10-5 . Опрос: напишите цикл while, в котором программа спрашивает у пользователя, почему ему нравится программировать . Каждый раз, когда пользователь вводит очередную причину, сохраните текст его ответа в файле .
Исключения Для управления ошибками, возникающими в ходе выполнения программы, в Python используются специальные объекты, называемые исключениями. Если при возникновении ошибки Python не знает, что делать дальше, создается объект исключения. Если в программу включен код обработки исключения, то выполнение программы продолжится, а если нет — программа останавливается и выводит трассировку с отчетом об исключении. Исключения обрабатываются в блоках try-except. Блок try-except приказывает Python выполнить некоторые действия, но при этом также сообщает, что нужно делать при возникновении исключения. С блоками try-except ваши программы будут работать даже в том случае, если что-то пошло не так. Вместо невразумительной трассировки выводится понятное сообщение об ошибке, которое вы определяете в программе.
Обработка исключения ZeroDivisionError Рассмотрим простую ошибку, при которой Python инициирует исключение. Конечно, вы знаете, что деление на ноль невозможно, но мы все же прикажем Python выполнить эту операцию: division.py print(5/0)
Из этого ничего не выйдет, поэтому на экран выводятся данные трассировки: Traceback (most recent call last): File «division.py», line 1, in print(5/0) ZeroDivisionError: division by zero
Глава 10 • Файлы и исключения
Ошибка, упоминаемая в трассировке — ZeroDivisionError, — является объектом исключения. Такие объекты создаются в том случае, если Python не может выполнить ваши распоряжения. Обычно в таких случаях Python прерывает выполнение программы и сообщает тип обнаруженного исключения. Эта информация может использоваться в программе; по сути вы сообщаете Python, как следует поступить при возникновении исключения данного типа. В таком случае ваша программа будет подготовлена к его появлению.
Блоки try-except Если вы предполагаете, что в программе может произойти ошибка, напишите блок try-except для обработки возникающего исключения. Такой блок приказывает Python выполнить некоторый код, а также сообщает, что нужно делать, если при его выполнении произойдет исключение конкретного типа. Вот как выглядит блок try-except для обработки исключений ZeroDivisionError: try: print(5/0) except ZeroDivisionError: print(«You can’t divide by zero!»)
Команда print(5/0), порождающая ошибку, находится в блоке try. Если код в блоке try выполнен успешно, то Python пропускает блок except. Если код в блоке try порождает ошибку, то Python ищет блок except с соответствующей ошибкой и выпускает код в этом блоке. В этом примере код блока try порождает ошибку ZeroDivisionError, поэтому Python ищет блок except с описанием того, как следует действовать в такой ситуации. При выполнении кода этого блока пользователь видит понятное сообщение об ошибке вместо данных трассировки: You can’t divide by zero!
Если бы за кодом try-except следовал другой код, то выполнение программы продолжилось, потому что мы объяснили Python, как обрабатывать эту ошибку. В следующем примере обработка ошибки позволяет программе продолжить выполнение.
Использование исключений для предотвращения аварийного завершения программы Правильная обработка ошибок особенно важна в том случае, если программа должна продолжить работу после возникновения ошибки. Такая ситуация часто встречается в программах, запрашивающих данные у пользователя. Если программа правильно среагировала на некорректный ввод, она может запросить новые данные после сбоя. Создадим простой калькулятор, который выполняет только операцию деления:
division.py print(«Give me two numbers, and I’ll divide them.») print(«Enter ‘q’ to quit.») while True: first_number = input(«\nFirst number: «) if first_number == ‘q’: break second_number = input(«Second number: «) if second_number == ‘q’: break answer = int(first_number) / int(second_number) print(answer)
Программа запрашивает у пользователя первое число first_number , а затем, если пользователь не ввел q для завершения работы, запрашивает второе число second_number . Далее одно число делится на другое для получения результата answer . Программа никак не обрабатывает ошибки, так что попытка деления на ноль приводит к ее аварийному завершению: Give me two numbers, and I’ll divide them. Enter ‘q’ to quit. First number: 5 Second number: 0 Traceback (most recent call last): File «division.py», line 9, in answer = int(first_number) / int(second_number) ZeroDivisionError: division by zero
Конечно, аварийное завершение — это плохо, но еще хуже, если пользователь увидит данные трассировки. Неопытного пользователя они собьют с толку, а при сознательной попытке взлома злоумышленник сможет получить из них больше информации, чем вам хотелось бы. Например, он узнает имя файла программы и увидит некорректно работающую часть кода. На основании этой информации опытный хакер иногда может определить, какие атаки следует применять против вашего кода.
Блок else Для повышения устойчивости программы к ошибкам можно заключить строку, выдающую ошибки, в блок try-except. Ошибка происходит в строке, выполняющей деление; следовательно, именно эту строку следует заключить в блок try-except. Данный пример также включает блок else. Любой код, зависящий от успешного выполнения блока try, размещается в блоке else: print(«Give me two numbers, and I’ll divide them.») print(«Enter ‘q’ to quit.») while True: first_number = input(«\nFirst number: «) if first_number == ‘q’:
Глава 10 • Файлы и исключения
break second_number = input(«Second number: «) try: answer = int(first_number) / int(second_number) except ZeroDivisionError: print(«You can’t divide by 0!») else: print(answer)
Программа пытается выполнить операцию деления в блоке try , который включает только код, способный породить ошибку. Любой код, зависящий от успешного выполнения блока try, добавляется в блок else. В данном случае, если операция деления выполняется успешно, блок else используется для вывода результата . Блок except сообщает Python, как следует поступать при возникновении ошибки ZeroDivisionError . Если при выполнении команды из блока try происходит ошибка, связанная с делением на 0, программа выводит понятное сообщение, которое объясняет пользователю, как избежать подобных ошибок. Выполнение программы продолжается, и пользователь не сталкивается с трассировкой: Give me two numbers, and I’ll divide them. Enter ‘q’ to quit. First number: 5 Second number: 0 You can’t divide by 0! First number: 5 Second number: 2 2.5 First number: q
Блок try-except-else работает так: Python пытается выполнить код в блоке try. В блоках try следует размещать только тот код, при выполнении которого может возникнуть исключение. Иногда некоторый код должен выполняться только в том случае, если выполнение try прошло успешно; такой код размещается в блоке else. Блок except сообщает Python, что делать, если при выполнении кода try произошло определенное исключение. Заранее определяя вероятные источники ошибок, вы повышаете надежность своих программ, которые продолжают работать даже при вводе некорректных данных или при недоступности ресурсов. Ваш код оказывается защищенным от случайных ошибок пользователей и сознательных атак.
Обработка исключения FileNotFoundError Одна из стандартных проблем при работе с файлами — отсутствие необходимых файлов. Тот файл, который вам нужен, может находиться в другом месте, в имени файла может быть допущена ошибка, или файл может вообще не существовать. Все эти ситуации достаточно прямолинейно обрабатываются в блоках try-except.
Попробуем прочитать данные из несуществующего файла. Следующая программа пытается прочитать содержимое файла с текстом «Алисы в Стране чудес», но я не сохранил файл alice .txt в одном каталоге с файлом alice .py: alice.py filename = ‘alice.txt’ with open(filename) as f_obj: contents = f_obj.read()
Прочитать данные из несуществующего файла нельзя, поэтому Python выдает исключение: Traceback (most recent call last): File «alice.py», line 3, in with open(filename) as f_obj: FileNotFoundError: [Errno 2] No such file or directory: ‘alice.txt’
В последней строке трассировки упоминается FileNotFoundError: это исключение выдается в том случае, если Python не может найти открываемый файл. В данном примере функция open() порождает ошибку, и, чтобы обработать ее, блок try начинается перед строкой с вызовом open(): filename = ‘alice.txt’ try: with open(filename) as f_obj: contents = f_obj.read() except FileNotFoundError: msg = «Sorry, the file » + filename + » does not exist.» print(msg)
В этом примере код блока try выдает исключение FileNotFoundError, поэтому Python ищет блок except для этой ошибки. Затем выполняется код этого блока, в результате чего вместо трассировки выдается доступное сообщение об ошибке: Sorry, the file alice.txt does not exist.
Если файл не существует, программе больше делать нечего, поэтому код обработки ошибок почти ничего не добавляет в эту программу. Доработаем этот пример и посмотрим, как обработка исключений помогает при работе с несколькими файлами.
Анализ текста Программа может анализировать текстовые файлы, содержащие целые книги. Многие классические произведения, ставшие общественным достоянием, доступны в виде простых текстовых файлов. Тексты, использованные в этом разделе, взяты с сайта проекта «Гутенберг» (http://gutenberg.org/). На этом сайте хранится под-
Глава 10 • Файлы и исключения
борка литературных произведений, не защищенных авторским правом; это превосходный ресурс для разработчиков, которые собираются работать с литературными текстами в своих программных проектах. Прочитаем текст «Алисы в Стране чудес» и попробуем подсчитать количество слов в тексте. Мы воспользуемся методом split(), предназначенным для построения списка слов на основе строки. Вот как метод split() работает со строкой, содержащей только название книги: >>> title = «Alice in Wonderland» >>> title.split() [‘Alice’, ‘in’, ‘Wonderland’]
Метод split() разделяет строку на части по всем позициям, в которых обнаружит пробел, и сохраняет все части строки в элементах списка. В результате создается список слов, входящих в строку (впрочем, вместе с некоторыми словами могут храниться знаки препинания). Для подсчета слов в книге мы воспользуемся вызовом split() для всего текста, а затем подсчитаем элементы списка, чтобы получить примерное количество слов в тексте: filename = ‘alice.txt’ try: with open(filename) as f_obj: contents = f_obj.read() except FileNotFoundError: msg = «Sorry, the file » + filename + » does not exist.» print(msg) else: # Подсчет приблизительного количества строк в файле. words = contents.split() num_words = len(words) print(«The file » + filename + » has about » + str(num_words) + » words.»)
Затем я переместил файл alice .txt в правильный каталог, чтобы код в блоке try был выполнен без ошибок. В точке программа загружает текст в переменную contents, которая теперь содержит весь текст в виде одной длинной строки и использует метод split() для получения списка всех слов в книге. Запрашивая длину этого списка при помощи функции len(), мы получаем неплохое приближенное значение количества слов в исходной строке . В точке выводится сообщение с количеством слов, найденных в файле. Этот код помещен в блок else, потому что он должен выводиться только в случае успешного выполнения блока try. Выходные данные программы сообщают, сколько слов содержит файл alice .txt: The file alice.txt has about 29461 words.
Количество слов немного завышено, потому что в нем учитывается дополнительная информация, включенная в текстовый файл издателем, но в целом оно довольно точно оценивает длину «Алисы в Стране чудес».
Работа с несколькими файлами Добавим еще несколько файлов с книгами для анализа. Но для начала переместим основной код программы в функцию с именем count_words(). Это упростит проведение анализа для нескольких книг: word_count.py def count_words(filename): «»»Подсчет приблизительного количества строк в файле.»»» try: with open(filename) as f_obj: contents = f_obj.read() except FileNotFoundError: msg = «Sorry, the file » + filename + » does not exist.» print(msg) else: # Подсчет приблизительного количества строк в файле. words = contents.split() num_words = len(words) print(«The file » + filename + » has about » + str(num_words) + » words.») filename = ‘alice.txt’ count_words(filename)
Бульшая часть кода не изменилась. Мы просто снабдили код отступом и переместили его в тело count_words(). Кроме того, комментарий был преобразован в строку документации . Теперь мы можем написать простой цикл для подсчета слов в любом тексте, который нужно проанализировать. Для этого имена анализируемых файлов сохраняются в списке, после чего для каждого файла в списке вызывается функция count_words(). Мы попробуем подсчитать слова в «Алисе в Стране чудес», «Сиддхартхе», «Моби Дике» и «Маленьких женщинах» — все эти книги есть в свободном доступе. Я намеренно не стал копировать файл siddhartha .txt в каталог с программой word_count .py, чтобы выяснить, насколько хорошо наша программа справляется с отсутствием файла: def count_words(filename): . filenames = [‘alice.txt’, ‘siddhartha.txt’, ‘moby_dick.txt’, ‘little_women.txt’] for filename in filenames: count_words(filename)
Отсутствие файла siddhartha .txt не влияет на выполнение программы: The file alice.txt has about 29461 words. Sorry, the file siddhartha.txt does not exist. The file moby_dick.txt has about 215136 words. The file little_women.txt has about 189079 words.
Глава 10 • Файлы и исключения
Использование блока try-except в этом примере дает два важных преимущества: программа ограждает пользователя от вывода трассировки и продолжает выполнение, анализируя тексты, которые ей удается найти. Если бы в программе не перехватывалось исключение FileNotFoundError, инициированное из-за отсутствия siddhartha .txt, то пользователь увидел бы полную трассировку, а работа программы прервалась бы после попытки подсчитать слова в тексте «Сиддхартхи»; до анализа «Моби Дика» или «Маленьких женщин» дело не дошло бы.
Ошибки без уведомления пользователя В предыдущем примере мы сообщили пользователю о том, что один из файлов оказался недоступен. Тем не менее вы не обязаны сообщать о каждом обнаруженном исключении. Иногда при возникновении исключения программа должна просто проигнорировать сбой и продолжать работу, словно ничего не произошло. Для этого блок try пишется так же, как обычно, но в блоке except вы явно приказываете Python не предпринимать никаких особых действий в случае ошибки. В языке Python существует команда pass, с которой блок ничего не делает: def count_words(filename): «»»Подсчет приблизительного количества строк в файле.»»» try: . except FileNotFoundError: pass else: . filenames = [‘alice.txt’, ‘siddhartha.txt’, ‘moby_dick.txt’, ‘little_women.txt’] for filename in filenames: count_words(filename)
Единственное отличие этого листинга от предыдущего — команда pass в точке . Теперь при возникновении ошибки FileNotFoundError выполняется код в блоке except, но при этом ничего не происходит. Программа не выдает данные трассировки и вообще никакие результаты, указывающие на возникновение ошибки. Пользователи получают данные о количестве слов во всех существующих файлах, однако ничто не сообщает о том, что какой-то файл не был найден: The file alice.txt has about 29461 words. The file moby_dick.txt has about 215136 words. The file little_women.txt has about 189079 words.
Команда pass также может служить временным заполнителем. Она напоминает, что в этот конкретный момент выполнения вашей программы вы решили ничего не предпринимать, хотя, возможно, сделаете что-то позднее. Например, эта программа может записать все имена отсутствующих файлов в файл с именем missing_files .txt. Пользователи этот файл не увидят, но создатель программы сможет прочитать его и разобраться с отсутствующими текстами.
О каких ошибках нужно сообщать? Как определить, в каком случае следует сообщить об ошибке пользователю, а когда можно просто проигнорировать ее незаметно для пользователя? Если пользователь знает, с какими текстами должна работать программа, вероятно, он предпочтет получить сообщение, объясняющее, почему некоторые тексты были пропущены при анализе. Пользователь ожидает увидеть какие-то результаты, но не знает, какие книги должны быть проанализированы? Возможно, ему и не нужно знать о недоступности каких-то файлов. Лишняя информация только сделает вашу программу менее удобной для пользователя. Средства обработки ошибок Python позволяют достаточно точно управлять тем, какой объем информации следует предоставить пользователю. Хорошо написанный, правильно протестированный код редко содержит внутренние ошибки (например, синтаксические или логические). Но в любой ситуации, в которой ваша программа зависит от внешних факторов (пользовательского ввода, существования файла, доступности сетевого подключения), существует риск возникновения исключения. С накоплением практического опыта вы начнете видеть, в каких местах программы следует разместить блоки обработки исключений и сколько информации предоставлять пользователям о возникающих ошибках. УПРАЖНЕНИЯ 10-6 . Сложение: при вводе числовых данных часто встречается типичная проблема: пользователь вводит текст вместо чисел . При попытке преобразовать данные в int происходит исключение TypeError . Напишите программу, которая запрашивает два числа, складывает их и выводит результат . Перехватите исключение TypeError, если какое-либо из входных значений не является числом, и выведите удобное сообщение об ошибке . Протестируйте свою программу: сначала введите два числа, а потом введите текст вместо одного из чисел . 10-7 . Калькулятор: заключите код из упражнения 10-6 в цикл while, чтобы пользователь мог продолжать вводить числа, даже если он допустил ошибку и ввел текст вместо числа . 10-8 . Кошки и собаки: создайте два файла с именами cats .txt и dogs .txt . Сохраните минимум три клички кошек в первом файле и три клички собак во втором . Напишите программу, которая пытается прочитать эти файлы и выводит их содержимое на экран . Заключите свой код в блок try-except для перехвата исключения FileNotFoundError и вывода понятного сообщения об отсутствии файла . Переместите один из файлов в другое место файловой системы; убедитесь в том, что код блока except выполняется, как и положено . 10-9 . Ошибки без уведомления: измените блок except из упражнения 10-8 так, чтобы при отсутствии файла программа продолжала работу, не уведомляя пользователя о проблеме . 10-10 . Частые слова: зайдите на сайт проекта «Гутенберг» (http://gutenberg .org/) и найдите несколько книг для анализа . Загрузите текстовые файлы этих произведений или скопируйте текст из браузера в текстовый файл на вашем компьютере . Для подсчета количества вхождений слова или выражения в строку можно воспользоваться методом count() . Например, следующий код подсчитывает количество вхождений ‘row’ в строке: >>> line = «Row, row, row your boat» >>> line.count(‘row’) 2 >>> line.lower().count(‘row’) 3
Глава 10 • Файлы и исключения
Обратите внимание: преобразование строки к нижнему регистру функцией lower() позволяет найти все вхождения искомого слова независимо от регистра . Напишите программу, которая читает файлы из проекта «Гутенберг» и определяет количество вхождений слова ‘the’ в каждом тексте .
Сохранение данных Многие ваши программы будут запрашивать у пользователя информацию. Например, пользователь может вводить настройки для компьютерной игры или данные для визуального представления. Чем бы ни занималась ваша программа, информация, введенная пользователем, будет сохраняться в структурах данных (таких, как списки или словари). Когда пользователь закрывает программу, введенную им информацию почти всегда следует сохранять на будущее. Простейший способ сохранения данных основан на использовании модуля json. Модуль json обеспечивает запись простых структур данных Python в файл и загрузку данных из файла при следующем запуске программы. Модуль json также может использоваться для обмена данными между программами Python. Более того, формат данных JSON не привязан к Python, поэтому данные в этом формате можно передавать программам, написанным на многих других языках программирования. Это полезный и универсальный формат, который к тому же легко изучается. ПРИМЕЧАНИЕ Формат JSON (JavaScript Object Notation) был изначально разработан для JavaScript . Впрочем, с того времени он стал использоваться во многих языках, включая Python .
Функции json .dump() и json .load() Напишем короткую программу для сохранения набора чисел и другую программу, которая будет читать эти числа обратно в память. Первая программа использует функцию json.dump(), а вторая — функцию json.load(). Функция json.dump() получает два аргумента: сохраняемые данные и объект файла, используемый для сохранения. В следующем примере json.dump() используется для сохранения списка чисел: number_writer.py import json numbers = [2, 3, 5, 7, 11, 13] filename = ‘numbers.json’ with open(filename, ‘w’) as f_obj: json.dump(numbers, f_obj)
Программа импортирует модуль json и создает список чисел для работы. В точке выбирается имя файла для хранения списка. Обычно для таких файлов принято использовать расширение .json, указывающее, что данные в файле хранятся в фор-
мате JSON. Затем файл открывается в режиме записи, чтобы модуль json мог записать в него данные . В точке функция json.dump() используется для сохранения списка numbers в файле numbers .json. Программа ничего не выводит, но давайте откроем файл numbers .json и посмотрим на его содержимое. Данные хранятся в формате, очень похожем на код Python: [2, 3, 5, 7, 11, 13]
А теперь напишем следующую программу, которая использует json.load() для чтения списка обратно в память: number_reader.py import json filename = ‘numbers.json’ with open(filename) as f_obj: numbers = json.load(f_obj) print(numbers)
В точке для чтения данных используется тот же файл, в который эти данные были записаны. На этот раз файл открывается в режиме чтения, потому что Python нужно только прочитать данные из файла . В точке функция json.load() используется для загрузки информации из numbers .json; эта информация сохраняется в переменной numbers. Наконец, программа выводит прочитанный список. Как видите, этот тот же список, который был создан в программе number_writer .py: [2, 3, 5, 7, 11, 13]
Модуль json позволяет организовать простейший обмен данными между программами.
Сохранение и чтение данных, сгенерированных пользователем Сохранение с использованием модуля json особенно полезно при работе с данными, сгенерированными пользователем, потому что без сохранения эта информация будет потеряна при остановке программы. В следующем примере программа запрашивает у пользователя имя при первом запуске программы и «вспоминает» его при повторных запусках. Начнем с сохранения имени пользователя: remember_me.py import json username = input(«What is your name? «) filename = ‘username.json’ with open(filename, ‘w’) as f_obj: json.dump(username, f_obj) print(«We’ll remember you when you come back, » + username + «!»)
Глава 10 • Файлы и исключения
В точке программа запрашивает имя пользователя для сохранения. Затем вызывается функция json.dump(), которой передается имя пользователя и объект файла; функция сохраняет имя пользователя в файле . Далее выводится сообщение о том, что имя пользователя было сохранено : What is your name? Eric We’ll remember you when you come back, Eric!
А теперь напишем другую программу, которая приветствует пользователя по ранее сохраненному имени: greet_user.py import json filename = ‘username.json’ with open(filename) as f_obj: username = json.load(f_obj) print(«Welcome back, » + username + «!»)
В точке вызов json.load() читает информацию из файла username .json в переменную username. После того как данные будут успешно прочитаны, мы можем поприветствовать пользователя по имени : Welcome back, Eric!
Теперь эти две программы необходимо объединить в один файл. Когда пользователь запускает remember_me .py, программа должна взять имя пользователя из памяти, если это возможно; соответственно, программа начинается с блока try, который пытается прочитать имя пользователя. Если файл username .json не существует, блок except запросит имя пользователя и сохранит его в username .json на будущее: remember_me.py import json # Программа загружает имя пользователя, если оно было сохранено ранее. # В противном случае она запрашивает имя пользователя и сохраняет его. filename = ‘username.json’ try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: username = input(«What is your name? «) with open(filename, ‘w’) as f_obj: json.dump(username, f_obj) print(«We’ll remember you when you come back, » + username + «!») else: print(«Welcome back, » + username + «!»)
Никакого нового кода здесь нет; просто блоки кода из двух предыдущих примеров были объединены в один файл. В точке программа пытается открыть файл username .json. Если файл существует, программа читает имя пользователя в память и выводит сообщение, приветствующее пользователя, в блоке else. Если
программа запускается впервые, то файл username .json не существует, и происходит исключение FileNotFoundError . Python переходит к блоку except, в котором пользователю предлагается ввести имя . Затем программа вызывает json.dump() для сохранения имени пользователя и выводит приветствие . Какой бы блок ни выполнялся, результатом является имя пользователя и соответствующее сообщение. При первом запуске программы результат выглядит так: What is your name? Eric We’ll remember you when you come back, Eric!
Если же программа уже была выполнена хотя бы один раз, результат будет таким: Welcome back, Eric!
Рефакторинг Часто возникает типичная ситуация: код работает, но вы понимаете, что его структуру можно усовершенствовать, разбив его на функции, каждая из которых решает свою конкретную задачу. Этот процесс называется рефакторингом (или переработкой). Рефакторинг делает ваш код более чистым, понятным и простым в расширении. В процессе рефакторинга remember_me .py мы можем переместить основную часть логики в одну или несколько функций. Основной задачей remember_me .py является вывод приветствия для пользователя, поэтому весь существующий код будет перемещен в функцию greet_user(): remember_me.py import json def greet_user(): «»»Приветствует пользователя по имени.»»» filename = ‘username.json’ try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: username = input(«What is your name? «) with open(filename, ‘w’) as f_obj: json.dump(username, f_obj) print(«We’ll remember you when you come back, » + username + «!») else: print(«Welcome back, » + username + «!») greet_user()
С переходом на функцию комментарии дополняются строкой документации, которая описывает работу кода в текущей версии . Код становится немного чище, но функция greet_user() не только приветствует пользователя — она также загружает хранимое имя пользователя, если оно существует, и запрашивает новое имя, если оно не было сохранено ранее.
Глава 10 • Файлы и исключения
Переработаем функцию greet_user(), чтобы она не решала столько разных задач. Начнем с перемещения кода загрузки хранимого имени пользователя в отдельную функцию: import json def get_stored_username(): «»»Получает хранимое имя пользователя, если оно существует.»»» filename = ‘username.json’ try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: return None else: return username def greet_user(): «»»Приветствует пользователя по имени.»»» username = get_stored_username() if username: print(«Welcome back, » + username + «!») else: username = input(«What is your name? «) filename = ‘username.json’ with open(filename, ‘w’) as f_obj: json.dump(username, f_obj) print(«We’ll remember you when you come back, » + username + «!») greet_user()
Новая функция get_stored_username() имеет четкое предназначение, изложенное в строке документации . Эта функция читает и возвращает сохраненное имя пользователя, если его удается найти. Если файл username .json не существует, то функция возвращает None . И это правильно: функция должна возвращать либо ожидаемое значение, либо None. Это позволяет провести простую проверку возвращаемого значения функции. В точке программа выводит приветствие для пользователя, если попытка получения имени пользователя была успешной; в противном случае программа запрашивает новое имя пользователя. Из функции greet_user() стоит вынести еще один блок кода. Если имя пользователя не существует, то код запроса нового имени должен размещаться в функции, специализирующейся на решении этой задачи: import json def get_stored_username(): «»»Получает хранимое имя пользователя, если оно существует.»»» . def get_new_username(): «»»Запрашивает новое имя пользователя.»»» username = input(«What is your name? «) filename = ‘username.json’ with open(filename, ‘w’) as f_obj: json.dump(username, f_obj) return username
def greet_user(): «»»Приветствует пользователя по имени.»»» username = get_stored_username() if username: print(«Welcome back, » + username + «!») else: username = get_new_username() print(«We’ll remember you when you come back, » + username + «!») greet_user()
Каждая функция в окончательной версии remember_me .py имеет четкое, конкретное предназначение. Мы вызываем greet_user(), и эта функция выводит нужное приветствие: либо для уже знакомого, либо для нового пользователя. Для этого функция вызывает функцию get_stored_username(), которая отвечает только за чтение хранимого имени пользователя (если оно есть). Наконец, функция greet_user() при необходимости вызывает функцию get_new_username(), которая отвечает только за получение нового имени пользователя и его сохранение. Такое «разделение обязанностей» является важнейшим аспектом написания чистого кода, простого в сопровождении и расширении. УПРАЖНЕНИЯ 10-11 . Любимое число: напишите программу, которая запрашивает у пользователя его любимое число . Воспользуйтесь функцией json .dump() для сохранения этого числа в файле . Напишите другую программу, которая читает это значение и выводит сообщение: «Я знаю ваше любимое число! Это _____» . 10-12 . Сохраненное любимое число: объедините две программы из упражнения 10-11 в один файл . Если число уже сохранено, сообщите его пользователю, а если нет — запросите любимое число пользователя и сохраните в файле . Выполните программу дважды, чтобы убедиться в том, что она работает . 10-13 . Проверка пользователя: последняя версия remember_me .py предполагает, что пользователь либо уже ввел свое имя, либо программа выполняется впервые . Ее нужно изменить на тот случай, если текущий пользователь не является тем человеком, который последним использовал программу . Прежде чем выводить приветствие в greet_user(), спросите пользователя, правильно ли определено имя пользователя . Если ответ будет отрицательным, вызовите get_new_ username() для получения правильного имени пользователя .
Итоги В этой главе вы научились работать с файлами. Вы узнали, как прочитать сразу весь файл и как читать его содержимое по строкам. Вы научились записывать в файл и присоединять текст в конец файла, познакомились с исключениями и средствами обработки исключений, возникающих в программе. В завершающей части главы рассматриваются структуры данных Python для сохранения введенной информации, чтобы пользователю не приходилось каждый раз вводить данные заново при каждом запуске программы. В главе 11 мы займемся эффективной организацией тестирования вашего кода. Тестирование поможет убедиться в том, что написанный код работает правильно, а также выявит ошибки, внесенные в процессе расширения уже написанных программ.
Вместе с функциями и классами вы также можете написать тесты для своего кода. Тестирование доказывает, что код работает так, как положено, для любых разновидностей входных данных, которые он может получать. Тесты позволят вам быть уверенным в том, что код будет работать правильно и тогда, когда вашими программами станут пользоваться другие люди. Тестирование при добавлении нового кода гарантирует, что внесенные изменения не повлияют на текущее поведение программы. Все программисты допускают ошибки, поэтому каждый программист должен часто тестировать свой код и выявлять ошибки до того, как с ними столкнутся другие пользователи. В этой главе вы научитесь тестировать код средствами модуля Python unittest. Вы узнаете, как построить тестовые сценарии, как проверить, выдает ли программа для конкретных входных данных ожидаемый результат и как тестировать функции и классы. Также вы научитесь оценивать, сколько тестов нужно написать для проекта.
Тестирование функции Чтобы потренироваться в тестировании, нам понадобится код. Ниже приведена простая функция, которая получает имя и фамилию и возвращает отформатированное полное имя: name_function.py def get_formatted_name(first, last): «»»Строит отформатированное полное имя.»»» full_name = first + ‘ ‘ + last return full_name.title()
Функция get_formatted_name() строит полное имя из имени и фамилии, разделив их пробелом, преобразует первый символ каждого слова к верхнему регистру и возвращает полученный результат. Чтобы убедиться в том, что функция get_ formatted_name() работает правильно, мы напишем программу, использующую эту функцию. Программа names .py запрашивает у пользователя имя и фамилию и выдает отформатированное полное имя: names.py from name_function import get_formatted_name
print(«Enter ‘q’ at any time to quit.») while True: first = input(«\nPlease give me a first name: «) if first == ‘q’: break last = input(«Please give me a last name: «) if last == ‘q’: break formatted_name = get_formatted_name(first, last) print(«\tNeatly formatted name: » + formatted_name + ‘.’)
Программа импортирует функцию g e t _ f o r m a t t e d _ n a m e ( ) из модуля name_function .py . Пользователь вводит последовательность имен и фамилий и видит, что программа сгенерировала отформатированные полные имена: Enter ‘q’ at any time to quit. Please give me a first name: janis Please give me a last name: joplin Neatly formatted name: Janis Joplin. Please give me a first name: bob Please give me a last name: dylan Neatly formatted name: Bob Dylan. Please give me a first name: q
Как видно из листинга, имена сгенерированы правильно. Но допустим, вы решили изменить функцию get_formatted_name(), чтобы она также работала со вторыми именами. При этом необходимо проследить за тем, чтобы функция не перестала правильно работать для имен, состоящих только из имени и фамилии. Чтобы протестировать код, можно запустить names .py и для проверки вводить имя из двух компонентов (скажем, Janis Joplin) при каждом изменении get_formatted_name(), но это довольно утомительно. К счастью, Python предоставляет эффективный механизм автоматизации тестирования вывода функций. При автоматизации тестирования get_formatted_name() вы будете уверены в том, что функция успешно работает для всех видов имен, для которых написаны тесты.
Прохождение теста Вы не сразу привыкнете к синтаксису создания тестовых сценариев, но после того, как тестовый сценарий будет создан, вы сможете легко добавить новые модульные тесты для своих функций. Чтобы написать тестовый сценарий для функции, импортируйте модуль unittest и функцию, которую необходимо протестировать. Затем создайте класс, наследующий от unittest.TestCase, и напишите серию методов для тестирования различных аспектов поведения своей функции. Ниже приведен тестовый сценарий с одним методом, который проверяет, что функция get_formatted_name() правильно работает при передаче имени и фамилии:
Глава 11 • Тестирование
test_name_function.py import unittest from name_function import get_formatted_name class NamesTestCase(unittest.TestCase): «»»Тесты для ‘name_function.py’.»»»
def test_first_last_name(self): «»»Имена вида ‘Janis Joplin’ работают правильно?»»» formatted_name = get_formatted_name(‘janis’, ‘joplin’) self.assertEqual(formatted_name, ‘Janis Joplin’)
Сначала мы импортируем unittest и тестируемую функцию get_formatted_name(). В точке создается класс NamesTestCase, который содержит серию модульных тестов для get_formatted_name(). Имя класса выбирается произвольно, но лучше выбрать имя, связанное с функцией, которую вы собираетесь тестировать, и включить в имя класса слово Test. Этот класс должен наследовать от класса unittest. TestCase, чтобы Python знал, как запустить написанные вами тесты. Класс NamesTestCase содержит один метод, который тестирует всего один аспект get_formatted_name() — правильность форматирования имен, состоящих только из имени и фамилии. Мы назвали этот метод test_first_last_name(). Любой метод, имя которого начинается с test_, будет выполняться автоматически при запуске test_name_function .py. В тестовом методе вызывается тестируемая функция и сохраняется возвращаемое значение, которое необходимо проверить. В данном примере вызывается функция get_formatted_name() с аргументами ‘janis’ и ‘joplin’, а результат сохраняется в переменной formatted_name . В точке используется одна из самых полезных особенностей unittest: метод assert. Методы assert проверяют, что полученный результат соответствует тому результату, который вы рассчитывали получить. В данном случае известно, что функция get_formatted_name() должна вернуть полное имя с пробелами и капитализацией слов, поэтому переменная formatted_name должна содержать текст «Janis Joplin». Чтобы убедиться в этом, мы используем метод assertEqual() из модуля unittest и передаем ему переменную formatted_name и строку ‘Janis Joplin’. Вызов self.assertEqual(formatted_name, ‘Janis Joplin’)
означает: «Сравни значение formatted_name со строкой ‘Janis Joplin’. Если они равны, как и ожидалось, — хорошо. Но если они не равны, обязательно сообщи мне!» Строка unittest.main() приказывает Python выполнить тесты из этого файла. При запуске test_name_function .py будет получен следующий результат: . ———————————————————————Ran 1 test in 0.000s
Точка в первой строке вывода сообщает, что один тест прошел успешно. Следующая строка говорит, что для выполнения одного теста Python потребовалось менее 0,001 секунды. Наконец, завершающее сообщение OK говорит о том, что все модульные тесты в тестовом сценарии прошли. Этот результат показывает, что функция get_formatted_name() успешно работает для полных имен, состоящих из имени и фамилии, если только функция не была изменена. В случае внесения изменений в get_formatted_name() тест можно запустить снова. И если тестовый сценарий снова пройдет, мы будем знать, что функция продолжает успешно работать с полными именами из двух компонентов.
Сбой теста Как выглядит сбойный тест? Попробуем изменить функцию get_formatted_name(), чтобы она работала со вторыми именами, — но сделаем это так, чтобы она перестала работать с полными именами из двух компонентов. Новая версия get_formatted_name() с дополнительным аргументом второго имени выглядит так: name_function.py def get_formatted_name(first, middle, last): «»»Строит отформатированное полное имя.»»» full_name = first + ‘ ‘ + middle + ‘ ‘ + last return full_name.title()
Эта версия должна работать для полных имен из трех компонентов, но тестирование показывает, что она перестала работать для полных имен из двух компонентов. На этот раз файл test_name_function .py выдает следующий результат: E ====================================================================== ERROR: test_first_last_name (__main__.NamesTestCase) ——————————————————————— Traceback (most recent call last): File «test_name_function.py», line 8, in test_first_last_name formatted_name = get_formatted_name(‘janis’, ‘joplin’) TypeError: get_formatted_name() missing 1 required positional argument: ‘last’ ——————————————————————— Ran 1 test in 0.000s FAILED (errors=1)
На этот раз информации гораздо больше, потому что при сбое теста разработчик должен знать, почему это произошло. Вывод начинается с одной буквы E , которая сообщает, что один модульный тест в тестовом сценарии привел к ошибке. Затем мы видим, что ошибка произошла в тесте test_first_last_name() в NamesTestCase . Конкретная информация о сбойном тесте особенно важна в том случае, если тестовый сценарий состоит из нескольких модульных тестов. В точке мы видим стандартную трассировку, из которой понятно, что вызов функции get_formatted_name(‘janis’, ‘joplin’) перестал работать из-за необходимого позиционного аргумента.
Глава 11 • Тестирование
Также из вывода следует, что был выполнен один модульный тест . Наконец, дополнительное сообщение информирует, что тестовый сценарий в целом не прошел и произошла одна ошибка при выполнении тестового сценария . Эта информация размещается в конце вывода, чтобы она была видна сразу; разработчику не придется прокручивать длинный протокол, чтобы узнать количество сбойных тестов.
Реакция на сбойный тест Что делать, если тест не проходит? Если предположить, что проверяются правильные условия, прохождение теста означает, что функция работает правильно, а сбой — что в новом коде добавилась ошибка. Итак, если тест не прошел, изменять нужно не тест, а код, который привел к сбою теста. Проанализируйте изменения, внесенные в функцию, и разберитесь, как они привели к нарушению ожидаемого поведения. В данном случае у функции get_formatted_name() было всего два обязательных параметра: имя и фамилия. Теперь она требует три обязательных параметра: имя, второе имя и фамилию. Добавление обязательного параметра для второго имени нарушило ожидаемое поведение get_formatted_name(). В таком случае лучше всего сделать параметр второго имени необязательным. После этого тесты для имен с двумя компонентами снова будут проходить, и программа сможет получать также вторые имена. Изменим функцию get_formatted_name(), чтобы параметр второго имени перестал быть обязательным, и снова выполним тестовый сценарий. Если он пройдет, можно переходить к проверке правильности обработки вторых имен. Чтобы сделать второе имя необязательным, нужно переместить параметр middle в конец списка параметров в определении функции и задать ему пустое значение по умолчанию. Также будет добавлена проверка if, которая правильно строит полное имя в зависимости от того, передается второе имя или нет: name_function.py def get_formatted_name(first, last, middle=»): «»»Строит отформатированное полное имя.»»» if middle: full_name = first + ‘ ‘ + middle + ‘ ‘ + last else: full_name = first + ‘ ‘ + last return full_name.title()
В новой версии get_formatted_name() параметр middle не обязателен. Если второе имя передается функции (if middle:), то полное имя будет содержать имя, второе имя и фамилию. В противном случае полное имя состоит только из имени и фамилии. Теперь функция должна работать для обеих разновидностей имен. Чтобы узнать, работает ли функция для имен из двух компонентов, снова запустите test_name_function .py: . ———————————————————————Ran 1 test in 0.000s
Теперь тестовый сценарий проходит. Такой исход идеален; он означает, что функция снова работает для имен из двух компонентов и нам не пришлось тестировать функцию вручную. Исправить ошибку было несложно, потому что сбойный тест помог выявить новый код, нарушивший существующее поведение.
Добавление новых тестов Теперь мы знаем, что get_formatted_name() работает для простых имен, и можем написать второй тест для имен из трех компонентов. Для этого в класс NamesTestCase добавляется еще один метод: import unittest from name_function import get_formatted_name class NamesTestCase(unittest.TestCase): «»»Тесты для ‘name_function.py’.»»» def test_first_last_name(self): «»»Работают ли такие имена, как ‘Janis Joplin’?»»» formatted_name = get_formatted_name(‘janis’, ‘joplin’) self.assertEqual(formatted_name, ‘Janis Joplin’)
def test_first_last_middle_name(self): «»»Работают ли такие имена, как ‘Wolfgang Amadeus Mozart’?»»» formatted_name = get_formatted_name( ‘wolfgang’, ‘mozart’, ‘amadeus’) self.assertEqual(formatted_name, ‘Wolfgang Amadeus Mozart’)
Новому методу присваивается имя test_first_last_middle_name(). Имя метода должно начинаться с test_, чтобы этот метод выполнялся автоматически при запуске test_name_function .py. В остальном имя выбирается так, чтобы оно четко показывало, какое именно поведение get_formatted_name() мы тестируем. В результате при сбое теста вы сразу видите, к каким именам он относится. Не нужно опасаться длинных имен методов в классах TestCase: имена должны быть содержательными, чтобы донести информацию до разработчика в случае сбоя, а поскольку Python вызывает их автоматически, вам никогда не придется вручную вводить эти имена при вызове. Чтобы протестировать функцию, мы вызываем get_formatted_name() c тремя компонентами , после чего используем assertEqual() для проверки того, что возвращенное полное имя совпадает с ожидаемым. При повторном запуске test_ name_function .py оба теста проходят успешно: .. ———————————————————————Ran 2 tests in 0.000s
Отлично! Теперь мы знаем, что функция по-прежнему работает с именами из двух компонентов, как Janis Joplin, но можем быть уверены в том, что она
Глава 11 • Тестирование
сработает и для имен с тремя компонентам — такими, как Wolfgang Amadeus Mozart. УПРАЖНЕНИЯ 11-1 . Город, страна: напишите функцию, которая получает два параметра: название страны и название города . Функция должна возвращать одну строку в формате «Город, Страна», например «Santiago, Chile» . Сохраните функцию в модуле с именем city_ functions .py . Создайте файл test_cities .py для тестирования только что написанной функции (не забудьте импортировать unittest и тестируемую функцию) . Напишите метод test_city_country() для проверки того, что вызов функции с такими значениями, как ‘santiago’ и ‘chile’, дает правильную строку . Запустите test_cities .py и убедитесь в том, что тест test_city_country() проходит успешно . 11-2 . Население: измените свою функцию так, чтобы у нее был третий обязательный параметр — население . В новой версии функция должна возвращать одну строку вида «Santiago, Chile — population 5000000 .» Снова запустите программу test_cities .py . Убедитесь в том, что тест test_city_country() на этот раз не проходит . Измените функцию так, чтобы параметр населения стал необязательным . Снова запустите test_cities .py и убедитесь в том, что тест test_city_country() проходит успешно . Напишите второй тест test_city_country_population(), который проверяет вызов функции со значениями ‘santiago’, ‘chile’ и ‘population=5000000’ . Снова запустите test_cities .py и убедитесь в том, что новый тест проходит успешно .
Тестирование класса В первой части этой главы мы писали тесты для отдельной функции. Сейчас мы займемся написанием тестов для класса. Классы будут использоваться во многих ваших программах, поэтому возможность доказать, что ваши классы работают правильно, будет безусловно полезной. Если тесты для класса, над которым вы работаете, проходят успешно, вы можете быть уверены в том, что усовершенствования класса не приведут к случайному нарушению его текущего поведения.
Разные методы assert Класс unittest.TestCase содержит целое семейство проверочных методов assert. Как упоминалось ранее, эти методы проверяют, выполняется ли условие, которое должно выполняться в определенной точке вашего кода. Если условие истинно, как и предполагалось, то ваши ожидания относительно поведения части вашей программы подтверждаются; вы можете быть уверены в отсутствии ошибок. Если же условие, которое должно быть истинным, окажется ложным, то Python выдает исключение. В табл. 11.1 перечислены шесть часто используемых методов assert. С их помощью можно проверить, что возвращаемые значения равны или не равны ожидаемым, что значения равны True или False или что значения входят или не входят в заданный список. Эти методы могут использоваться только в классах, наследующих
от unittest.TestCase; рассмотрим пример использования такого метода в контексте тестирования реального класса. Таблица 11.1. Методы assert, предоставляемые модулем unittest Метод
Проверяет, что a == b
Проверяет, что a != b
Проверяет, что значение x истинно
Проверяет, что значение x ложно
Проверяет, что элемент входит в список
Проверяет, что элемент не входит в список
Класс для тестирования Тестирование класса имеет много общего с тестированием функции — значительная часть работы направлена на тестирование поведения методов класса. Впрочем, существуют и различия, поэтому мы напишем отдельный класс для тестирования. Возьмем класс для управления проведением анонимных опросов: survey.py class AnonymousSurvey(): «»»Сбор анонимных ответов на опросы.»»»
def __init__(self, question): «»»Сохраняет вопрос и готовится к сохранению ответов.»»» self.question = question self.responses = []
def show_question(self): «»»Выводит вопрос.»»» print(question)
def store_response(self, new_response): «»»Сохраняет один ответ на опрос.»»» self.responses.append(new_response)
def show_results(self): «»»Выводит все полученные ответы.»»» print(«Survey results:») for response in responses: print(‘- ‘ + response)
Класс начинается с вопроса, предоставленного администратором , и включает пустой список для хранения ответов. Класс содержит методы для вывода вопроса , добавления нового ответа в список ответов и вывода всех ответов, хранящихся в списке . Чтобы создать экземпляр на основе этого класса, необходимо предоставить вопрос. После того как будет создан экземпляр, представляющий конкретный опрос, программа выводит вопрос методом show_
Глава 11 • Тестирование
question(), сохраняет ответ методом store_response() и выводит результаты вызовом show_results().
Чтобы продемонстрировать, что класс AnonymousSurvey работает, напишем программу, которая использует этот класс: language_survey.py from survey import AnonymousSurvey # Определение вопроса с созданием экземпляра AnonymousSurvey. question = «What language did you first learn to speak?» my_survey = AnonymousSurvey(question) # Вывод вопроса и сохранение ответов. my_survey.show_question() print(«Enter ‘q’ at any time to quit.\n») while True: response = input(«Language: «) if response == ‘q’: break my_survey.store_response(response) # Вывод результатов опроса. print(«\nThank you to everyone who participated in the survey!») my_survey.show_results()
Программа определяет вопрос и создает объект AnonymousSurvey на базе этого вопроса. Программа вызывает метод show_question() для вывода вопроса, после чего переходит к получению ответов. Каждый ответ сохраняется сразу же при получении. Когда ввод ответов был завершен (пользователь ввел q), метод show_results() выводит результаты опроса: What language did you first learn to speak? Enter ‘q’ at any time to quit. Language: Language: Language: Language: Language:
English Spanish English Mandarin q
Thank you to everyone who participated in the survey! Survey results: — English — Spanish — English — Mandarin
Этот класс работает для простого анонимного опроса. Но допустим, вы решили усовершенствовать класс AnonymousSurvey и модуль survey, в котором он находится. Например, каждому пользователю будет разрешено ввести несколько ответов. Или вы напишете метод, который будет выводить только уникальные ответы и сообщать, сколько раз был дан тот или иной ответ. Или вы напишете другой класс для проведения неанонимных опросов.
Реализация таких изменений грозит повлиять на текущее поведение класса AnonymousSurvey. Например, может оказаться, что поддержка ввода нескольких ответов случайно повлияет на процесс обработки одиночных ответов. Чтобы гарантировать, что доработка модуля не нарушит существующего поведения, для класса нужно написать тесты.
Тестирование класса AnonymousSurvey Напишем тест, проверяющий всего один аспект поведения AnonymousSurvey. Этот тест будет проверять, что один ответ на опрос сохраняется правильно. После того как метод будет сохранен, метод assertIn() проверяет, что он действительно находится в списке ответов: test_survey.py import unittest from survey import AnonymousSurvey class TestAnonmyousSurvey(unittest.TestCase): «»»Тесты для класса AnonymousSurvey»»»
def test_store_single_response(self): «»»Проверяет, что один ответ сохранен правильно.»»» question = «What language did you first learn to speak?» my_survey = AnonymousSurvey(question) my_survey.store_response(‘English’) self.assertIn(‘English’, my_survey.responses)
Программа начинается с импортирования модуля unittest и тестируемого класса AnonymousSurvey. Тестовый сценарий TestAnonymousSurvey, как и в предыдущих случаях, наследует от unittest.TestCase . Первый тестовый метод проверяет, что сохраненный ответ действительно попадает в список ответов опроса. Этому методу присваивается хорошее содержательное имя test_store_single_response() . Если тест не проходит, имя метода в выходных данных сбойного теста ясно показывает, что проблема связана с сохранением отдельного ответа на опрос. Чтобы протестировать поведение класса, необходимо создать экземпляр класса. В точке создается экземпляр с именем my_survey для вопроса «What language did you first learn to speak?», Один ответ (English) сохраняется с использованием метода store_response(). Затем программа убеждается в том, что ответ был сохранен правильно; для этого она проверяет, что значение English присутствует в списке my_survey.responses . При запуске программы test_survey .py тест проходит успешно: . ———————————————————————Ran 1 test in 0.001s
Глава 11 • Тестирование
Неплохо, но опрос с одним ответом вряд ли можно назвать полезным. Убедимся в том, что три ответа сохраняются правильно. Для этого в TestAnonymousSurvey добавляется еще один метод: import unittest from survey import AnonymousSurvey class TestAnonymousSurvey(unittest.TestCase): «»»Тесты для класса AnonymousSurvey»»» def test_store_single_response(self): «»»Проверяет, что один ответ сохранен правильно.»»» .
def test_store_three_responses(self): «»»Проверяет, что три ответа были сохранены правильно.»»» question = «What language did you first learn to speak?» my_survey = AnonymousSurvey(question) responses = [‘English’, ‘Spanish’, ‘Mandarin’] for response in responses: my_survey.store_response(response) for response in responses: self.assertIn(response, my_survey.responses)
Новому методу присваивается имя test_store_three_responses(). Мы создаем объект опроса по аналогии с тем, как это делалось в test_store_single_response(). Затем определяется список, содержащий три разных ответа , и для каждого из этих ответов вызывается метод store_response(). После того как ответы будут сохранены, следующий цикл проверяет, что каждый ответ теперь присутствует в my_survey.responses . Если снова запустить test_survey .py, оба теста (для одного ответа и для трех ответов) проходят успешно: .. ———————————————————————Ran 2 tests in 0.000s
Все прекрасно работает. Тем не менее тесты выглядят немного однообразно, поэтому мы воспользуемся еще одной возможностью unittest для повышения их эффективности.
Метод setUp() В программе test_survey .py в каждом тестовом методе создавался новый экземпляр AnonymousSurvey, а также новые ответы. Класс unittest.TestCase содержит метод
setUp(), который позволяет создать эти объекты один раз, а затем использовать их в каждом из тестовых методов. Если в класс TestCase включается метод setUp(), Python выполняет метод setUp() перед запуском каждого метода, имя которого начинается с test_. Все объекты, созданные методом setUp(), становятся доступными
во всех написанных вами тестовых методах. Используем setUp() для создания экземпляра AnonymousSurvey и набора ответов, которые могут использоваться в test_store_single_response() и test_store_ three_responses(): import unittest from survey import AnonymousSurvey class TestAnonymousSurvey(unittest.TestCase): «»»Тесты для класса AnonymousSurvey.»»»
def setUp(self): «»» Создание опроса и набора ответов для всех тестовых методов. «»» question = «What language did you first learn to speak?» self.my_survey = AnonymousSurvey(question) self.responses = [‘English’, ‘Spanish’, ‘Mandarin’]
def test_store_single_response(self): «»»Проверяет, что один ответ сохранен правильно.»»» self.my_survey.store_response(self.responses[0]) self.assertIn(self.responses[0], self.my_survey.responses) def test_store_three_responses(self): «»»Проверяет, что три ответа были сохранены правильно.»»» for response in self.responses: self.my_survey.store_response(response) for response in self.responses: self.assertIn(response, self.my_survey.responses) unittest.main()
Метод setUp() решает две задачи: он создает экземпляр опроса и список ответов . Каждый из этих атрибутов снабжается префиксом self, поэтому он может использоваться где угодно в классе. Это обстоятельство упрощает два тестовых метода, потому что им уже не нужно создавать экземпляр опроса или ответы. Метод test_store_single_response() убеждается в том, что первый ответ в self. responses — self.responses[0] — сохранен правильно, а метод test_store_single_ response() убеждается в том, что правильно были сохранены все три ответа в self. responses. При повторном запуске test_survey .py оба теста по-прежнему проходят. Эти тесты будут особенно полезными при расширении AnonymousSurvey с поддержкой нескольких ответов для каждого участника. После внесения изменений вы можете повторить тесты и убедиться в том, что изменения не повлияли на возможность сохранения отдельного ответа или серии ответов.
Глава 11 • Тестирование
При тестировании классов, написанных вами, метод setUp() упрощает написание тестовых методов. Вы создаете один набор экземпляров и атрибутов в setUp(), а затем используете эти экземпляры во всех тестовых методах. Это намного проще и удобнее, чем создавать новый набор экземпляров и атрибутов в каждом тестовом методе. ПРИМЕЧАНИЕ Во время работы тестового сценария Python выводит один символ для каждого модульного теста после его завершения . Для прошедшего теста выводится точка; если при выполнении произошла ошибка, выводится символ E, а если не прошла проверка условия assert, выводится символ F . Вот почему вы увидите другое количество точек и символов в первой строке вывода при выполнении ваших тестовых сценариев . Если выполнение тестового сценария занимает слишком много времени, потому что сценарий содержит слишком много тестов, эти символы дадут некоторое представление о количестве прошедших тестов . УПРАЖНЕНИЯ 11-3 . Работник: напишите класс Employee, представляющий работника . Метод __init__() должен получать имя, фамилию и ежегодный оклад; все эти значения должны сохраняться в атрибутах . Напишите метод give_raise(), который по умолчанию увеличивает ежегодный оклад на $5000 — но при этом может получать другую величину прибавки . Напишите тестовый сценарий для Employee . Напишите два тестовых метода, test_give_ default_raise() и test_give_custom_raise() . Используйте метод setUp(), чтобы вам не приходилось заново создавать экземпляр Employee в каждом тестовом методе . Запустите свой тестовый сценарий и убедитесь в том, что оба теста прошли успешно .
Итоги В этой главе вы научились писать тесты для функций и классов с использованием средств модуля unittest. Вы узнали, как написать класс, наследующий от unittest. TestCase, и как писать тестовые методы для проверки конкретных аспектов поведения ваших функций и классов. Вы научились использовать метод setUp() для эффективного создания экземпляров и атрибутов, которые могут использоваться во всех методах для тестирования класса. Тестирование — важная тема, на которую многие новички не обращают внимания. Пока вы делаете свои первые шаги в программировании, писать тесты для простых проектов не нужно. Но как только вы начинаете работать над проектами, требующими значительных затрат ресурсов на разработку, непременно обеспечьте тестирование критических аспектов поведения ваших функций и классов. С эффективными тестами вы можете быть уверены в том, что изменения в проекте не повредят тому, что уже работает, а это развяжет вам руки для усовершенствования кода. Случайно нарушив существующую функциональность, вы немедленно узнаете об этом, что позволит вам быстро исправить проблему. Отреагировать на сбой теста всегда намного проще, чем на отчет об ошибке от недовольного пользователя. Другие программисты будут более уважительно относиться к вашим проектам, если вы включите в них исходные тесты. Они будут чувствовать себя более комфортно,
экспериментируя с вашим кодом, и с большей готовностью присоединятся к участию в ваших проектах. Если вы будете участвовать в проекте, над которым работают другие программисты, вам придется продемонстрировать, что ваш код проходит существующие тесты; кроме того, от вас будут ждать, что вы напишете тесты для нового поведения, добавленного вами в проект. Поэкспериментируйте с тестами и освойтесь с процессом тестирования кода. Пишите тесты для критических аспектов поведения ваших функций и классов, но не стремитесь к полному тестовому покрытию своих ранних проектов (если у вас для этого нет особых причин).
Ч а с т ь II . Проекты Поздравляем! Вы знаете о Python достаточно для того, чтобы взяться за построение интерактивных, осмысленных проектов . Создание собственных проектов закрепит новые навыки и упрочит ваше понимание концепций, представленных в части I . В части II представлены три типа проектов; вы можете взяться за любые из них в том порядке, который вам больше нравится . Ниже приведено краткое описание каждого проекта, чтобы вам было проще решить, с чего начать .
Программирование игры на языке Python В проекте Alien Invasion (главы 12, 13 и 14) мы воспользуемся пакетом Pygame для написания 2D-игры, в которой игрок должен сбивать корабли пришельцев, падающие по экрану с нарастающей скоростью и сложностью. К концу этого проекта вы будете знать достаточно для того, чтобы создавать собственные 2D-игры с использованием Pygame.
Визуализация данных Проект Data Visualization начинается с главы 15. В этом проекте вы научитесь генерировать данные и создавать практичные, элегантные визуализации этих данных с использованием пакетов matplotlib и Pygal. Глава 16 научит вас работать с данными из сетевых источников и передавать их пакету визуализации для построения графиков погодных данных и карты с населением мира. Наконец, глава 17 показывает, как написать программу для автоматической загрузки и визуализации данных. Навыки визуализации пригодятся вам в изучении области анализа данных — в современном мире это умение ценится очень высоко.
Веб-приложения В проекте Web Applications (главы 18, 19 и 20) мы при помощи пакета Django создадим простое веб-приложение для ведения веб-дневника по произвольным темам. Пользователь создает учетную запись с именем и паролем, вводит тему и делает заметки. Вы также научитесь развертывать свои приложения так, чтобы сделать их доступными для потенциальных пользователей со всего мира. После завершения проекта вы сможете заняться построением собственных простых веб-приложений. Кроме того, вы будете готовы к изучению более серьезных ресурсов, посвященных построению приложений с использованием Django.
Проект I . Инопланетное вторжение
Давайте создадим собственную игру! Мы воспользуемся Pygame — подборкой интересных, мощных модулей Python для управления графикой, анимацией и даже звуком, упрощающей построение сложных игр. Pygame берет на себя такие задачи, как перерисовка изображений на экране, что позволяет вам пропустить бульшую часть рутинного, сложного программирования и сосредоточиться на высокоуровневой логике игровой динамики. В этой главе мы настроим Pygame и создадим корабль, который движется влево и вправо и стреляет по приказу пользователя. В следующих двух главах вы создадите флот инопланетного вторжения, а затем займетесь внесением усовершенствований — например, ограничением количества попыток и добавлением таблицы рекордов. Эта глава также научит вас управлять большими проектами, состоящими из многих файлов. Мы часто будем проводить рефакторинг и изменять структуру содержимого файлов, чтобы проект был четко организован, а код оставался эффективным. Программирование игр — идеальный способ совместить изучение языка с развлечением. Написание простой игры поможет вам понять, как пишутся профессиональные игры. В процессе работы над этой главой вводите и запускайте код, чтобы понять, как каждый блок кода участвует в общем игровом процессе. Экспериментируйте с разными значениями и настройками, чтобы лучше понять, как следует организовать взаимодействие с пользователем в ваших собственных играх. ПРИМЕЧАНИЕ Игра Alien Invasion состоит из множества файлов; создайте в своей системе новый каталог с именем alien_invasion . Чтобы команды import работали правильно, все файлы проекта должны находиться в этой папке .
Планирование проекта Построение крупного проекта должно начинаться не с написания кода, а с планирования. План поможет вам направить усилия в нужном направлении и повысит вероятность успешного завершения проекта. Итак, напишем общее описание игрового процесса. Хотя это описание не затрагивает все аспекты игры, оно дает достаточно четкое представление о том, с чего начинать работу:
Каждый игрок управляет кораблем, который находится в середине нижнего края экрана . Игрок перемещает корабль вправо и влево клавишами управления или курсором; клавиша «пробел» используется для стрельбы . В начале игры флот пришельцев находится в верхней части экрана и постепенно опускается вниз, также смещаясь в сторону . Игрок выстрелами уничтожает пришельцев . Если ему удается сбить всех пришельцев, появляется новый флот, который движется быстрее предыдущего . Если пришелец сталкивается с кораблем игрока или доходит до нижнего края экрана, игрок теряет корабль . Если игрок теряет все три корабля, игра заканчивается .
В первой фазе разработки мы создадим корабль, который может двигаться вправо и влево. Корабль должен стрелять из пушки, когда игрок нажимает клавишу «пробел». Когда это поведение будет реализовано, мы сможем заняться пришельцами и доработкой игрового процесса.
Установка Pygame Прежде чем браться за программирование, установите пакет Pygame. Ниже описан процесс установки в Linux, OS X и Microsoft Windows. Если вы используете Python 3 в системе Linux или если вы работаете в OS X, для установки Pygame используется pip — программа, управляющая загрузкой и установкой пакетов Python. Процедура установки пакетов с использованием pip описана ниже. Если вы используете Python 2.7 в системе Linux или если вы работаете в Windows, для установки Pygame программа pip вам не понадобится. Вместо этого перейдите к разделу «Установка Pygame в Linux» (с. 229) или «Установка Pygame в Windows» (с. 231). ПРИМЕЧАНИЕ Далее приводятся инструкции по установке pip во всех системах, потому что эта программа понадобится вам для визуализации данных и веб-приложений . Инструкции также доступны по адресу https://www .nostarch .com/pythoncrashcourse/ . Если у вас возникнут проблемы с инструкциями, приведенными ниже, попробуйте загрузить инструкции с сайта — возможно, они сработают .
Установка пакетов Python с использованием pip В последних версиях Python pip устанавливается автоматически, поэтому сначала проверьте, присутствует ли эта программа в вашей системе. В Python 3 программа pip иногда называется pip3. Проверка pip в Linux и OS X Откройте терминальное окно и введите следующую команду: $ pip —version pip 7.0.3 from /usr/local/lib/python3.5/dist-packages (python 3.5) $
Глава 12 • Стреляющий корабль
Если в вашей системе установлена только одна версия Python и вы получили примерно такой результат, переходите к разделу «Установка Pygame в Linux» (с. 229) или «Установка Pygame в OS X» (с. 230). Если вы получите сообщение об ошибке, попробуйте ввести имя pip3 вместо pip. Если ни одна версия не установлена в вашей системе, обратитесь к разделу «Установка pip» (c. 228). Если в вашей системе установлено несколько версий Python, проверьте, что программа pip связана с используемой версией — например, Python 3.5 . Если программа pip связана с правильной версией, переходите к разделу «Установка Pygame в Linux» (с. 229) или «Установка Pygame в OS X» (с. 230). Если версия неправильная, попробуйте ввести имя pip3 вместо pip. Если ни одна команда не работает для вашей версии Python, обратитесь к разделу «Установка pip» (c. 228). Проверка pip в Windows Откройте окно командной строки и введите следующую команду: $ python -m pip —version pip 7.0.3 from C:\Python35\lib\site-packages (python 3.5) $
Если в вашей системе установлена только одна версия Python, и вы получили примерно такой результат, переходите к разделу «Установка Pygame в Windows» (с. 231). Если вы получите сообщение об ошибке, попробуйте ввести имя pip3 вместо pip. Если ни одна версия не установлена в вашей системе, обратитесь к разделу «Установка pip» (с. 228). Если в вашей системе установлено несколько версий Python, проверьте, что программа pip связана с используемой версией, например Python 3.5 . Если программа pip связана с правильной версией, переходите к разделу «Установка Pygame в Windows» (с. 231). Если версия неправильная, попробуйте ввести имя pip3 вместо pip. Если ни одна команда не работает для вашей версии Python, обратитесь к следующему разделу «Установка pip». Установка pip Чтобы установить pip, обратитесь по адресу https://bootstrap.pypa.io/get-pip.py. Сохраните файл, если вам будет предложено. Если код get-pip .py появится в браузере, скопируйте код в текстовый редактор и сохраните в файле с именем get-pip .py. После того как программа get-pip .py будет сохранена на вашем компьютере, ее необходимо будет запустить с административными привилегиями, потому что pip будет устанавливать новые пакеты в вашей системе. ПРИМЕЧАНИЕ Если вы не нашли программу get-pip .py, обратитесь по адресу https://pip .pypa .io/, щелкните на ссылке Installation на левой панели, а затем в разделе Install pip перейдите по ссылке для загрузки get-pip .py .
Установка pip в Linux и OS X Чтобы запустить get-pip .py с административными привилегиями, введите следующую команду: $ sudo python get-pip.py ПРИМЕЧАНИЕ Если терминальный сеанс был запущен командой python3, используйте команду sudo python3 getpip .py .
После выполнения программы введите команду pip —version (или pip3 —version), чтобы убедиться в том, что программа pip была установлена правильно. Установка pip в Windows Чтобы запустить get-pip .py, введите следующую команду: $ python get-pip.py
Если для запуска Python в терминале использовалась другая команда, проследите за тем, чтобы программа get-pip .py запускалась этой же командой — например, python3 get-pip .py или C:\Python35\python get-pip .py. После выполнения программы введите команду python -m pip —version, чтобы убедиться в том, что программа pip была установлена правильно.
Установка Pygame в Linux Если вы используете Python 2.7, установите Pygame при помощи менеджера пакетов. Откройте терминальное окно и введите следующую команду, которая загрузит и установит Pygame в вашей системе: $ sudo apt-get install python-pygame
Проверьте правильность установки в терминальном сеансе; для этого введите следующую команду: $ python >>> import pygame >>>
Если никаких дополнительных сообщений нет, значит, импортирование Pygame прошло успешно, и вы можете переходить к разделу «Создание проекта игры» на с. 231. Если вы используете Python 3, процесс состоит из двух шагов: установки библиотек, от которых зависит Pygame, и загрузки/установки Pygame. Чтобы установить библиотеки, необходимые Pygame, введите следующую команду (если в вашей системе используется другая команда, например python3.5, замените python3-dev на python3 .5-dev). $ sudo apt-get install python3-dev mercurial $ sudo apt-get install libsdl-image1.2-dev libsdl2-dev libsdl-ttf2.0-dev
Глава 12 • Стреляющий корабль
Эти команды установят библиотеки, необходимые для успешного запуска игры Alien Invasion. Если вы хотите включить расширенную функциональность Pygame (например, возможность добавления звуков), добавьте следующие библиотеки: $ sudo apt-get install libsdl-mixer1.2-dev libportmidi-dev $ sudo apt-get install libswscale-dev libsmpeg-dev libavformat-dev libavcode-dev $ sudo apt-get install python-numpy
Теперь установите Pygame следующей командой (используйте pip3, если эта команда соответствует вашей системе): $ pip install —user hg+http://bitbucket.org/pygame/pygame
После небольшой паузы программа сообщает, какие библиотеки были найдены. Нажмите Enter, даже если некоторые библиотеки отсутствуют. Вы увидите сообщение об успешной установке Pygame. Чтобы проверить правильность установки, откройте терминальный сеанс и попробуйте импортировать Pygame: $ python3 >>> import pygame >>>
Если импортирование прошло нормально, переходите к разделу «Создание проекта игры» на с. 231.
Установка Pygame в OS X Для установки некоторых пакетов, от которых зависит Pygame, вам понадобится менеджер пакетов Homebrew. Если в вашей системе он еще не установлен, обращайтесь к приложению А за инструкциями. Чтобы установить библиотеки, от которых зависит Pygame, введите следующую команду: $ brew install hg sdl sdl_image sdl_ttf
Команда устанавливает библиотеки, необходимые для Alien Invasion. В процессе установки каждой библиотеки на экране должна выводиться соответствующая информация. Если вы хотите включить расширенную функциональность (например, возможность добавления звуков), добавьте еще две библиотеки: $ brew install sdl_mixer portmidi
Установите Pygame следующей командой (используйте pip вместо pip3, если вы используете Python 2.7): $ pip3 install —user hg+http://bitbucket.org/pygame/pygame
Чтобы проверить правильность установки, откройте терминальный сеанс и попробуйте импортировать Pygame (используйте python вместо python3, если вы используете Python 2.7):
Создание проекта игры
$ python3 >>> import pygame >>>
Если импортирование прошло нормально, переходите к разделу «Создание проекта игры».
Установка Pygame в Windows Проект Pygame размещен на сайте совместного использования кода Bitbucket. Чтобы установить Pygame для вашей версии Windows, найдите на странице https:// bitbucket.org/pygame/pygame/downloads/ программу установки для Windows, соответствующую вашей версии Python. Если вы не нашли подходящую программу установки на сайте Bitbucket, попробуйте поискать по адресу http://www.lfd.uci. edu/~gohlke/pythonlibs/#pygame. Когда подходящий файл будет загружен, запустите программу установки, если это файл с расширением .exe. Если файл имеет суффикс .whl, скопируйте его в каталог проекта. Откройте окно командной строки, перейдите в папку, в которую был скопирован установочный пакет, и воспользуйтесь программой pip для запуска установки: > python -m pip install —user pygame-1.9.2a0-cp35-none-win32.whl
Создание проекта игры Построение игры начнется с создания пустого окна Pygame, в котором позднее будут отображаться игровые элементы — прежде всего корабль и пришельцы. Также игра должна реагировать на действия пользователя, назначать цвет фона и загружать изображение корабля.
Создание окна Pygame и обработка ввода Начнем с создания пустого окна Pygame. Ниже приведена базовая структура игры, написанной с использованием Pygame: alien_invasion.py import sys import pygame def run_game(): # Инициализирует игру и создает объект экрана. pygame.init() screen = pygame.display.set_mode((1200, 800)) pygame.display.set_caption(«Alien Invasion») # Запуск основного цикла игры.
Глава 12 • Стреляющий корабль while True: # Отслеживание событий клавиатуры и мыши. for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit()
# Отображение последнего прорисованного экрана. pygame.display.flip() run_game()
Программа начинается с импортирования модуля sys и pygame. Модуль pygame содержит функциональность, необходимую для создания игры, а модуль sys завершает игру по команде игрока. Игра Alien Invasion начинается с определения функции run_game(). Строка pygame. init() инициализирует настройки, необходимые Pygame для нормальной работы. В точке вызов pygame.display.set_mode() создает отображаемую область screen, на которой прорисовываются все графические элементы игры. Аргумент (1200, 800) представляет собой кортеж, определяющий размеры игрового окна. Передавая эти размеры pygame.display.set_mode(), мы создаем игровое окно с шириной 1200 пикселов и высотой 800 пикселов. (Вы можете изменить эти значения в соответствии с размерами своего монитора.) Объект screen называется поверхностью (surface). Поверхность в Pygame представляет часть экрана, на которой отображается игровой элемент. Каждый элемент в игре (например, пришелец или корабль игрока) представлен поверхностью. Поверхность, возвращаемая display.set_mode(), представляет все игровое окно. При активизации игрового цикла анимации эта поверхность автоматически перерисовывается при каждом проходе цикла. Процессом игры управляет цикл while , который содержит цикл событий и код, управляющий обновлениями экрана. Событием называется действие, выполняемое пользователем во время игры (например, нажатие клавиши или перемещение мыши). Чтобы наша программа реагировала на события, мы напишем цикл событий для прослушивания событий и выполнения соответствующей операции в зависимости от типа произошедшего события. Этим циклом событий является цикл for в точке . Чтобы получить доступ к событиям, обнаруженным Pygame, мы используем метод pygame.event.get(). При любом событии клавиатуры или мыши отрабатывает цикл for. В этом цикле мы пишем серию команд if для обнаружения и обработки конкретных событий. Например, когда игрок щелкает на кнопке закрытия игрового окна, программа обнаруживает событие pygame.QUIT, и программа вызывает метод sys.exit() для выхода из игры . Вызов pygame.display.flip() приказывает Pygame отобразить последний отрисованный экран. В данном случае при каждом выполнении цикла while будет отображаться пустой экран со стиранием старого экрана, так что виден будет только новый экран. При перемещении игровых элементов вызов pygame.display.flip() будет постоянно обновлять экран, отображая игровые элементы в новых позициях и скрывая старые изображения; таким образом создается иллюзия плавного движения.
Создание проекта игры
Последняя строка в этой базовой структуре вызывает метод run_game(), который инициализирует игру и запускает основной цикл. Запустите этот код, и вы увидите пустое окно Pygame.
Назначение цвета фона Pygame по умолчанию создает черный экран, но это банально. Выберем другой цвет фона: alien_invasion.py . def run_game(): . pygame.display.set_caption(«Alien Invasion»)
# Назначение цвета фона. bg_color = (230, 230, 230)
# Запуск основного цикла игры. while True: # Отслеживание событий клавиатуры и мыши. . # При каждом проходе цикла перерисовывается экран. screen.fill(bg_color) # Отображение последнего прорисованного экрана. pygame.display.flip() run_game()
Сначала программа создает цвет фона и сохраняет его в переменной bg_color . Цвет достаточно задать только один раз, поэтому его значение определяется до входа в основной цикл while. Цвета в Pygame задаются в схеме RGB: тройками интенсивности красной, зеленой и синей составляющих цвета. Значение каждой составляющей лежит в диапазоне от 0 до 255. Цветовое значение (255, 0, 0) соответствует красному цвету, (0, 255, 0) — зеленому и (0, 0, 255) — синему. Разные сочетания составляющих RGB позволяют создать до 16 миллионов цветов. В цветовом значении (230, 230, 230) красная, синяя и зеленая составляющие смешиваются в равных долях, давая светло-серый цвет фона. В точке экран заполняется цветом фона. Для этого вызывается метод screen. fill(), получающий всего один аргумент: цвет фона.
Создание класса Settings Каждый раз, когда в нашу игру добавляется новая функциональность, также в нее обычно добавляются новые настройки (параметры конфигурации). Вместо того чтобы добавлять настройки в коде, мы напишем модуль с именем settings; этот
Глава 12 • Стреляющий корабль
модуль содержит класс с именем Settings для хранения всех настроек. Такое решение позволит передавать один объект вместо множества отдельных настроек. Кроме того, оно упрощает вызовы функций и упрощает изменение внешнего вида игры с ростом проекта. Чтобы внести изменения в игру, достаточно будет изменить некоторые значения в settings .py вместо того, чтобы искать разные настройки в файлах. Исходная версия класса Settings выглядит так: settings.py class Settings(): «»»Класс для хранения всех настроек игры Alien Invasion.»»» def __init__(self): «»»Инициализирует настройки игры.»»» # Параметры экрана self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 230, 230)
Чтобы создать экземпляр Settings и использовать его для обращения к настройкам, внесите изменения в alien_invasion .py: alien_invasion.py . import pygame from settings import Settings def run_game(): # Инициализирует pygame, settings и объект экрана. pygame.init() ai_settings = Settings() screen = pygame.display.set_mode( (ai_settings.screen_width, ai_settings.screen_height)) pygame.display.set_caption(«Alien Invasion») # Запуск основного цикла игры. while True: . # При каждом проходе цикла перерисовывается экран. screen.fill(ai_settings.bg_color) # Отображение последнего прорисованного экрана. pygame.display.flip() run_game()
Класс Settings импортируется в основной файл программы, после чего программа создает экземпляр Settings и сохраняет его в ai_settings после вызова pygame. init() . При создании экрана используются атрибуты screen_width и screen_ height объекта ai_settings, после чего объект ai_settings также используется для получения цвета фона при заполнении экрана .
Добавление изображения корабля
Добавление изображения корабля А теперь добавим в игру космический корабль, которым управляет игрок. Чтобы вывести его на экран, мы загрузим изображение, после чего воспользуемся методом Pygame blit() для вывода изображения. Выбирая графику для своих игр, обязательно обращайте внимание на условия лицензирования. Самый безопасный и дешевый начальный вариант — использование бесплатной графики с таких сайтов, как http://pixabay.com/. В игре можно использовать практически любые графические форматы, но проще всего использовать файлы в формате .bmp, потому что этот формат Pygame загружает по умолчанию. И хотя Pygame можно настроить для других типов файлов, некоторые типы зависят от установки на компьютере определенных графических библиотек. (Большинство изображений, которые вы найдете, имеют формат .jpg, .png или .gif, но их можно преобразовать в формат .bmp при помощи таких программ, как Photoshop, GIMP или Paint.) Обратите особое внимание на цвет фона вашего изображения. Попробуйте найти файл с прозрачным фоном, который можно заменить любым цветом фона в графическом редакторе. Чтобы ваша игра хорошо смотрелась, цвет фона изображения должен соответствовать цвету фона игры. Также можно подобрать цвет фона игры под цвет фона изображения. В игре Alien Invasion используется файл ship .bmp (рис. 12.1), который можно загрузить в числе ресурсов книги по адресу https://www.nostarch.com/pythoncrashcourse/. Цвет фона файла соответствует настройкам, используемым в проекте. Создайте в главном каталоге проекта (alien_invasion) каталог с именем images. Сохраните файл ship .bmp в каталоге images.
Рис. 12.1. Корабль для игры Alien Invasion
Глава 12 • Стреляющий корабль
Создание класса Ship После того как изображение корабля выбрано, его необходимо вывести на экран. Для работы с кораблем мы напишем модуль ship, содержащий класс Ship. Этот класс реализует бульшую часть поведения корабля. ship.py import pygame class Ship(): def __init__(self, screen): «»»Инициализирует корабль и задает его начальную позицию.»»» self.screen = screen
# Загрузка изображения корабля и получение прямоугольника. self.image = pygame.image.load(‘images/ship.bmp’) self.rect = self.image.get_rect() self.screen_rect = screen.get_rect() # Каждый новый корабль появляется у нижнего края экрана. self.rect.centerx = self.screen_rect.centerx self.rect.bottom = self.screen_rect.bottom def blitme(self): «»»Рисует корабль в текущей позиции.»»» self.screen.blit(self.image, self.rect)
Сначала программа импортирует модуль pygame. Метод __init__() класса Ship получает два параметра: ссылку self и объект screen, на котором выводится корабль. Загрузка изображения выполняется вызовом pygame.image.load() . Функция возвращает поверхность, представляющую корабль; полученный объект сохраняется в self.image. После того как изображение будет загружено, метод get_rect() используется для получения атрибута rect поверхности . Один из факторов эффективности Pygame заключается в том, что программист может выполнять операции с игровыми элементами как с прямоугольниками даже в том случае, если они имеют другую форму. Операции с прямоугольниками эффективны, потому что прямоугольник — простая геометрическая фигура. Обычно этот подход работает достаточно хорошо и игроки не замечают, что программа не отслеживает точную геометрическую форму каждого игрового элемента. При работе с объектом rect для вас доступны координаты x и y верхней, нижней, левой и правой сторон, а также центра. Присваивая любые из этих значений, вы задаете текущую позицию прямоугольника. Местонахождение центра игрового элемента определяется атрибутами center, centerx или centery прямоугольника. Стороны определяются атрибутами top, bottom, left и right. Для изменения горизонтального или вертикального расположения прямоугольника достаточно задать атрибуты x и y , содержащие координаты левого верхнего угла. Эти атрибуты избавляют вас от вычислений, которые раньше разработчикам игр приходилось выполнять вручную, притом достаточно часто.
Добавление изображения корабля
ПРИМЕЧАНИЕ В Pygame начало координат (0, 0) находится в левом верхнем углу экрана, а оси направлены сверху вниз и слева направо . На экране с размерами 1200 на 800 начало координат располагается в левом верхнем углу, а правый нижний угол имеет координаты (1200, 800) .
Корабль будет расположен в середине нижней стороны экрана. Для этого мы сначала сохраняем прямоугольник экрана в self.screen_rect , а затем присваиваем self.rect.centerx (координата x центра корабля) значение атрибута centerx прямоугольника экрана . Атрибуту self.rect.bottom (координата y низа корабля) присваивается значение атрибута bottom прямоугольника экрана. Pygame использует эти атрибуты rect для позиционирования изображения, чтобы корабль был выровнен по центру, а его нижний край совпадал с нижним краем экрана. В точке определяется метод blitme(), который выводит изображение на экран в позиции, заданной self.rect.
Вывод корабля на экран Изменим программу alien_invasion .py, чтобы в ней создавался корабль (экземпляр Ship) и вызывался метод blitme() класса Ship: alien_invasion.py . from settings import Settings from ship import Ship def run_game(): . pygame.display.set_caption(«Alien Invasion»)
# Создание корабля. ship = Ship(screen)
# Start the main loop for the game. while True: . # При каждом проходе цикла перерисовывается экран. screen.fill(ai_settings.bg_color) ship.blitme() # Отображение последнего прорисованного экрана. pygame.display.flip() run_game()
После создания экрана программа импортирует класс Ship и создает его экземпляр (с именем ship). Это должно происходить до начала основного цикла while , чтобы при каждом проходе цикла не создавался новый экземпляр корабля. Чтобы перерисовать корабль на экране, мы вызываем ship.blitme() после заполнения фона, так что корабль выводится поверх фона . Если вы запустите alien_invasion .py сейчас, вы увидите пустой игровой экран, в центре нижней стороны которого находится корабль (рис. 12.2).
Глава 12 • Стреляющий корабль
Рис. 12.2. Корабль в середине нижней стороны экрана
Рефакторинг: модуль game_functions В больших проектах перед добавлением нового кода часто проводится рефакторинг уже написанного кода. Рефакторинг упрощает структуру существующего кода и дальнейшее развитие проекта. В этом разделе мы создадим новый модуль game_functions для хранения функций, обеспечивающих работу игры. Модуль game_functions предотвратит чрезмерное разрастание alien_invasion .py и сделает логику alien_invasion .py более простой и понятной.
Функция check_events() Начнем с перемещения кода управления событиями в отдельную функцию check_ events() . Тем самым вы упростите run_game() и изолируете цикл управления событиями от остального кода. Изоляция цикла событий позволит организовать управление событиями отдельно от других аспектов игры (например, обновления экрана). Поместим check_events() в отдельный модуль с именем game_functions: game_functions.py import sys import pygame def check_events():
Рефакторинг: модуль game_functions
«»»Обрабатывает нажатия клавиш и события мыши.»»» for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit()
Этот модуль импортирует модули sys и pygame, используемые в цикле обработки событий. На данный момент эта функция не получает параметров, а ее тело копируется из цикла событий в alien_invasion .py. Теперь изменим код alien_invasion .py , чтобы он импортировал модуль game_ functions, и мы заменим цикл событий вызовом check_events(): alien_invasion.py import pygame from settings import Settings from ship import Ship import game_functions as gf def run_game(): . # Запуск основного цикла игры. while True: gf.check_events() # При каждом проходе цикла перерисовывается экран. .
Импортировать модуль sys прямо в главный файл в программы уже не нужно, потому что он сейчас используется только в модуле game_functions. Импортируемому модулю game_functions для удобства присваивается псевдоним gf.
Функция update_screen() Для дальнейшего упрощения run_game() выделим код обновления экрана в отдельную функцию update_screen() в game_functions .py: game_functions.py . def check_events(): . def update_screen(ai_settings, screen, ship): «»»Обновляет изображения на экране и отображает новый экран.»»» # При каждом проходе цикла перерисовывается экран. screen.fill(ai_settings.bg_color) ship.blitme() # Отображение последнего прорисованного экрана. pygame.display.flip()
Новая функция update_screen() получает три параметра: ai_settings, screen и ship. Теперь необходимо заменить цикл while из alien_invasion .py вызовом update_ screen():
Глава 12 • Стреляющий корабль
alien_invasion.py . # Запуск основного цикла игры. while True: gf.check_events() gf.update_screen(ai_settings, screen, ship) run_game()
Эти две функции упрощают цикл while и процесс дальнейшей разработки. Бульшая часть работы будет выполняться не в run_game(), а в модуле game_functions. Так как мы решили начать работу с кодом c одного файла, мы не стали вводить модуль game_functions с самого начала. Эта последовательность дает представление о реальном процессе разработки: сначала вы пишете свой код в самом простом виде, а потом подвергаете его рефакторингу по мере роста сложности проекта. Теперь, когда мы изменили структуру кода и упростили его расширение, можно переходить к динамическим аспектам игры! УПРАЖНЕНИЯ 12-1 . Синее небо: создайте окно Pygame с синим фоном . 12-2 . Игровой персонаж: найдите изображение игрового персонажа, который вам нравится, в формате .bmp (или преобразуйте существующее изображение) . Создайте класс, который рисует персонажа в центре экрана, и приведите цвет фона изображения в соответствие с цветом фона экрана (или наоборот) .
Управление кораблем Реализуем возможность перемещения корабля по горизонтали. Для этого мы напишем код, реагирующий на нажатие клавиш → или ←. Начнем с движения вправо, а потом применим те же принципы к движению влево. Заодно вы научитесь управлять перемещением изображений на экране.
Обработка нажатия клавиши Каждый раз, когда пользователь нажимает клавишу, это нажатие регистрируется в Pygame как событие. Каждое событие идентифицируется методом pygame.event. get(), поэтому в функции check_events() необходимо указать, какие события должны отслеживаться. Каждое нажатие клавиши регистрируется как событие KEYDOWN. При обнаружении события KEYDOWN необходимо проверить, была ли нажата клавиша, инициирующая некоторое игровое событие. Например, при нажатии клавиши → значение rect.centerx корабля увеличивается для перемещения корабля вправо: game_functions.py def check_events(ship): «»»Обрабатывает нажатия клавиш и события мыши.»»»
for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: # Переместить корабль вправо. ship.rect.centerx += 1
Функции check_events() передается параметр ship, потому что корабль должен двигаться вправо при нажатии клавиши →. Внутри check_events() в цикл событий добавляется блок elif для выполнения кода при обнаружении события KEYDOWN . Чтобы проверить, является ли нажатая клавиша клавишей → (pygame.K_RIGHT), мы читаем атрибут event.key . Если нажата клавиша →, корабль перемещается вправо, для чего значение ship.rect.centerx увеличивается на 1 . Вызов check_events() в alien_invasion .py необходимо изменить, чтобы в аргументе передавался объект ship: alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ship) gf.update_screen(ai_settings, screen, ship)
Если запустить программу alien_invasion .py сейчас, вы увидите, что корабль перемещается вправо на 1 пиксел при каждом нажатии клавиши →. Неплохо для начала, но это не лучший способ управления кораблем. Чтобы управление было более удобным, следует реализовать возможность непрерывного перемещения.
Непрерывное перемещение Если игрок удерживает клавишу →, корабль должен двигаться вправо до тех пор, пока клавиша не будет отпущена. Чтобы узнать, когда клавиша → будет отпущена, наша игра отслеживает событие pygame.KEYUP; таким образом, реализация непрерывного движения будет основана на отслеживании событий KEYDOWN и KEYUP в сочетании с флагом moving_right. В неподвижном состоянии корабля флаг moving_right равен False. При нажатии клавиши → флагу присваивается значение True, а когда клавиша будет отпущена, флаг возвращается в состояние False. Класс Ship управляет всеми атрибутами корабля, и мы добавим в него атрибут с именем moving_right и метод update() для проверки состояния флага moving_ right. Метод update() изменяет позицию корабля, если флаг содержит значение True. Этот метод будет вызываться каждый раз, когда вы хотите обновить позицию корабля. Ниже приведены изменения в классе Ship: ship.py class Ship():
Глава 12 • Стреляющий корабль
def __init__(self, screen): . # Каждый новый корабль появляется у нижнего края экрана. self.rect.centerx = self.screen_rect.centerx self.rect.bottom = self.screen_rect.bottom
# Флаг перемещения self.moving_right = False def update(self): «»»Обновляет позицию корабля с учетом флага.»»» if self.moving_right: self.rect.centerx += 1 def blitme(self): .
Мы добавляем атрибут self.moving_right в методе __init__() и инициализируем его значением False . Затем вызывается метод update(), который перемещает корабль вправо, если флаг равен True . Теперь внесем изменения в check_events(), чтобы при нажатии клавиши → moving_ right присваивалось значение True, а при ее отпускании — False: game_functions.py def check_events(ship): «»»Обрабатывает нажатия клавиш и события мыши.»»» for event in pygame.event.get(): . elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.type == pygame.KEYUP: if event.key == pygame.K_RIGHT: ship.moving_right = False
В точке изменяется реакция игры при нажатии клавиши →; вместо непосредственного изменения позиции корабля программа просто присваивает moving_right значение True. В точке добавляется новый блок elif, реагирующий на события KEYUP. Когда игрок отпускает клавишу → (K_RIGHT), moving_right присваивается значение False. Остается изменить цикл while в alien_invasion .py, чтобы при каждом проходе цикла вызывался метод update() корабля: alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ship) ship.update() gf.update_screen(ai_settings, screen, ship)
Позиция корабля будет обновляться после проверки событий клавиатуры, но перед обновлением экрана. Таким образом, позиция корабля обновляется в от-
вет на действия пользователя и будет использоваться при перерисовке корабля на экране. Если запустить alien_invasion .py и удерживать клавишу →, корабль непрерывно двигается вправо, пока клавиша не будет отпущена.
Перемещение влево и вправо Теперь, когда мы реализовали непрерывное движение вправо, добавить движение влево относительно несложно. Для этого нужно снова изменить класс Ship и функцию check_events(). Ниже приведены необходимые изменения в __init__() и update() в классе Ship: ship.py def __init__(self, screen): . # Флаги перемещения self.moving_right = False self.moving_left = False def update(self): «»»Обновляет позицию корабля с учетом флагов.»»» if self.moving_right: self.rect.centerx += 1 if self.moving_left: self.rect.centerx -= 1
В методе __init__() добавляется флаг self.moving_left. В update() используются два отдельных блока if вместо elif, чтобы при нажатии обеих клавиш со стрелками атрибут rect.centerx сначала увеличивался, а потом уменьшался. В результате корабль остается на месте. Если бы для движения влево использовался блок elif, то клавиша → всегда имела бы приоритет. Такая реализация повышает точность перемещения при переключении направления, когда игрок может ненадолго удерживать нажатыми обе клавиши. В check_events() необходимо внести два изменения: game_functions.py def check_events(ship): «»»Обрабатывает нажатия клавиш и события мыши.»»» for event in pygame.event.get(): . elif event.type == pygame.KEYDOWN: if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True elif event.type == pygame.KEYUP: if event.key == pygame.K_RIGHT: ship.moving_right = False elif event.key == pygame.K_LEFT: ship.moving_left = False
Глава 12 • Стреляющий корабль
Если событие KEYDOWN происходит для события K_LEFT, то moving_left присваивается True. Если событие KEYUP происходит для события K_LEFT, то moving_left присваивается False. Здесь возможно использовать блоки elif, потому что каждое событие связано только с одной клавишей. Если же игрок нажимает обе клавиши одновременно, то программа обнаруживает два разных события. Если вы запустите alien_invasion .py, то увидите, что корабль может непрерывно двигаться влево и вправо. Если же нажать обе клавиши, корабль останавливается. Следующий шаг — доработка движения корабля. Внесем изменения в скорость и ограничим величину перемещения, чтобы корабль не выходил за края экрана.
Регулировка скорости корабля В настоящий момент корабль смещается на один пиксел за каждый проход цикла while, но для повышения точности управления скоростью можно добавить в класс Settings атрибут ship_speed_factor. Этот атрибут определяет величину смещения корабля при каждом проходе цикла. Новый атрибут settings .py выглядит так: settings.py class Settings(): «»»Класс для хранения всех настроек игры Alien Invasion.»»» def __init__(self): . # Настройки корабля
Переменной ship_speed_factor присваивается значение 1.5. При перемещении корабля его позиция изменяется на 1,5 пиксела вместо 1. Дробные значения скорости позволят лучше управлять скоростью корабля при последующем повышении темпа игры. Однако атрибуты прямоугольников (такие, как centerx) принимают только целочисленные значения, поэтому в Ship необходимо внести ряд изменений: ship.py class Ship(): def __init__(self, ai_settings, screen): «»»Инициализирует корабль и задает его начальную позицию.»»» self.screen = screen self.ai_settings = ai_settings . # Каждый новый корабль появляется у нижнего края экрана .
# Сохранение вещественной координаты центра корабля. self.center = float(self.rect.centerx)
# Флаги перемещения self.moving_right = False self.moving_left = False
def update(self): «»»Обновляет позицию корабля с учетом флагов.»»» # Обновляется атрибут center, не rect. if self.moving_right: self.center += self.ai_settings.ship_speed_factor if self.moving_left: self.center -= self.ai_settings.ship_speed_factor # Обновление атрибута rect на основании self.center. self.rect.centerx = self.center def blitme(self): .
В точке в список параметров __init__() добавляется параметр ai_settings, чтобы для корабля была доступна величина его скорости. Затем параметр ai_settings преобразуется в атрибут для использования в update() . Так как позиция корабля изменяется с нецелым приращением пикселов, она должна храниться в переменной, способной хранить дробные значения. Формально атрибутам rect можно присвоить дробные значения, но rect сохранит только целую часть этого значения. Для точного хранения позиции корабля определяется новый атрибут self.center, способный хранить дробные значения . Функция float() используется для преобразования значения self.rect.centerx в вещественный формат и сохранения этого значения в self.center. После изменения позиции корабля в update() значение self.center изменяется на величину, хранящуюся в ai_settings.ship_speed_factor . После обновления self.center новое значение используется для обновления атрибута self. rect.centerx, управляющего позицией корабля . В self.rect.centerx будет сохранена только целая часть self.center, но для отображения корабля этого достаточно. Значение ai_settings должно передаваться в аргументе при создании экземпляра Ship в alien_invasion .py: alien_invasion.py . def run_game(): . # Создание корабля. ship = Ship(ai_settings, screen) .
Теперь с любым значением ship_speed_factor, бульшим 1, корабль будет двигаться быстрее. Эта возможность ускорит реакцию корабля на действия игрока, а также позволит нам изменить темп игры с течением времени.
Ограничение перемещений Если удерживать какую-нибудь клавишу со стрелкой достаточно долго, корабль выйдет за край экрана. Давайте сделаем так, чтобы корабль останавливался при достижении края экрана. Задача решается изменением метода update() в классе Ship:
Глава 12 • Стреляющий корабль
ship.py def update(self): «»»Обновляет позицию корабля с учетом флагов.»»» # Обновляется атрибут center, не rect. if self.moving_right and self.rect.right 0: self.center -= self.ai_settings.ship_speed_factor # Обновление атрибута rect на основании self.center self.rect.centerx = self.center
Этот код проверяет позицию корабля перед изменением значения self.center. Выражение self.rect.right возвращает координату x правого края прямоугольника корабля. Если это значение меньше значения, возвращаемого self. screen_rect.right, значит, корабль еще не достиг правого края экрана . То же относится и к левому краю: если координата x левой стороны прямоугольника больше 0, значит, корабль еще не достиг левого края экрана . Проверка гарантирует, что корабль будет оставаться в пределах экрана, перед изменением значения self.center. Если вы запустите alien_invasion .py сейчас, то движение корабля будет останавливаться у края экрана.
Рефакторинг check_events() В ходе разработки функция check_events() будет становиться все длиннее, поэтому мы выделим из check_events() еще две функции: для обработки событий KEYDOWN и для обработки событий KEYUP: game_functions.py def check_keydown_events(event, ship): «»»Реагирует на нажатие клавиш.»»» if event.key == pygame.K_RIGHT: ship.moving_right = True elif event.key == pygame.K_LEFT: ship.moving_left = True def check_keyup_events(event, ship): «»»Реагирует на отпускание клавиш.»»» if event.key == pygame.K_RIGHT: ship.moving_right = False elif event.key == pygame.K_LEFT: ship.moving_left = False def check_events(ship): «»»Обрабатывает нажатия клавиш и события мыши.»»» for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() elif event.type == pygame.KEYDOWN: check_keydown_events(event, ship)
elif event.type == pygame.KEYUP: check_keyup_events(event, ship)
В программе появились две новые функции: check_keydown_events() и check_ keyup_events(). Каждая функция получает параметр event и параметр ship. Тела двух функций скопированы из check_events(), а старый код заменен вызовами новых функций. Новая структура кода упрощает функцию check_events() и облегчает последующее программирование реакции на действия игрока.
В двух словах В следующем разделе мы реализуем стрельбу, для чего нам потребуется новый файл с именем bullet .py и изменения в некоторых уже имеющихся файлах. В настоящее время программа состоит из четырех файлов с разными классами, функциями и методами. Чтобы вы четко представляли себе структуру проекта, кратко проанализируем каждый из этих файлов перед добавлением новой функциональности.
alien_invasion .py Главный файл программы alien_invasion .py создает ряд важных объектов, используемых ходе игры: настройки хранятся в ai_settings, основная поверхность для вывода изображения хранится в screen, а экземпляр ship тоже создается в этом файле. Также в alien_invasion .py содержится главный цикл игры — цикл while с вызовами check_events(), ship.update() и update_screen(). Файл alien_invasion .py — единственный файл, который должен запускаться для игры в Alien Invasion. Все остальные файлы — settings .py, game_functions .py, ship .py — содержат код, который импортируется (прямо или косвенно) в этот файл.
settings .py Файл settings .py содержит класс Settings . Этот класс содержит только метод __init__(), инициализирующий атрибуты, которые управляют внешним видом и скоростью игры.
game_functions .py Файл game_functions .py содержит набор функций, выполняющих основную работу в игре. Функция check_events() обнаруживает события, представляющие интерес для игры (например, нажатия и отпускания клавиш), и обрабатывает все эти типы событий при помощи вспомогательных функций check_keydown_events() и check_keyup_events(). Пока эти функции управляют только движением корабля. Модуль game_functions также содержит функцию update_screen(), которая перерисовывает экран при каждом проходе основного цикла.
Глава 12 • Стреляющий корабль
ship .py Файл ship .py содержит класс Ship . В этом классе определен метод __init__() , метод update() для управления позицией корабля и метод blitme() для вывода изображения корабля на экран. Изображение корабля хранится в файле ship .bmp, который находится в папке images. УПРАЖНЕНИЯ 12-3 . Ракета: создайте игру, у которой в исходном состоянии в центре экрана находится ракета . Игрок может перемещать ракету вверх, вниз, вправо и влево четырьмя клавишами со стрелками . Проследите за тем, чтобы ракета не выходила за края экрана . 12-4 . Клавиши: создайте файл Pygame, который создает пустой экран . В цикле событий выводите значение атрибута event .key при обнаружении события pygame .KEYDOWN . Запустите программу, нажимайте различные клавиши и понаблюдайте за реакцией Pygame .
Стрельба А теперь добавим в игру возможность стрельбы. Мы напишем код, который выпускает пулю (маленький прямоугольник) при нажатии игроком клавиши «пробел». Пули летят вертикально вверх, пока не исчезнут у верхнего края экрана.
Добавление настроек Сначала добавим в settings .py новые настройки для значений, управляющих поведением класса Bullet. Эти настройки добавляются в конец метода __init__(): settings.py def __init__(self): . # Параметры пули self.bullet_speed_factor = 1 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60
Эти настройки создают темно-серые пули с шириной 3 пиксела и высотой 15 пикселов. Пули двигаются немного медленнее, чем корабль.
Создание класса Bullet Теперь создадим файл bullet .py для хранения класса Bullet. Первая часть файла bullet .py выглядит так: bullet.py import pygame from pygame.sprite import Sprite class Bullet(Sprite):
«»»Класс для управления пулями, выпущенными кораблем.»»» def __init__(self, ai_settings, screen, ship): «»»Создает объект пули в текущей позиции корабля.»»» super(Bullet, self).__init__() self.screen = screen
# Создание пули в позиции (0,0) и назначение правильной позиции. self.rect = pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height) self.rect.centerx = ship.rect.centerx self.rect.top = ship.rect.top
# Позиция пули хранится в вещественном формате. self.y = float(self.rect.y)
self.color = ai_settings.bullet_color self.speed_factor = ai_settings.bullet_speed_factor
Класс Bullet наследует от класса Sprite, импортируемого из модуля pygame.sprite. Работая со спрайтами (sprite), разработчик группирует связанные элементы в своей игре и выполняет операцию со всеми сгруппированными элементами одновременно. Чтобы создать экземпляр пули, методу __init__() необходимо передать экземпляры ai_settings, screen и ship, а вызов super() необходим для правильной реализации наследования от Sprite. ПРИМЕЧАНИЕ Вызов super(Bullet, self) .__init__() использует синтаксис Python 2 .7 . В Python 3 этот синтаксис тоже работает, хотя вызов также можно записать в более простой форме super() .__init__() .
В точке создается атрибут rect пули. Пуля не создается на основе готового изображения, поэтому прямоугольник приходится строить «с нуля» при помощи класса pygame.Rect(). При создании экземпляра этого класса необходимо задать координаты левого верхнего угла прямоугольника, его ширину и высоту. Прямоугольник инициализируется в точке (0, 0), но в следующих двух строках он перемещается в нужное место, так как позиция пули зависит от позиции корабля. Ширина и высота пули определяются значениями, хранящимися в ai_settings. В точке атрибуту centerx пули присваивается значение rect.centerx корабля. Пуля должна появляться у верхнего края корабля, поэтому верхний край пули совмещается с верхним краем прямоугольника корабля для имитации «выстрела» из корабля . Координата y пули хранится в вещественной форме для внесения более точных изменений в скорость пули . В точке настройки цвета и скорости пули сохраняются в self.color и self.speed_factor. А вот как выглядит вторая часть bullet .py, update() и draw_bullet(): bullet.py def update(self): «»»Перемещает пулю вверх по экрану.»»»
Глава 12 • Стреляющий корабль # Обновление позиции пули в вещественном формате. self.y -= self.speed_factor # Обновление позиции прямоугольника. self.rect.y = self.y def draw_bullet(self): «»»Вывод пули на экран.»»» pygame.draw.rect(self.screen, self.color, self.rect)
Метод update() управляет позицией пули. Когда происходит выстрел, пуля двигается вверх по экрану, что соответствует уменьшению координаты y; следовательно, для обновления позиции пули следует вычесть величину, хранящуюся в self. speed_factor, из self.y . Затем значение self.y используется для изменения значения self.rect.y . Атрибут speed_factor позволяет увеличить скорость пуль по ходу игры или при изменении ее поведения. Координата x пули после выстрела не изменяется, поэтому пуля летит вертикально по прямой линии. Для вывода пули на экран вызывается функция draw_bullet(). Она заполняет часть экрана, определяемую прямоугольником пули, цветом из self.color .
Группировка пуль Класс Bullet и все необходимые настройки готовы; можно переходить к написанию кода, который будет выпускать пулю каждый раз, когда игрок нажимает клавишу «пробел». Сначала мы создадим в alien_invasion .py группу для хранения всех летящих пуль, чтобы программа могла управлять их полетом. Эта группа будет представлена экземпляром класса pygame.sprite.Group, который напоминает список с расширенной функциональностью, которая может быть полезна при построении игр. Мы воспользуемся группой для прорисовки пуль на экране при каждом проходе основного цикла и обновления текущей позиции каждой пули: alien_invasion.py import pygame from pygame.sprite import Group . def run_game(): . # Создание корабля. ship = Ship(ai_settings, screen) # Создание группы для хранения пуль. bullets = Group() # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() bullets.update() gf.update_screen(ai_settings, screen, ship, bullets) run_game()
Класс Group импортируется из pygame.sprite. В точке создается экземпляр Group с именем bullets. Эта группа создается за пределами цикла while, чтобы новая группа пуль не создавалась при каждом проходе цикла. ПРИМЕЧАНИЕ Если группа будет создаваться в цикле, в результате программа создает тысячи групп, и скорость игры упадет до минимума . Если ваша игра со временем начинает заметно «тормозить», внимательно проверьте, что происходит в основном цикле while .
Объект bullets передается методам check_events() и update_screen(). В check_ events() он используется при обработке клавиши «пробел», а в update_screen() необходимо перерисовать выводимые на экран пули. Вызов update() для группы приводит к автоматическому вызову update() для каждого спрайта в группе. Строка bullets.update() вызывает bullet.update() для каждой пули, включенной в группу bullets.
Обработка выстрелов В файле game_functions .py необходимо внести изменения в метод check_keydown_ events(), чтобы при нажатии клавиши «пробел» происходил выстрел. Изменять check_keyup_events() не нужно, потому что при отпускании клавиши ничего не происходит. Также необходимо изменить update_screen() и вывести каждую пулю на экран перед вызовом flip() . Ниже представлены соответствующие изменения в game_functions .py: game_functions.py . from bullet import Bullet def check_keydown_events(event, ai_settings, screen, ship, bullets): . elif event.key == pygame.K_SPACE: # Создание новой пули и включение ее в группу bullets. new_bullet = Bullet(ai_settings, screen, ship) bullets.add(new_bullet) . def check_events(ai_settings, screen, ship, bullets): «»»Обрабатывает нажатия клавиш и события мыши.»»» for event in pygame.event.get(): . elif event.type == pygame.KEYDOWN: check_keydown_events(event, ai_settings, screen, ship, bullets) . def update_screen(ai_settings, screen, ship, bullets): . # Все пули выводятся позади изображений корабля и пришельцев. for bullet in bullets.sprites(): bullet.draw_bullet() ship.blitme() .
Глава 12 • Стреляющий корабль
Рис. 12.3. Экран игры после серии выстрелов
Группа bullets передается check_keydown_events() . Когда игрок нажимает пробел, создается новая пуля (экземпляр Bullet с именем new_bullet), которая добавляется в группу bullets методом add(); код bullets.add(new_bullet) сохраняет новую пулю в группе bullets. Группу bullets необходимо добавить в число параметров в определении check_events() , а также передать в аргументе при вызове check_keydown_ events(). Параметр bullets передается функции update_screen() , которая рисует пули на экране. Метод bullets.sprites() возвращает список всех спрайтов в группе bullets. Чтобы нарисовать все выпущенные пули на экране, программа перебирает спрайты в bullets и вызывает для каждого draw_bullet() . Если запустить alien_invasion .py сейчас, вы сможете двигать корабль влево и вправо и выпускать сколько угодно пуль. Пули перемещаются вверх по экрану и исчезают при достижении верхнего края (рис. 12.3). Размер, цвет и скорость пуль можно изменить при помощи настроек в settings .py.
Удаление старых пуль На данный момент пули исчезают по достижении верхнего края, но только потому, что Pygame не может нарисовать их выше края экрана. На самом деле пули продолжают существовать; их координата y продолжает уменьшаться. И это создает проблему, потому что пули продолжают потреблять память и вычислительные мощности.
От старых пуль необходимо избавиться, иначе игра замедлится из-за большого объема лишней работы. Для этого необходимо определить момент, когда атрибут bottom прямоугольника пули достигнет 0, — это означает, что пуля вышла за верхний край экрана: alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, ship, bullets) ship.update() bullets.update()
# Удаление пуль, вышедших за край экрана. for bullet in bullets.copy(): if bullet.rect.bottom 0: . else: stats.game_active = False pygame.mouse.set_visible(True)
Указатель снова становится видимым сразу же после того, как игра становится неактивной, что происходит в ship_hit(). Внимание к подобным деталям сделает вашу игру более профессиональной, а игрок сможет сосредоточиться на игре вместо того, чтобы разбираться в сложностях пользовательского интерфейса. УПРАЖНЕНИЯ 14-1 . Запуск игры клавишей P: так как в Alien Invasion игрок управляет кораблем с клавиатуры, для запуска игры также лучше использовать клавиатуру . Добавьте код, с которым игрок сможет запустить игру нажатием клавиши P . Возможно, часть кода из check_play_ button() стоит переместить в функцию start_game(), которая будет вызываться из check_ play_button() и check_keydown_events() . 14-2 . Учебная стрельба: создайте у правого края экрана прямоугольник, который двигается вверх и вниз с постоянной скоростью . У левого края располагается корабль, который перемещается вверх и вниз игроком и стреляет по движущейся прямоугольной мишени . Добавьте кнопку Play для запуска игры . После трех промахов игра заканчивается, а на экране снова появляется кнопка Play . Нажатие этой кнопки перезапускает игру .
Повышение сложности В текущей версии после того, как весь флот пришельцев будет уничтожен, игрок переходит на новый уровень, но сложность игры остается неизменной. Давайте немного оживим игру и повысим ее сложность; для этого скорость игры будет повышаться каждый раз, когда игрок уничтожает весь флот.
Изменение настроек скорости Начнем с реорганизации класса Settings и разделения настроек игры на две категории: постоянные и изменяющиеся. Также необходимо проследить за тем, чтобы настройки, изменяющиеся в ходе игры, сбрасывались в исходное состояние в начале новой игры. Метод __init__() из файла settings .py выглядит так: settings.py def __init__(self): «»»Инициализирует статические настройки игры.»»» # Настройки экрана self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 230, 230) # Настройки корабля self.ship_limit = 3 # Настройки пуль
self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60 self.bullets_allowed = 3 # Настройки пришельцев self.fleet_drop_speed = 10
# Темп ускорения игры self.speedup_scale = 1.1
Значения, которые остаются неизменными, по-прежнему инициализируются в методе __init__(). В точке добавляется настройка speedup_scale, управляющая быстротой нарастания скорости; со значением 2 скорость удваивается каждый раз, когда игрок переходит на следующий уровень, а со значением 1 скорость остается постоянной. С таким значением, как 1,1, скорость будет увеличиваться в достаточной степени, чтобы игра усложнилась, но не стала невозможной. Наконец, вызов initialize_dynamic_settings() инициализирует значения атрибутов, которые должны изменяться в ходе игры . Код initialize_dynamic_settings() выглядит так: settings.py def initialize_dynamic_settings(self): «»»Инициализирует настройки, изменяющиеся в ходе игры.»»» self.ship_speed_factor = 1.5 self.bullet_speed_factor = 3 self.alien_speed_factor = 1 # fleet_direction = 1 обозначает движение вправо; а -1 — влево. self.fleet_direction = 1
Метод задает исходные значения скоростей корабля, пуль и пришельцев. Эти скорости будут увеличиваться по ходу игры и будут сбрасываться каждый раз, когда игрок запускает новую игру. Мы включаем в этот метод fleet_direction, чтобы пришельцы в начале новой игры всегда двигались вправо. Для увеличения скорости корабля, пуль и пришельцев каждый раз, когда игрок достигает нового уровня, в программе используется функция increase_ speed(): settings.py def increase_speed(self): «»»Увеличивает настройки скорости.»»» self.ship_speed_factor *= self.speedup_scale self.bullet_speed_factor *= self.speedup_scale self.alien_speed_factor *= self.speedup_scale
Чтобы увеличить скорость этих игровых элементов, мы умножаем каждую настройку скорости на значение speedup_scale.
Глава 14 • Ведение счета
Темп игры повышается вызовом increase_speed() в check_bullet_alien_ collisions() при уничтожении последнего пришельца во флоте, но перед созданием нового флота: game_functions.py def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets): . if len(aliens) == 0: # Уничтожение пуль, повышение скорости и создание нового флота. bullets.empty() ai_settings.increase_speed() create_fleet(ai_settings, screen, ship, aliens)
Изменения значений настроек скорости ship_speed_factor, alien_speed_factor и bullet_speed_factor достаточно для того, чтобы ускорить всю игру!
Сброс скорости Каждый раз, когда игрок начинает новую игру, все измененные настройки должны вернуться к исходным значениям, иначе каждая новая игра будет начинаться с повышенными настройками скорости предыдущей игры: game_functions.py def check_play_button(ai_settings, screen, stats, play_button, ship, aliens, bullets, mouse_x, mouse_y): «»»Запускает новую игру при нажатии кнопки Play.»»» button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) if button_clicked and not stats.game_active: # Сброс игровых настроек. ai_settings.initialize_dynamic_settings() # Указатель мыши скрывается. pygame.mouse.set_visible(False) .
Игра Alien Invasion стала достаточно сложной и интересной. Каждый раз, когда игрок очищает экран, игра должна слегка ускориться, а ее сложность должна слегка возрасти. Если сложность игры возрастает слишком быстро, уменьшите значение settings.speedup_scale, а если наоборот, сложность недостаточна, — слегка увеличьте это значение. Найдите оптимальное значение, оценивая сложность игры за разумный промежуток времени. Первая пара флотов должна быть простой, несколько следующих — сложными, но возможными, а при последующих попытках сложность должна становиться практически безнадежной. УПРАЖНЕНИЯ 14-3 . Учебная стрельба с нарастающей сложностью: начните с кода упражнения 14-2 (с . 288) . Скорость мишени должна увеличиваться по ходу игры, а при нажатии игроком кнопки Play мишень должна возвращаться к исходной скорости .
Подсчет очков Система подсчета очков позволит отслеживать счет игры в реальном времени; также на экране будет выводиться текущий рекорд, уровень и количество оставшихся кораблей. Счет игры также относится к игровой статистике, поэтому мы добавим атрибут score в класс GameStats: game_stats.py class GameStats(): . def reset_stats(self): «»»Инициализирует статистику, изменяющуюся в ходе игры.»»» self.ships_left = self.ai_settings.ship_limit self.score = 0
Чтобы счет сбрасывался при запуске новой игры, мы инициализируем score в reset_stats() вместо __init__().
Вывод счета Чтобы вывести счет на экран, мы сначала создаем новый класс Scoreboard. Пока этот класс ограничивается выводом текущего счета, но мы используем его для вывода рекордного счета, уровня и количества оставшихся кораблей. Ниже приведена первая часть класса; сохраните ее под именем scoreboard .py: scoreboard.py import pygame.font class Scoreboard(): «»»Класс для вывода игровой информации.»»» def __init__(self, ai_settings, screen, stats): «»»Инициализирует атрибуты подсчета очков.»»» self.screen = screen self.screen_rect = screen.get_rect() self.ai_settings = ai_settings self.stats = stats
# Настройки шрифта для вывода счета. self.text_color = (30, 30, 30) self.font = pygame.font.SysFont(None, 48) # Подготовка исходного изображения. self.prep_score()
Так как Scoreboard выводит текст на экран, код начинается с импортирования модуля pygame.font. Затем __init__() передаются параметры ai_settings, screen и stats, чтобы класс мог выводить информацию об отслеживаемых показателях . Далее назначается цвет текста и создается экземпляр объекта шрифта . Чтобы преобразовать выводимый текст в изображение, мы вызываем метод prep_ score() , который определяется следующим образом: scoreboard.py def prep_score(self): «»»Преобразует текущий счет в графическое изображение.»»»
Глава 14 • Ведение счета score_str = str(self.stats.score) self.score_image = self.font.render(score_str, True, self. text_color, self.ai_settings.bg_color) # Вывод счета в правой верхней части экрана. self.score_rect = self.score_image.get_rect() self.score_rect.right = self.screen_rect.right — 20 self.score_rect.top = 20
В методе prep_score() преобразуем числовое значение stats.score в строку ; эта строка передается методу render(), создающему изображение . Чтобы счет был хорошо виден на экране, мы передаем render() цвет фона и цвет текста. Счет размещается в правой верхней части экрана и расширяется влево с ростом значения и ширины числа. Чтобы счет всегда оставался выровненным по правой стороне, мы создаем прямоугольник rect с именем score_rect и смещаем его правую сторону на 20 пикселов от правого края экрана . Затем верхняя сторона прямоугольника смещается на 20 пикселов вниз от верхнего края экрана . Остается создать метод show_score() для вывода построенного графического изображения: scoreboard.py def show_score(self): «»»Выводит счет на экран.»»» self.screen.blit(self.score_image, self.score_rect)
Метод выводит счет на экран в позиции, определяемой score_rect.
Создание экземпляра Scoreboard Чтобы вывести счет, мы создадим в alien_invasion .py экземпляр Scoreboard: alien_invasion.py . from game_stats import GameStats from scoreboard import Scoreboard . def run_game(): . # Создание экземпляров GameStats и Scoreboard. stats = GameStats(ai_settings) sb = Scoreboard(ai_settings, screen, stats) . # Запуск основного цикла игры. while True: . gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button) run_game()
Мы импортируем новый класс Scoreboard и создаем экземпляр sb после создания экземпляра stats . Затем экземпляр sb передается методу update_screen(), чтобы счет можно было вывести на экран . Для отображения счета метод update_screen() изменяется следующим образом: game_functions.py def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button): . # Вывод счета. sb.show_score() # Кнопка Play отображается в том случае, если игра неактивна. if not stats.game_active: play_button.draw_button() # Отображение последнего прорисованного экрана. pygame.display.flip()
Мы добавляем sb в список параметров, определяющих update_screen(), и вызываем show_score() непосредственно перед отображением кнопки Play. Если запустить Alien Invasion сейчас, в правом верхнем углу экрана отображается счет 0. (Пока мы просто хотим убедиться в том, что счет отображается в нужном месте, прежде чем заниматься дальнейшей доработкой системы подсчета очков.) На рис. 14.2 изображено окно игры перед ее началом.
Рис. 14.2. Счет отображается в правом верхнем углу экрана
А теперь нужно организовать начисление очков за каждого пришельца!
Глава 14 • Ведение счета
Обновление счета при уничтожении пришельцев Чтобы на экране выводился оперативно обновляемый счет, мы будем обновлять значение stats.score при каждом попадании в пришельца, а затем вызывать prep_score() для обновления изображения счета. Но сначала нужно определить, сколько очков игрок будет получать за каждого пришельца: settings.py def initialize_dynamic_settings(self): . # Подсчет очков self.alien_points = 50
Стоимость каждого пришельца в очках будет увеличиваться по ходу игры. Чтобы значение сбрасывалось в начале каждой новой игры, мы задаем значение в initialize_dynamic_settings(). Счет будет обновляться за каждого сбитого пришельца в check_bullet_alien_ collisions(): game_functions.py def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets): «»»Обработка коллизий пуль с пришельцами.»»» # Удаление пуль и пришельцев, участвующих в коллизиях. collisions = pygame.sprite.groupcollide(bullets, aliens, True, True) if collisions: stats.score += ai_settings.alien_points sb.prep_score() .
Мы обновляем определение check_bullet_alien_collisions() и включаем параметры stats и sb, чтобы функция могла обновлять счет и рекорд. При попадании пули в пришельца Pygame возвращает словарь collisions. Программа проверяет, существует ли словарь, и если существует — стоимость пришельца добавляется к счету . Затем вызов prep_score() создает новое изображение для обновленного счета. Также необходимо обновить определение update_bullets(), чтобы соответствующие аргументы передавались между функциями: game_functions.py def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets): «»»Обновляет позиции пуль и удаляет старые пули.»»» . check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets)
Определению update_bullets() необходимы дополнительные параметры stats и sb. Кроме того, вызов check_bullet_alien_collisions() должен включать аргументы stats и sb.
Также необходимо изменить вызов update_bullets() в основном цикле while: alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, stats, play_button, ship, aliens, bullets) if stats.game_active: ship.update() gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets) .
При вызове update_bullets() должны передаваться аргументы stats и sb. Теперь во время игры вы сможете набирать очки!
Начисление очков за все попадания В том виде, в котором написан код, некоторые пришельцы будут пропускаться при подсчете. Например, если две пули попадают в пришельцев во время одного прохода цикла или если вы создадите широкую «пулю» для поражения нескольких пришельцев одновременно, игрок получит очки только за одного подстреленного пришельца. Чтобы устранить этот недостаток, нужно доработать механизм обнаружения коллизий между пулями и пришельцами. В функции check_bullet_alien_collisions() любая пуля, столкнувшаяся с пришельцем, становится ключом словаря collisions. С каждой пулей связывается значение — список пришельцев, участвующих в коллизии. Переберем словарь collisions и убедимся в том, что очки начисляются за каждого подбитого пришельца: game_functions.py def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets): . if collisions: for aliens in collisions.values(): stats.score += ai_settings.alien_points * len(aliens) sb.prep_score() .
Если словарь collisions был определен, программа перебирает все значения в словаре collisions. Вспомните, что каждое значение представляет собой список пришельцев, в которых попала одна пуля. Стоимость каждого пришельца умножается на количество пришельцев в списке, а результат прибавляется к текущему счету. Чтобы протестировать эту систему, увеличьте ширину пули до 300 пикселов и убедитесь в том, что игра начисляет очки за каждого пришельца, в которого попала эта большая пуля; затем верните ширину пули к нормальному состоянию.
Глава 14 • Ведение счета
Увеличение стоимости пришельцев Так как с каждым достижением нового уровня игра становится более сложной, за пришельцев на этих уровнях следует давать больше очков. Чтобы реализовать эту функциональность, мы добавим код, увеличивающий стоимость пришельцев при возрастании скорости игры: settings.py class Settings(): «»»Класс для хранения всех настроек игры Alien Invasion.»»» def __init__(self): . # Темп ускорения игры self.speedup_scale = 1.1 # Темп роста стоимости пришельцев self.score_scale = 1.5 self.initialize_dynamic_settings() def increase_speed(self): «»»Увеличивает настройки скорости и стоимость пришельцев.»»» self.ship_speed_factor *= self.speedup_scale self.bullet_speed_factor *= self.speedup_scale self.alien_speed_factor *= self.speedup_scale
self.alien_points = int(self.alien_points * self.score_ scale)
В программе определяется коэффициент прироста начисляемых очков; он называется score_scale . С небольшим увеличением скорости (1,1) игра быстро усложняется, но, чтобы увидеть заметную разницу в очках, необходимо изменять стоимость пришельцев в большем темпе (1,5). После увеличения скорости игры стоимость каждого попадания также увеличивается . Чтобы счет возрастал на целое количество очков, в программе используется функция int(). Чтобы увидеть стоимость каждого пришельца, добавьте в метод increase_speed() в классе Settings команду print: settings.py def increase_speed(self): . self.alien_points = int(self.alien_points * self.score_scale) print(self.alien_points)
Новое значение должно выводиться в терминальном окне каждый раз, когда игрок переходит на новый уровень. ПРИМЕЧАНИЕ Убедившись, что стоимость пришельцев действительно возрастает, не забудьте удалить команду print; в противном случае лишний вывод повлияет на быстродействие игры и будет отвлекать игрока .
Округление счета В большинстве аркадных «стрелялок» счет ведется значениями, кратными 10, и мы воспользуемся этой схемой в своей игре. Давайте отформатируем счет так, чтобы в больших числах группы разрядов разделялись запятыми. Изменения вносятся в классе Scoreboard: scoreboard.py def prep_score(self): «»»Преобразует текущий счет в графическое изображение.»»» rounded_score = int(round(self.stats.score, -1)) score_str = «».format(rounded_score) self.score_image = self.font.render(score_str, True, self.text_color, self.ai_settings.bg_color) .
Функция round() обычно округляет дробное число до заданного количества знаков, переданного во втором аргументе. Но если во втором аргументе передается отрицательное число, round() округляет значение до ближайших десятков, сотен, тысяч и т. д. Код приказывает Python округлить значение stats.score до десятков и сохранить его в rounded_score. ПРИМЕЧАНИЕ В Python 2 .7 функция round() всегда возвращает дробное значение, поэтому мы используем int(), чтобы гарантировать, что счет будет выводиться в виде целого числа . Если вы используете Python 3, вызов int() можно опустить .
Рис. 14.3. Округленный счет с разделителями групп
Глава 14 • Ведение счета
В точке директива форматирования строки приказывает Python вставить запятые при преобразовании числового значения в строку — например, чтобы вместо 1000000 выводилась строка 1,000,000. Теперь при запуске игры всегда будет отображаться аккуратно отформатированный, округленный счет (рис. 14.3).
Рекорды Каждый игрок желает превзойти предыдущий рекорд игры, поэтому мы будем отслеживать и выводить рекорды, чтобы у игрока была ясная цель. Рекорды будут храниться в классе GameStats: game_stats.py def __init__(self, ai_settings): . # Рекорд не должен сбрасываться. self.high_score = 0
Так как рекорд не должен сбрасываться при повторном запуске, значение high_ score инициализируется в __init__(), а не в reset_stats(). Теперь изменим класс Scoreboard для отображения рекорда. Начнем с метода __init__(): scoreboard.py def __init__(self, ai_settings, screen, stats): . # Подготовка изображений счетов. self.prep_score() self.prep_high_score()
Рекорд должен отображаться отдельно от текущего счета, поэтому для подготовки его изображения понадобится новый метод prep_high_score(): scoreboard.py def prep_high_score(self): «»»Преобразует рекордный счет в графическое изображение.»»» high_score = int(round(self.stats.high_score, -1)) high_score_str = «».format(high_score) self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.bg_color)
# Рекорд выравнивается по центру верхней стороны. self.high_score_rect = self.high_score_image.get_rect() self.high_score_rect.centerx = self.screen_rect.centerx self.high_score_rect.top = self.score_rect.top
Рекорд округляется до десятков и форматируется с запятыми . Затем для рекорда строится графическое изображение , выполняется горизонтальное выравнивание прямоугольника по центру экрана , а атрибут top прямоугольника приводится в соответствие с верхней стороной изображения счета .
Теперь метод show_score() выводит текущий счет в правом верхнем углу, а рекорд — в центре верхней стороны: scoreboard.py def show_score(self): «»»Выводит счет на экран.»»» self.screen.blit(self.score_image, self.score_rect) self.screen.blit(self.high_score_image, self.high_score_rect)
Для обновления рекорда в файл game_functions .py добавляется новая функция check_high_score(): game_functions.py def check_high_score(stats, sb): «»»Проверяет, появился ли новый рекорд.»»» if stats.score > stats.high_score: stats.high_score = stats.score sb.prep_high_score()
Рис. 14.4. Рекордный счет выводится в середине экрана
Функция check_high_score() получает два параметра, stats и sb. Параметр stats используется для проверки текущего счета и рекорда, а параметр sb необходим для изменения изображения рекорда при необходимости. В точке программа сравнивает текущий счет с рекордом. Если текущий счет выше, мы обновляем значение high_score и вызываем prep_high_score() для обновления изображения рекорда. Функция check_high_score() должна вызываться при каждом попадании в пришельца после обновления счета в check_bullet_alien_collisions():
Глава 14 • Ведение счета
game_functions.py def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets): . if collisions: for aliens in collisions.values(): stats.score += ai_settings.alien_points * len(aliens) sb.prep_score() check_high_score(stats, sb) .
Когда вы впервые играете в Alien Invasion, текущий счет одновременно будет наивысшим, поэтому он будет отображаться и как текущий счет, и как рекорд. Но в начале второй игры ваш предыдущий рекорд должен отображаться в середине, а текущий счет справа, как показано на рис. 14.4.
Вывод уровня Чтобы в игре выводился текущий уровень, сначала в класс GameStats следует включить атрибут для его представления. Чтобы уровень сбрасывался в начале каждой игры, инициализируйте его в reset_stats(): game_stats.py def reset_stats(self): «»»Инициализирует статистику, изменяющуюся в ходе игры.»»» self.ships_left = self.ai_settings.ship_limit self.score = 0 self.level = 1
Чтобы класс Scoreboard выводил текущий уровень (сразу же под текущим счетом), мы вызываем новый метод prep_level() из __init__(): scoreboard.py def __init__(self, ai_settings, screen, stats): . # Подготовка изображений счетов. self.prep_score() self.prep_high_score() self.prep_level()
Метод prep_level() выглядит так: scoreboard.py
def prep_level(self): «»»Преобразует уровень в графическое изображение.»»» self.level_image = self.font.render(str(self.stats.level), True, self.text_color, self.ai_settings.bg_color) # Уровень выводится под текущим счетом. self.level_rect = self.level_image.get_rect()
self.level_rect.right = self.score_rect.right self.level_rect.top = self.score_rect.bottom + 10
Метод prep_level() создает изображение на базе значения, хранящегося в stats. level , и приводит атрибут right изображения в соответствие с атрибутом right счета . Затем атрибут top сдвигается на 10 пикселов ниже нижнего края изображения текущего счета, чтобы между счетом и уровнем оставался пустой интервал . В метод show_score() также необходимо внести изменения: scoreboard.py def show_score(self): «»»Выводит текущий счет, рекорд и число оставшихся кораблей.»»» self.screen.blit(self.score_image, self.score_rect) self.screen.blit(self.high_score_image, self.high_score_rect) self.screen.blit(self.level_image, self.level_rect)
Добавленная строка выводит на экран изображение, представляющее уровень. Увеличение stats.level и обновление изображения уровня выполняются в check_ bullet_alien_collisions(): game_functions.py def check_bullet_alien_collisions(ai_settings, screen, stats, sb, ship, aliens, bullets): . if len(aliens) == 0: # Если весь флот уничтожен, начинается следующий уровень. bullets.empty() ai_settings.increase_speed()
# Увеличение уровня. stats.level += 1 sb.prep_level()
create_fleet(ai_settings, screen, ship, aliens)
Если все пришельцы уничтожены, программа увеличивает значение stats.level и вызывает prep_level() для обновления уровня . Чтобы убедиться в том, что изображения текущего счета и уровня правильно обновляются в начале новой игры, инициируйте сброс при нажатии кнопки Play: game_functions.py def check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y): «»»Запускает новую игру при нажатии кнопки Play.»»» button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) if button_clicked and not stats.game_active: . # Сброс игровой статистики. stats.reset_stats() stats.game_active = True # Сброс изображений счетов и уровня.
Глава 14 • Ведение счета sb.prep_score() sb.prep_high_score() sb.prep_level()
# Очистка списков пришельцев и пуль. aliens.empty() bullets.empty() .
Определению check_play_button() необходим объект sb. Чтобы сбросить изображения на экране, после сброса игровых настроек следуют вызовы prep_score(), prep_high_score() и prep_level(). Затем объект sb передается check_events(), чтобы объект Scoreboard был доступен для check_play_button(): game_functions.py def check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets): «»»Обрабатывает нажатия клавиш и события мыши.»»» for event in pygame.event.get(): if event.type == pygame.QUIT: . elif event.type == pygame.MOUSEBUTTONDOWN: mouse_x, mouse_y = pygame.mouse.get_pos() check_play_button(ai_settings, screen, stats, sb, play_ button, ship, aliens, bullets, mouse_x, mouse_y)
Определение check_events() должно получать sb в параметре, чтобы при вызове check_play_button() можно было передать sb в аргументе . Остается обновить вызов check_events() в alien_invasion .py, чтобы в нем также передавался объект sb: alien_invasion.py # Запуск основного цикла игры. while True: gf.check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets) .
Теперь количество пройденных уровней отображается на экране (рис. 14.5). ПРИМЕЧАНИЕ В некоторых классических играх выводимая информация снабжается текстовыми метками: «Уровень», «Рекорд» и т . д . Мы эти метки опустили, потому что смысл каждого числа понятен каждому, кто сыграл в Alien Invasion . Если вы включите эти метки, добавьте их в строки непосредственно перед вызовами font .render() в Scoreboard .
Рис. 14.5. Текущий уровень выводится под текущим счетом
Вывод количества кораблей Остается вывести количество кораблей, оставшихся у игрока, но на этот раз информация будет выводиться в графическом виде. Как во многих классических аркадных играх, в левом верхнем углу экрана программа рисует несколько изображений корабля. Каждый корабль обозначает одну оставшуюся попытку. Для начала нужно сделать так, чтобы класс Ship наследовал от Sprite, — это необходимо для создания группы кораблей: ship.py import pygame from pygame.sprite import Sprite class Ship(Sprite): def __init__(self, ai_settings, screen): «»»Инициализирует корабль и задает его начальную позицию.»»» super(Ship, self).__init__() .
Здесь мы импортируем Sprite, объявляем о наследовании Ship от Sprite и вызываем super() в начале __init__() . Далее необходимо изменить Scoreboard и создать группу кораблей для вывода на экран. Команды import и метод __init__() выглядят так: scoreboard.py import pygame.font from pygame.sprite import Group
Глава 14 • Ведение счета
from ship import Ship class Scoreboard(): «»»Класс для вывода игровой информации.»»» def __init__(self, ai_settings, screen, stats): . self.prep_level() self.prep_ships() .
Так как мы собираемся создать группу кораблей, программа импортирует классы Group и Ship. Метод prep_ships() будет вызываться после prep_level(). Он выглядит так: scoreboard.py
def prep_ships(self): «»»Сообщает количество оставшихся кораблей.»»» self.ships = Group() for ship_number in range(self.stats.ships_left): ship = Ship(self.ai_settings, self.screen) ship.rect.x = 10 + ship_number * ship.rect.width ship.rect.y = 10 self.ships.add(ship)
Метод prep_ships() создает пустую группу self.ships для хранения экземпляров кораблей . В ходе заполнения этой группы цикл выполняется по одному разу для каждого корабля, оставшегося у игрока . В цикле создается новый корабль, а координата x этого корабля задается так, чтобы корабли размещались рядом друг с другом, разделенные интервалами величиной 10 пикселов . Координата y задается так, чтобы корабли были смещены на 10 пикселов от верхнего края экрана и были выровнены по изображению текущего счета . Наконец, каждый корабль добавляется в группу ships . Следующим шагом становится вывод кораблей на экран: scoreboard.py def show_score(self): . self.screen.blit(self.level_image, self.level_rect) # Вывод кораблей. self.ships.draw(self.screen)
При выводе кораблей на экран мы вызываем метод draw() для группы, а Pygame рисует каждый отдельный корабль. Чтобы игрок видел, сколько попыток у него в начале игры, мы вызываем prep_ ships() при запуске новой игры. Это происходит в функции check_play_button() из файла game_functions .py: game_functions.py def check_play_button(ai_settings, screen, stats, sb, play_button, ship,
aliens, bullets, mouse_x, mouse_y): «»»Запускает новую игру при нажатии кнопки Play.»»» button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) if button_clicked and not stats.game_active: . # Сброс изображений счетов и уровня. sb.prep_score() sb.prep_high_score() sb.prep_level() sb.prep_ships() .
Метод prep_ships() также вызывается при столкновении пришельца с кораблем, чтобы изображение обновлялось при потере корабля: game_functions.py def update_aliens(ai_settings, screen, stats, sb, ship, aliens, bullets): . # Проверка коллизий «пришелец-корабль». if pygame.sprite.spritecollideany(ship, aliens): ship_hit(ai_settings, screen, stats, sb, ship, aliens, bullets) # Проверяет, добрались ли пришельцы до нижнего края экрана. check_aliens_bottom(ai_settings, screen, stats, sb, ship, aliens, bullets) def ship_hit(ai_settings, screen, stats, sb, ship, aliens, bullets): «»»Обрабатывает столкновение корабля с пришельцем.»»» if stats.ships_left > 0: # Уменьшение ships_left. stats.ships_left -= 1
# Обновление игровой информации. sb.prep_ships()
# Очистка списков пришельцев и пуль. .
Сначала параметр sb добавляется в определение update_aliens() . Затем программа передает sb функциям ship_hit() и check_aliens_bottom(), чтобы эти функции имели доступ к объекту Scoreboard . Затем определение ship_hit() изменяется с включением sb . Метод prep_ships() вызывается после уменьшения значения ships_left , так что при каждой потере корабля выводится правильное количество изображений. Вызов ship_hit() также включен в check_aliens_bottom(), так что эту функцию тоже нужно обновить: game_functions.py def check_aliens_bottom(ai_settings, screen, stats, sb, ship, aliens, bullets): «»»Проверяет, добрались ли пришельцы до нижнего края экрана.»»»
Глава 14 • Ведение счета
screen_rect = screen.get_rect() for alien in aliens.sprites(): if alien.rect.bottom >= screen_rect.bottom: # Происходит то же, что при столкновении с кораблем. ship_hit(ai_settings, screen, stats, sb, ship, aliens, bullets) break
Так как check_aliens_bottom() теперь получает параметр sb, мы добавляем аргумент sb в вызов ship_hit(). Остается добавить sb в вызов update_aliens() в файле alien_invasion .py: alien_invasion.py # Запуск основного цикла игры. while True: . if stats.game_active: ship.update() gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets) gf.update_aliens(ai_settings, screen, stats, sb, ship, aliens, bullets) .
На рис. 14.6 показана полная игровая информация на экране, с количеством оставшихся кораблей в левой верхней части экрана.
Рис. 14.6. Полная игровая информация в Alien Invasion
УПРАЖНЕНИЯ 14-4 . Исторический рекорд: в текущей версии рекорд сбрасывается каждый раз, когда игрок закрывает и перезапускает Alien Invasion . Чтобы этого не происходило, запишите рекорд в файл перед вызовом sys .exit() и загрузите его при инициализации значения в GameStats . 14-5 . Рефакторинг: найдите функции и методы, которые решают более одной задачи, и проведите рефакторинг, улучшающий структуру и эффективность кода . Например, переместите часть кода функции check_bullet_alien_collisions(), которая запускает новый уровень при уничтожении флота, в функцию start_new_level() . Также переместите четыре метода, вызываемых в методе __init__() класса Scoreboard, в метод prep_images() для сокращения длины __init__() . Метод prep_images() также может оказать помощь check_play_button() или start_game(), если вы уже провели рефакторинг check_play_button() . ПРИМЕЧАНИЕ Прежде чем браться за рефакторинг проекта, обратитесь к приложению Г . В нем рассказано, как восстановить рабочее состояние проекта, если в ходе рефакторинга были допущены ошибки . 14-6 . Расширение Alien Invasion: подумайте над возможными расширениями Alien Invasion . Например, пришельцы тоже могут стрелять по кораблю, или же вы можете добавить укрытия, за которыми может скрываться корабль (укрытия могут разрушаться пулями с обеих сторон) . Или добавьте звуковые эффекты (например, взрывы или звуки выстрелов) средствами модуля pygame .mixer .
Итоги В этой главе вы узнали, как создать кнопку для запуска новой игры, как обнаруживать события мыши и скрывать указатель мыши в активных играх. Полученные знания помогут вам создать другие кнопки в играх, например кнопку для вывода инструкций по игре. Также вы научились изменять скорость по ходу игры, создавать прогрессивную систему подсчета очков и выводить информацию в текстовом и графическом виде.
Проект 2 . Визуализация данных
Под визуализацией данных понимается исследование данных через их визуальное представление. Визуализация тесно связана с анализом данных (data mining), использующим программный код для изучения закономерностей и связей в наборе данных. Набором данных может быть как маленький список чисел, помещающийся в одной строке кода, так и массивом из многих гигабайт. Качественное представление данных не сводится к красивой картинке. Если для набора данных подобрано простое, визуально привлекательное представление, его смысл становится очевидным для зрителя. Люди замечают в наборе данных закономерности, о которых они и не подозревали. К счастью, для визуализации сложных данных не нужен суперкомпьютер. Благодаря эффективности Python вы сможете быстро исследовать наборы данных из миллионов отдельных элементов данных (точек данных) на обычном ноутбуке. Элементы данных даже не обязаны быть числовыми. Приемы, о которых вы узнали в первой части книги, позволят вам проанализировать даже нечисловые данные. Python используется для обработки данных в генетике, исследовании климата, политическом и экономическом анализе и множестве других областей. Специалисты по обработке данных написали на Python впечатляющий инструментарий визуализации и анализа, и многие из этих разработок также доступны и для вас. Один из самых популярных инструментов такого рода — matplotlib, математическая библиотека построения диаграмм. С помощью matplotlib можно строить простые диаграммы, графики, диаграммы разброса данных и т. д. После этого будет создан более интересный набор данных, основанный на концепции случайного блуждания — визуализации, генерируемой на базе серии случайных решений. Также в этом проекте будет использоваться пакет Pygal, ориентированный на создание визуализаций, хорошо работающих с цифровыми устройствами. С помощью Pygal можно выделять и изменять размеры элементов в ходе взаимодействия пользователя с визуализацией; кроме того, размер визуализации легко изменяется под крошечные «умные часы» или гигантский монитор. Мы используем Pygal для исследования закономерностей различных бросков кубиков.
Установка matplotlib Сначала необходимо установить библиотеку matplotlib, которая будет использоваться в исходном наборе визуализаций. Если вы еще не использовали про-
Глава 15 • Генерирование данных
грамму pip, обращайтесь к разделу «Установка пакетов Python с использованием pip» (с. 227).
В Linux Если вы используете версию Python, входящую в поставку системы, вы сможете использовать менеджер пакетов своей системы для установки matplotlib всего одной командой: $ sudo apt-get install python3-matplotlib
Если вы используете Python 2.7, команда выглядит так: $ sudo apt-get install python-matplotlib
Если у вас установлена более новая версия Python, вам придется установить еще несколько библиотек, от которых зависит matplotlib: $ sudo apt-get install python3.5-dev python3.5-tk tk-dev $ sudo apt-get install libfreetype6-dev g++
Затем программа pip используется для установки matplotlib: $ pip install —user matplotlib
В OS X Компания Apple включает matplotlib в свою стандартную установку Python. Чтобы проверить, установлена ли библиотека в вашей системе, откройте терминальный сеанс и попробуйте импортировать matplotlib. Если библиотека matplotlib еще не установлена в системе и вы использовали Homebrew для установки Python, установите ее следующей командой: $ pip install —user matplotlib ПРИМЕЧАНИЕ Возможно, при установке пакетов вам придется использовать команду pip3 вместо pip . Если же эта команда не работает, попробуйте исключить флаг —user .
В Windows В системе Windows сначала необходимо установить Visual Studio. Откройте страницу https://dev.windows.com/, щелкните на ссылке Скачать средства (Downloads) и найдите Visual Studio Community — бесплатный набор средств разработчика для Windows. Загрузите и запустите программу установки. Затем вам понадобится программа установки для matplotlib . Обратитесь по адресу https://pypi.python.org/pypi/matplotlib/ и найдите файл с расширением .whl , соответствующий используемой версии Python. Например, если вы используете 32-разрядную версию Python 3.5, загрузите файл matplotlib-1 .4 .3cp35-none-win32 .whl.
Построение простого графика
ПРИМЕЧАНИЕ Если вы не нашли файл, соответствующий используемой версии Python, просмотрите возможные варианты по адресу http://www .lfd .uci .edu/~gohlke/pythonlibs/#matplotlib . На этом сайте новые пакеты появляются немного ранее, чем на официальном сайте matplotlib .
Скопируйте файл .whl в каталог проекта, откройте окно командной строки и перейдите в каталог проекта. Используйте pip для установки matplotlib: > cd python_work python_work> python -m pip install —user matplotlib-1.4.3-cp35-none-win32.whl
Тестирование matplotlib После того как необходимые пакеты будут установлены, протестируйте свою установку. Для этого откройте сеанс командной строки командой python или python3 и импортируйте matplotlib: $ python3 >>> import matplotlib >>>
Если на экране не появились сообщения об ошибках, значит, библиотека matplotlib установлена в вашей системе и вы можете переходить к следующему разделу. ПРИМЕЧАНИЕ Если у вас возникнут проблемы с установкой, поищите информацию в приложении В . Если ничего не помогает, обратитесь за помощью . Скорее всего, опытный программист Python быстро найдет причины возникших проблем с минимумом информации от вас .
Галерея matplotlib Чтобы получить представления о визуализациях, которые можно строить в matplotlib, посетите галерею на сайте http://matplotlib.org/. Щелкая на визуализации в галерее, вы сможете просмотреть код, использованный для ее построения.
Построение простого графика Начнем с построения простого линейного графика с использованием matplotlib, а затем настроим его для более содержательной визуализации данных. В качестве данных для графика будет использоваться последовательность квадратов 1, 4, 9, 16 и 25. Передайте matplotlib числа так, как показано ниже, а matplotlib сделает все остальное: mpl_squares.py import matplotlib.pyplot as plt squares = [1, 4, 9, 16, 25] plt.plot(squares) plt.show()
Глава 15 • Генерирование данных
Сначала импортируйте модуль pyplot с псевдонимом plt, чтобы вам не приходилось многократно вводить имя pyplot. (Это сокращение часто встречается в примерах на сайте, поэтому мы поступим так же.) Модуль pyplot содержит ряд функций для построения диаграмм и графиков. Мы создаем список для хранения квадратов и передаем его функции plot(), которая пытается построить осмысленное графическое представление для заданных чисел. Вызов plt.show() открывает окно просмотра matplotlib и выводит график (рис. 15.1). В окне просмотра можно изменять масштаб и перемещаться по построенному графику, а кнопка с диском позволяет сохранить любое изображение по вашему выбору.
Рис. 15.1. Пример простейшего графика в matplotlib
Изменение типа надписей и толщины графика Хотя из графика на рис. 15.1 видно, что числовая последовательность возрастает, текст надписей слишком мелкий, а линия слишком тонкая. К счастью, matplotlib позволяет настроить практически каждый аспект визуализации. Мы используем эти возможности настройки для того, чтобы сделать график более выразительным: mpl_squares.py import matplotlib.pyplot as plt squares = [1, 4, 9, 16, 25] plt.plot(squares, linewidth=5) # Назначение заголовка диаграммы и меток осей. plt.title(«Square Numbers», fontsize=24) plt.xlabel(«Value», fontsize=14)
Построение простого графика
plt.ylabel(«Square of Value», fontsize=14) # Назначение размера шрифта делений на осях. plt.tick_params(axis=’both’, labelsize=14) plt.show()
Параметр linewidth управляет толщиной линии, которая строится вызовом plot(). Функция title() назначает заголовок диаграммы. Параметры fontsize , неоднократно встречающиеся в коде, управляют размером текста диаграммы. Функции xlabel() и ylabel() позволяют назначить метки (заголовки) каждой из осей , а функция tick_params() определяет оформление делений на осях . Аргументы, использованные в данном примере, относятся к делениям на обоих осях (axes=’both’) и устанавливают для меток делений размер шрифта 14 (labelsize=14). Как видно из рис. 15.2, график выглядит гораздо лучше. Текст надписей стал крупнее, а линия графика — толще.
Рис. 15.2. График выглядит гораздо лучше
Корректировка графика Теперь, когда текст на графике стал нормально читаться, мы видим, что данные помечены неправильно. Обратите внимание: для точки 4,0 в конце графика указан квадрат 25! Давайте исправим эту ошибку. Если plot() передает числовую последовательность, функция считает, что первый элемент данных соответствует координате x со значением 0, но в нашем примере первая точка соответствует значению 1. Чтобы переопределить значение по умолчанию, передайте plot() как входные значения, так и квадраты:
Глава 15 • Генерирование данных
mpl_squares.py import matplotlib.pyplot as plt input_values = [1, 2, 3, 4, 5] squares = [1, 4, 9, 16, 25] plt.plot(input_values, squares, linewidth=5) # Назначение заголовка диаграммы и меток осей. .
Теперь plot() правильно строит график, потому что мы предоставили оба набора значений, и функции не нужно предполагать, как был сгенерирован выходной набор чисел. На рис. 15.3 изображен правильный график.
Рис. 15.3. График с правильными данными
При вызове plot() можно передавать многочисленные аргументы, а также использовать различные функции для настройки графиков. Знакомство с этими функциями продолжится позднее, когда мы начнем работать с более интересными наборами данных в этой главе.
Нанесение и оформление отдельных точек функцией scatter() Иногда бывает полезно нанести на график отдельные точки, основанные на некоторых характеристиках, и определить их оформление. Например, на графике малые и большие значения могут отображаться разными цветами. Возможны и другие варианты: например, сначала нанести множество точек с одним типом оформления, а затем выделить отдельные точки набора, перерисовав их с другим оформлением. Для нанесения на диаграмму отдельной точки используется функция scatter(). Передайте scatter() координаты (x, y) нужной точки, и функция нанесет эти значения на диаграмму:
Построение простого графика
scatter_squares.py import matplotlib.pyplot as plt plt.scatter(2, 4) plt.show()
Применим оформление, чтобы результат выглядел более интересно. Мы добавим название, метки осей, а также увеличим шрифт, чтобы текст нормально читался: import matplotlib.pyplot as plt plt.scatter(2, 4, s=200) # Назначение заголовка диаграммы и меток осей. plt.title(«Square Numbers», fontsize=24) plt.xlabel(«Value», fontsize=14) plt.ylabel(«Square of Value», fontsize=14) # Назначение размера шрифта делений на осях. plt.tick_params(axis=’both’, which=’major’, labelsize=14) plt.show()
В точке вызывается функция; аргумент s задает размер точек, используемых для рисования диаграммы. Если запустить программу scatter_squares .py в текущем состоянии, вы увидите одну точку в середине диаграммы (рис. 15.4).
Рис. 15.4. Вывод одной точки
Вывод серии точек функцией scatter() Чтобы вывести на диаграмме серию точек, передайте scatter() списки значений координат x и y:
Глава 15 • Генерирование данных
scatter_squares.py import matplotlib.pyplot as plt x_values = [1, 2, 3, 4, 5] y_values = [1, 4, 9, 16, 25] plt.scatter(x_values, y_values, s=100) # Назначение заголовка диаграммы и меток осей. .
Список x_values содержит числа, возводимые в квадрат, а в y_values содержатся квадраты. При передаче этих списков scatter() библиотека matplotlib читает по одному значению из каждого списка и наносит их на диаграмму как точку. Таким образом, на диаграмму будут нанесены точки (1, 1), (2, 4), (3, 9), (4, 16) и (5, 25); результат показан на рис. 15.5.
Рис. 15.5. Точечная диаграмма с несколькими точками
Автоматическое вычисление данных Строить списки вручную неэффективно, особенно при большом объеме данных. Вместо того чтобы передавать данные в виде списка, мы воспользуемся циклом Python, который выполнит вычисления за нас. Вот как выглядит такой цикл для 1000 точек: scatter_squares.py import matplotlib.pyplot as plt x_values = list(range(1, 1001)) y_values = [x**2 for x in x_values] plt.scatter(x_values, y_values, s=40)
Построение простого графика
# Назначение заголовка диаграммы и меток осей. . # Назначение диапазона для каждой оси. w plt.axis([0, 1100, 0, 1100000]) plt.show()
Все начинается со списка значений координаты x с числами от 1 до 1000 . Затем генератор списка строит значения y, перебирая значения x (for x in x_values), возводя каждое число в квадрат (x**2) и сохраняя результаты в y_values. Затем оба списка (входной и выходной) передаются scatter() . Набор данных достаточно велик, поэтому мы выбираем меньший размер шрифта, а функция axis() используется для задания диапазона каждой оси . Функция axis() получает четыре значения: минимум и максимум по осям x и y. В данном случае по оси x откладывается диапазон от 0 до 1100, а по оси y — диапазон от 0 до 1 100 000. На рис. 15.6 показан результат.
Рис. 15.6. Диаграмма с 1000 точками строится так же легко, как и диаграмма с 5 точками
Удаление контуров точек Библиотека matplotlib позволяет раскрашивать точки на точечной диаграмме в разные цвета. Оформление по умолчанию — синие точки с черным контуром — хорошо подходит для диаграмм с несколькими точками. Однако при большом количестве точек черные контуры начинают сливаться. Чтобы удалить контуры вокруг точек, передайте аргумент edgecolor=’none’ при вызове scatter(): plt.scatter(x_values, y_values, edgecolor=’none’, s=40)
Запустите scatter_squares .py с этим вызовом, и вы увидите, что на диаграмме выводятся только заполненные синие точки.
Глава 15 • Генерирование данных
Определение пользовательских цветов Чтобы изменить цвет точек, передайте scatter() аргумент c с именем используемого цвета: plt.scatter(x_values, y_values, c=’red’, edgecolor=’none’, s=40)
Также возможно определять пользовательские цвета в цветовой модели RGB. Чтобы определить цвет, передайте аргумент c с кортежем из трех дробных значений (для красной, зеленой и синей составляющих) в диапазоне от 0 до 1. Например, следующая строка создает диаграмму со светло-синими точками: plt.scatter(x_values, y_values, c=(0, 0, 0.8), edgecolor=’none’, s=40)
Значения, близкие к 0, дают более темные цвета, а со значениями, близкими к 1, цвета получаются более светлыми.
Цветовые карты Цветовая карта (colormap) представляет собой серию цветов градиента, определяющую плавный переход от начального цвета к конечному. Цветовые карты используются в визуализациях для выделения закономерностей в данных. Например, малые значения можно обозначить светлыми цветами, а большие — темными. Модуль pyplot включает набор встроенных цветовых карт. Чтобы воспользоваться одной из готовых карт, нужно указать, как модуль pyplot должен присваивать цвет каждой точке набора данных. В следующем примере цвет каждой точки присваивается на основании значения y: scatter_squares.py import matplotlib.pyplot as plt x_values = list(range(1001)) y_values = [x**2 for x in x_values] plt.scatter(x_values, y_values, c=y_values, cmap=plt.cm.Blues, edgecolor=’none’, s=40) # Назначение заголовка диаграммы и меток осей. .
Мы передаем в c список y-values, а затем указываем pyplot, какая цветовая карта должна использоваться, при помощи аргумента cmap . Следующий код окрашивает точки с меньшими значениями y в светло-синий цвет, а точки с бульшими значениями y — в темно-синий цвет. Полученная диаграмма изображена на рис. 15.7. ПРИМЕЧАНИЕ Все цветовые карты, доступные в pyplot, можно просмотреть на сайте http://matplotlib .org/; откройте раздел Examples, прокрутите содержимое до пункта Color Examples и щелкните на ссылке colormaps_reference .
Рис. 15.7. Точечная диаграмма с цветовой картой Blues
Автоматическое сохранение диаграмм Если вы хотите, чтобы программа автоматически сохраняла диаграмму в файле, замените вызов plt.show() вызовом plt.savefig(): plt.savefig(‘squares_plot.png’, bbox_inches=’tight’)
Первый аргумент содержит имя файла для сохранения диаграммы; файл будет сохранен в одном каталоге с scatter_squares .py. Второй аргумент отсекает от диаграммы лишние пропуски. Если вы хотите оставить пустые места вокруг диаграммы, этот аргумент можно опустить. УПРАЖНЕНИЯ 15-1 . Кубы: число, возведенное в третью степень, называется «кубом» . Нанесите на диаграмму первые пять кубов, а затем первые 5000 кубов . 15-2 . Цветные кубы: примените цветовую карту к диаграмме с кубами .
Случайное блуждание В этом разделе мы используем Python для генерирования данных для случайного обхода, а затем при помощи matplotlib создадим привлекательное представление сгенерированных данных. Случайным блужданием (random walk) называется путь, который не имеет четкого направления, но определяется серией полностью случайных решений. Представьте, что муравей сошел с ума и делает каждый новый шаг в случайном направлении; его путь напоминает случайное блуждание. Случайное блуждание находит практическое применение в естественных науках: физике, биологии, химии и экономике. Например, пыльцевое зерно на поверхности водяной капли движется по поверхности воды, потому что его постоянно подталкивают молекулы воды. Молекулярное движение в капле воды случайно,
Глава 15 • Генерирование данных
поэтому путь пыльцевого зерна на поверхности представляет собой случайное блуждание. Код, который мы напишем, найдет применение при моделировании многих реальных ситуаций.
Создание класса RandomWalk() Чтобы создать путь случайного блуждания, мы создадим класс RandomWalk, который принимает случайные решения по выбору направления. Классу нужны три атрибута: переменная для хранения количества точек в пути и два списка для координат x и y каждой точки. Класс RandomWalk содержит всего два метода: __init__() и fill_walk() для вычисления точек случайного блуждания. Начнем с метода __init__(): random_walk.py from random import choice class RandomWalk(): «»»Класс для генерирования случайных блужданий.»»»
def __init__(self, num_points=5000): «»»Инициализирует атрибуты блуждания.»»» self.num_points = num_points # Все блуждания начинаются с точки (0, 0). self.x_values = [0] self.y_values = [0]
Чтобы принимать случайные решения, мы сохраним возможные варианты в списке и используем функцию choice() для принятия решения . Затем для списка устанавливается количество точек по умолчанию равным 5000 — достаточно большим, чтобы генерировать интересные закономерности, но достаточно малым, чтобы блуждания генерировались быстро . Затем в точке создаются два списка для хранения значений x и y, после чего каждый путь начинается с точки (0, 0).
Выбор направления Метод fill_walk(), как показано ниже, заполняет путь точками и определяет направление каждого шага. Добавьте этот метод в random_walk .py: random_walk.py def fill_walk(self): «»»Вычисляет все точки блуждания.»»»
# Шаги генерируются до достижения нужной длины. while len(self.x_values) >> from datetime import datetime >>> first_date = datetime.strptime(‘2014-7-1’, ‘%Y-%m-%d’) >>> print(first_date) 2014-07-01 00:00:00
Сначала необходимо импортировать класс datetime из модуля datetime. Затем вызывается метод strptime(), первый аргумент которого содержит строку с датой. Второй аргумент сообщает Python, как отформатирована дата. В данном примере значение ‘%Y-‘ сообщает Python, что часть строки, предшествующая первому дефису, должна интерпретироваться как год из четырех цифр; ‘%m-‘ приказывает Python интерпретировать часть строки перед вторым дефисом как число, представляющее месяц; наконец, ‘%d’ приказывает Python интерпретировать последнюю часть строки как день месяца от 1 до 31. Метод strptime() может получать различные аргументы, которые описывают, как должна интерпретироваться запись даты. В табл. 16.1 перечислены некоторые из таких аргументов. Таблица 16.1. Аргументы форматирования даты и времени из модуля datetime Аргумент
Название дня недели — например, Monday
Название месяца — например, January
Порядковый номер месяца (от 01 до 12)
День месяца (от 01 до 31)
Год из четырех цифр (например, 2015)
Что отображается после ввода следующего кода в интерпретатор python addition 22 10 print addition

Главное о библиотеке NumPy
- NumPy нужна для решений из линейной алгебры, реализации функций для бесперебойной работы со случайными величинами и прямого и обратного преобразования Фурье.
- Есть NumPy-функции для определения количества измерений, подсчета строк и столбцов, создания специальных массивов, математических операций и многого другого.
- Установить NumPy можно двумя способами: через Anaconda и PIP.
где X — записываемое число;
Например: 1 592 916 = 1,592 916 ∗ 10 6 ; 0,00027,459 = 27,459 ∗ 10 -5 . На мониторе вы увидите следующие значения:
В предназначенном месте памяти хранится целое число фиксированной длины и последовательность вносимого значения.
Строковые
Строка string в JavaScript — часть текста или определенный заведомо установленный порядок знаков. Их закавычивают.
Отдельные языки применяют специальный вид данных в программировании, с его помощью заносят символ. Например, в языке С — char.
Указатели
Указатель — это объект, который хранит определенный адрес в памяти компьютера. Значение указателя — это физический адрес определенных данных: указатель указывает на эти данные.
Получение данных по адресу, который хранится в указателе, называют разыменованием указателя. При создании указателя перед названием переменной добавляют *. Указатель может быть нулевым и не указывать ни на один объект.
Идентификационные
Конечный набор значений определяет перечисляемый тип. Как правило, его представляет список идентификаторов в объявлении типа.
Разберем пример на языке С++ (в С# и Java — аналогично):
В случае когда идентификатор указывается в списке значений перечисляемого типа, он становится наименованием константы, которая определена в блоке типа.
Абстрактные
У абстрактного типа данных (АТД) есть преимущества перед остальными типами:
- Инкапсуляция деталей реализации. Инкапсуляция — ограничение доступа к компонентам, составляющим объект. Достаточно один раз инкапсулировать, чтобы пользователь смог самостоятельно взаимодействовать с АТД через интерфейс. Если детали реализаций поменяют, то представления клиентов о работе не изменятся.
- Снижение уровня сложности через абстрагирование от деталей реализации. Это значит, что детали скрывают и сосредотачиваются на ключевых аспектах системы. Так можно сосредоточиться на том, что делает АТД.
- Ограничение области использования данных. Все данные структуры АТД не зависят от других участков кода.
- Высокая информативность интерфейса.
Примеры
- В языке Python используют следующие типы данных программирования:
- int — целочисленный;
- char — символьный;
- bool — логический;
- float — с плавающей запятой;
- double — с плавающей запятой двойной точности.
Освойте Python на курсе от Skypro. Вас ждут 400 часов обучения и практики (достаточно десяти часов в неделю), подготовка проектов для портфолио, индивидуальная проверка домашних заданий и помощь опытных наставников. Получится, даже если у вас нет опыта в IT.

- Язык программирования JavaScript содержит следующие типы данных:
- string — тип данных «строка»;
- number — «число»;
- object — тип данных, хранящий свойства и методы;
- undefined — тип данных, значения которых не определены;
- boolean — логический;
- null — с «пустыми» значениями.
JavaScript можно изучить на курсе Skypro «Веб-разработчик». Программа построена от простого к сложному, в ней много практики. Уже к концу второго блока обучения сможете самостоятельно сделать лендинг с играми на JavaScript.
Самоприменение типов данных
У типа данных может быть особенность параметризации с каким-либо другим типом, но строго подчиненная и соотнесенная с принципами абстракции и параметричности.
Под теорией типов понимают математически формализованную базу, которая служит как для проектирования и анализа, так и для изучения систем типов данных в теории языков программирования. За всё это отвечает специально выделенный раздел в информатике.
Пример. Чтобы функция сортировки последовательностей могла быть реализована, необязательно знать свойства всех элементов, которые ее составляют. Важен допуск операции по сравнению и анализу данных. Тогда параметр «последовательности» станет параметрически полиморфным. Составляющие такого типа будут определяться и использоваться не с конкретными типами, а с параметрами-типами. Но параметрически полиморфный тип может быть представлен как конструктор других любых типов. Он становится оператором над типами в арифметической реальности.
В программировании и информатике есть понятия «отражение», «рефлексивность» или «интроспекция». Они обозначают одно и то же — выявление метатипов в динамике.
Представление в компьютере
Реальное программирование отличается от формальной теории информации. Главное отличие — в изучении вопросов об эффективности.
Отличие заключается в понятии «число». Этот термин в науке об ЭВМ и на практике абсолютно неодинаков. Число на ЭВМ понимается как ячейка электронной памяти. Размер кластера зависит от архитектуры ЭВМ и диапазона значений числа. Он обязательно ограничен, а стоп-объем равен объему ячейки.
Целые и вещественные числа в ячейках представлены по-разному. Положительные или неотрицательные числа представляются непосредственно, а вот существенно отрицательные целые числа — в дополнительном коде.
2 ноября 18:00 МСК
Как без опыта и навыков гарантировано перейти на удаленную работу в 2023 году

Главное: что такое тип данных
- Тип данных — это характеристика значений, которые принимает некоторая переменная или выражение.
- Есть семь типов данных: логические, целочисленные, с плавающей запятой, строковые, указатели, идентификационные, абстрактные.
- В языке Python используют пять типов, а в JavaScript — шесть типов данных.
Примеры использования парадигм программирования
Процедурная — для написания кода микрочипа Здесь может быть особенно специфический процессор для разработки компилятора сложного объектно-ориентированного языка. Но надо помнить, что технических возможностей чипа может быть мало, чтобы программы нормально функционировали на современных языках. Объектно-ориентированная Есть много приложений для компьютеров и смартфонов, популярных интернет-сервисов, «умных» телевизоров и роутеров, которые написаны на объектно-ориентированном языке. Декларативная — для описания внешнего вида сайтов Здесь пишут на языке CSS. Через него указывают шрифт текста, цвет фона, размер картинки. Функциональная — для криптовалюты и блокчейна В этих областях главное — надежность. Они новые, поэтому открыты для экспериментов. Прочие языки стирают грань между математикой и программированием — это позволяет программировать математические доказательства. Практически все парадигмы используют для промышленных программ общего назначения, например различные редакторы текста и графики, таблиц, приложений и т. д. Но есть такие, которые используют в языках для специфических задач. Например, логическую парадигму разработчики берут для решения математических уравнений, а язык Prolog решает судоку.
Сравнение парадигм программирования
Представим, что есть массив следующих чисел:
412, 5, 625, 243, 2, 14, 30, 79, 4, 634, 367, 8, 34, 798, 159
В декларативном программировании разработчик пишет:
Получить из массива N все числа, которые больше 5 и меньше 500
В этом случае разработчику нужен исключительно конечный результат, а не процесс исполнения задачи. Если выбрать императивный вариант программирования, то придется самостоятельно написать код, проверяющий все числа из массива N на соответствие условию. Код должен уметь выписывать их в новый массив.
Создать массив N с числами …; объявить переменную i со значением 0; Начало цикла: Если значение элемента номер i больше 5 и меньше 500, то добавить его в массив N2; Прибавить к переменной i единицу; Повторять, пока значение переменной i меньше количества элементов массива N; Конец цикла; Вернуть массив N2;
n = arr = [412, 5, 625, 243, 2, 14, 30, 79, 4, 634, 367, 8, 34, 798, 159] result = [] for i in range(len(arr)): if arr[i] > 5 and arr[i] < 500: result.append(arr[i]) print(result)
n = [412, 5, 625, 243, 2, 14, 30, 79, 4, 634, 367, 8, 34, 798, 159] result = list(filter(lambda x: x > 5 and x < 500, arr)) print(result)
- Декларативное программирование не работает без императивного.
- На декларативном языке нельзя работать с задачами, для решения которых нужен доступ к состоянию программы.
- У императивного программирования меньше ограничений.
Главное
- Парадигма — это набор взаимосвязанных требований и принципов, которые используют в процессе разработки программного обеспечения для ПК.
- От парадигм зависят любые этапы программирования: шаблоны, свойства, паттерны, структура, архитектура программы или мобильного приложения.
- Есть императивное программирование и декларативное. Императивное включает в себя процедурное и объектно-ориентированное программирование. Декларативное — логическое и функциональное.
https://sky.pro/media/rabota-s-vremenem-v-python-lokalnoe-vremya-ili-utc/ time предлагает функцию time.time() для получения текущего времени. Но что она возвращает — локальное время или время в формате UTC?
import time print(time.time())
Если запустить этот код, мы увидим большое число с плавающей точкой. Это так называемая «unix-таймстампа» (Unix timestamp), которая представляет собой количество секунд, прошедших с полуночи 1 января 1970 года по Гринвичу. Но какое время она отражает — локальное или все-таки UTC? Ответ на этот вопрос прост: функция time.time() возвращает время в формате UTC. Это общепринятый стандарт времени, который используется во всем мире и не зависит от часовых поясов. Это значит, что результат работы функции будет одинаковым независимо от того, где именно выполняется ваш код. Если же необходимо получить текущее локальное время, можно воспользоваться другими функциями модуля time . Например, функция time.localtime() возвращает структуру, которая содержит текущую дату и время с учетом локального часового пояса.
import time print(time.localtime())
https://sky.pro/media/rabota-s-bolshimi-dannymi-v-pandas/ append или concat библиотеки Pandas.
Также стоит учесть, что выборка данных из базы данных может быть выполнена не только по столбцам, но и по строкам. Это может быть полезно для создания отчетов или генерации статистических данных.
В заключение стоит отметить, что, несмотря на некоторые трудности, Pandas является мощным инструментом для работы с большими данными, и его возможности существенно расширяются при использовании в совокупности с внешними базами данных.
https://sky.pro/media/vyvod-vseh-soobshhenij-loggera-python-v-stdout-i-log-fajl/ logging в Python, который предоставляет гибкие возможности для конфигурации логгирования. Основной компонент в модуле logging , который используется для вывода сообщений, называется Handler. Существуют разные типы обработчиков, которые можно использовать для вывода сообщений в различные места: файлы, stdout, сеть и т.д. Для вывода сообщений в stdout и файл лога одновременно, можно настроить два обработчика: StreamHandler и FileHandler . StreamHandler выводит сообщения в stdout (или любой другой поток), а FileHandler — в файл.
import logging logger = logging.getLogger(__name__) stream_handler = logging.StreamHandler() file_handler = logging.FileHandler('logfile.log') logger.addHandler(stream_handler) logger.addHandler(file_handler)
https://sky.pro/media/reshenie-problemy-ierarhicheskogo-indeksa-v-python-na-primere-pandas/ groupby.agg получается DataFrame с иерархическим индексом. Такой индекс может быть в строках или столбцах, и его уровни обычно разделяются запятыми. Вот пример такого DataFrame:
USAF WBAN year month day s_PC s_CL s_CD s_CNT tempf sum sum sum sum amax amin 0 702730 26451 1993 1 1 1 0 12 13 30.92 24.98 1 702730 26451 1993 1 2 0 0 13 13 32.00 24.98 2 702730 26451 1993 1 3 1 10 2 13 23.00 6.98 3 702730 26451 1993 1 4 1 0 12 13 10.04 3.92 4 702730 26451 1993 1 5 3 0 10 13 19.94 10.94
Иногда бывает необходимо преобразовать этот иерархический индекс в обычный «плоский» индекс, чтобы упростить работу с данными. В данном случае требуется получить такой DataFrame:
USAF WBAN year month day s_PC s_CL s_CD s_CNT tempf_amax tmpf_amin 0 702730 26451 1993 1 1 1 0 12 13 30.92 24.98 1 702730 26451 1993 1 2 0 0 13 13 32.00 24.98 2 702730 26451 1993 1 3 1 10 2 13 23.00 6.98 3 702730 26451 1993 1 4 1 0 12 13 10.04 3.92 4 702730 26451 1993 1 5 3 0 10 13 19.94 10.94
Эту задачу можно решить с помощью метода reset_index() библиотеки pandas. Этот метод убирает все уровни иерархического индекса и возвращает обычный DataFrame. Важно помнить, что метод reset_index() не изменяет исходный DataFrame, а возвращает новый.
flat_df = df.reset_index()
Однако, если в DataFrame есть колонки с мультииндексом, то после выполнения этой команды в названиях колонок появятся кортежи. Чтобы избежать этого, можно использовать метод map() для изменения названий колонок. Вот пример кода, который выполняет это преобразование:
df.columns = df.columns.map('_'.join)
https://sky.pro/media/kak-otklyuchit-preduprezhdeniya-v-python/ warnings , которая служит для вывода предупреждений о возможных ошибках или неправильном использовании функций.
import warnings warnings.warn("Это предупреждение!")
Выполнение данного кода приведет к выводу текста предупреждения. Иногда эти предупреждения могут быть неинформативными или неактуальными для конкретной задачи, и хотелось бы их отключить. Однако, как это сделать? Ведь в документации библиотеки warnings описан только способ отключения предупреждений для отдельных функций, что не всегда удобно. В Python нет встроенной опции, позволяющей отключить все предупреждения при запуске скрипта, вроде python -no-warning foo.py . Но не стоит отчаиваться, есть несколько других способов.
Использование функции warnings.filterwarnings
Самый простой способ отключить все предупреждения — использовать функцию filterwarnings из библиотеки warnings :
import warnings warnings.filterwarnings("ignore")
Если добавить эти две строки в начало скрипта, то все предупреждения, которые могли бы быть выведены, будут проигнорированы.
Переменная окружения PYTHONWARNINGS
Еще один способ — использование переменной окружения PYTHONWARNINGS . Если перед запуском скрипта установить значение этой переменной равным ignore , то все предупреждения также будут проигнорированы:
PYTHONWARNINGS="ignore" python script.py
https://sky.pro/media/kak-prosmotret-standartnyj-vyvod-print-vo-vremya-vypolneniya-pytest/ pytest . При написании кода иногда возникает необходимость использовать оператор print для вывода определенной информации во время выполнения тестов. Для этого в код вносятся операторы print , которые помогают отслеживать ход выполнения программы. Приведем пример. Пусть есть функция, которая выполняет какую-то важную работу, и хочется увидеть промежуточные результаты ее работы:
def important_function(): print("Important function started") # . print("Important function finished")
И есть тесты для этой функции:
def test_important_function(): important_function()
Но когда эти тесты запускаются с помощью pytest , все операторы print игнорируются, и их вывод не появляется в консоли. На такой вопрос есть простое решение. Чтобы увидеть стандартный вывод print во время выполнения тестов с использованием pytest , нужно воспользоваться опцией -s при запуске pytest . Опция -s указывает pytest отключить все подавления и перенаправления стандартного вывода и ошибок. Вот как это выглядит в командной строке:
pytest -s
https://sky.pro/media/izmenenie-razmera-figury-s-podgrafikami-v-matplotlib/ figsize() не даст ожидаемого результата.
f.figsize(15, 15)
Этот код никак не повлияет на размер фигуры. Дело в том, что figsize() не является методом объекта figure , а является параметром функции subplots() , который определяет размер фигуры при ее создании.
f, axarr = plt.subplots(2, 2, figsize=(15, 15)) axarr[0, 0].plot(x, y) axarr[0, 1].scatter(x, y) axarr[1, 0].plot(x, y ** 2) axarr[1, 1].scatter(x, y ** 2) plt.show()
https://sky.pro/media/slovarnoe-vklyuchenie-v-python/ . Также есть возможность задать одно и то же значение для всех ключей. Например, для создания словаря, где каждому числу соответствует значение True , можно использовать такой код:
numbers = [1, 2, 3, 4, 5] true_dict =
Словарное включение также поддерживает условные конструкции. Например, можно создать словарь, где каждому четному числу соответствует True , а нечетному — False :
numbers = [1, 2, 3, 4, 5] parity_dict =
https://sky.pro/media/razbor-bulevyh-znachenij-s-argparse-v-python/ parsed_args.my_bool будет равно True . Это происходит из-за того, что любая непустая строка в Python воспринимается как True . Таким образом, «False» преобразуется в True . Чтобы корректно обрабатывать булевые значения с помощью argparse, можно воспользоваться следующим решением:
import argparse def str2bool(v): if isinstance(v, bool): return v if v.lower() in ('yes', 'true', 't', 'y', '1'): return True elif v.lower() in ('no', 'false', 'f', 'n', '0'): return False else: raise argparse.ArgumentTypeError('Boolean value expected.') parser = argparse.ArgumentParser() parser.add_argument('--my_bool', type=str2bool)
https://sky.pro/media/sravnenie-neskolkih-peremennyh-s-odnim-znacheniem-v-python/ or возвращает первое истинное значение или последнее, если ни одно из значений не является истинным. Поэтому в выражении a or b or c == 0 сначала сравнивается a с None , затем b с None , и только потом происходит сравнение c с 0 . Если a и b не равны None , то выражение a or b or c == 0 будет равно c == 0 , что не является верным в нашем случае, потому что мы хотим проверить все переменные на равенство нулю.
Правильный способ сделать это
Чтобы правильно сравнить несколько переменных с одним значением, нужно сравнить каждую переменную отдельно. Вот как это можно сделать:
if a == 0 or b == 0 or c == 0: print("Одна из переменных равна нулю")
https://sky.pro/media/ustanovka-paketov-python-iz-lokalnoj-fajlovoj-sistemy-v-virtualenv-s-pomoshhyu-pip/ /home/user/mypackage/mypackage-1.0.0.tar.gz . Теперь разработчик хочет установить этот пакет в виртуальную среду. Существует способ указать pip установить пакет из локального файла, например, можно использовать команду pip install /home/user/mypackage/mypackage-1.0.0.tar.gz . Однако интересует вопрос: можно ли настроить pip так, чтобы он автоматически искал пакеты в определенном месте, если была введена команда pip install mypackage ? Чтобы понять, как это сделать, нужно разобраться, как работает pip. Pip ищет пакеты на PyPI (Python Package Index) по умолчанию. Однако pip также поддерживает использование других источников, таких как локальные каталоги, VCS, архивы и т.д., которые могут быть указаны в параметрах командной строки или в файле конфигурации. Если нужно использовать локальный каталог в качестве места поиска пакетов, можно добавить следующую строку в файл pip.conf (создать его, если он не существует):
[global] extra-index-url = file:///home/user/mypackage
https://sky.pro/media/generacziya-spiska-bukv-alfavita-v-python/ chr() . Функция chr() в Python возвращает строку, представляющую символ, который представляет собой юникод-код указанного целого числа. Например, chr(97) возвращает строку ‘a’. В ASCII (стандартной системе кодирования символов), буквы английского алфавита имеют коды от 97 до 122 для строчных букв и от 65 до 90 для прописных. Зная это, можно легко сгенерировать список букв алфавита следующим образом:
alphabet = [chr(i) for i in range(97, 123)]
https://sky.pro/media/udalenie-poslednego-simvola-iz-stroki-v-python/ Работа со строками в Python — одна из самых частых задач, которые ставятся перед программистами. Одной из особенностей работы со строками является неизменяемость строк в Python. Это означает, что нельзя просто так взять и изменить один символ в уже существующей строке. Но что если требуется удалить последний символ из строки? Допустим, есть строка «Привет, мир!». И требуется преобразовать её в «Привет, мир». Как это сделать в Python?
Способ 1: Использование срезов строк
Один из самых простых и эффективных способов — использование срезов строк. В Python можно «срезать» нужную часть строки, указав начало и конец среза. В данном случае, чтобы удалить последний символ, нужно сделать срез от начала строки до предпоследнего символа. Это делается очень просто — нужно указать в квадратных скобках индекс первого символа (в данном случае, это 0, так как индексация в Python начинается с нуля), двоеточие и индекс последнего символа.
s = "Привет, мир!" s_new = s[:-1] print(s_new) # Выведет: "Привет, мир"
Способ 2: Использование метода rstrip()
Еще один способ — использование метода rstrip(). Этот метод удаляет указанные символы справа от строки.
s = "Привет, мир!" s_new = s.rstrip("!") print(s_new) # Выведет: "Привет, мир"
df[1] = df[1].astype(float) df[2] = df[2].astype(float)
Здесь мы преобразовали второй и третий столбцы в числа с плавающей точкой.
Задание типа данных столбцов при создании DataFrame
Также типы данных столбцов можно задать сразу при создании DataFrame, используя параметр dtype в функции pd.DataFrame() .
df = pd.DataFrame(table, dtype=float)
Однако, в этом случае все столбцы будут иметь заданный тип данных, что может вызвать ошибки, если в некоторых столбцах содержатся нечисловые данные.
Динамическое изменение типа данных столбцов
Если в DataFrame много столбцов и неизвестно, какие из них нужно преобразовать, можно использовать метод apply() , который применяет указанную функцию к каждому столбцу.
df = df.apply(lambda col: col.astype(float) if col.str.replace('.','').str.isdigit().all() else col)
https://sky.pro/media/vybor-stroki-v-pandas-series-dataframe-po-czelochislennomu-indeksu/ df[2] . Однако при попытке выполнить такую операцию, часто можно столкнуться с ошибкой. При этом, другие способы доступа к строкам, такие как df.ix[2] или df[2:3] , работают без проблем. Это может вызвать некоторое недоумение и вопросы о причинах такого поведения pandas.
Почему df[2] не работает?
В pandas существуют некоторые особенности индексации, которые отличаются от стандартного поведения Python. Одна из таких особенностей — это то, что df[2] пытается выбрать данные по метке, а не по целочисленному индексу. Если в dataframe используются целочисленные метки, то df[2] будет работать, но выберет не строку с индексом 2, а строку с меткой 2. Если же метки не являются числами или нет метки 2, то будет выдана ошибка.
Как правильно выбрать строку по индексу?
Вместо использования df[2] , в pandas предлагается использовать функции .loc[] или .iloc[] для выбора данных по метке или по позиции соответственно. Функция .iloc[] работает аналогично обычной индексации в Python, то есть df.iloc[2] выберет третью строку в dataframe независимо от её метки.
df.iloc[2]
Функция .loc[] , в свою очередь, используется для доступа по метке, то есть df.loc[2] выберет строку с меткой 2.
df.loc[2]
Заключение
https://sky.pro/media/peredacha-spiska-kak-argumenta-komandnoj-stroki-s-pomoshhyu-argparse/ args.list будет списком ["1", "2", "3", "4"] , однако на самом деле возвращается список ['1 2 3 4'] . Это происходит потому, что argparse воспринимает аргумент -l как одну строку, а не список. Чтобы решить эту проблему, можно воспользоваться аргументом nargs функции add_argument . nargs='+' говорит argparse , что ожидается один или более аргументов. Вот так можно изменить код:
import argparse parser = argparse.ArgumentParser() parser.add_argument('-l', '--list', nargs='+', dest='list', help='Set flag', required=True) args = parser.parse_args() print(args.list)
Теперь, если запустить скрипт с теми же аргументами:
python test.py -l 1 2 3 4
https://sky.pro/media/obnovlenie-pip-vnutri-virtualnoj-sredy/ pip update или pip update pip , но увы, они не приводят к желаемому результату. Это объясняется тем, что в pip нет встроенной команды для самообновления. Однако, существует несколько простых способов обновления pip. Достаточно выполнить одну из следующих команд:
pip install --upgrade pip
python -m pip install --upgrade pip
Эти команды обновляют pip до последней доступной версии. Пакет --upgrade говорит pip, что нужно обновить указанный пакет (в данном случае сам pip). Если пакет уже установлен, pip установит доступное обновление. Если пакет еще не установлен, pip установит последнюю версию пакета. Если обновление не происходит, возможно, нужно проверить права доступа к файлам виртуальной среды или использовать ключ -H , который меняет домашний каталог на домашний каталог реального идентификатора пользователя.
sudo -H pip install --upgrade pip
https://sky.pro/media/kak-poluchit-kolichestvo-strok-v-dataframe-biblioteki-pandas/ shape у объекта DataFrame. Этот атрибут возвращает кортеж, содержащий количество строк и столбцов в DataFrame. Чтобы получить количество строк, нужно обратиться к первому элементу этого кортежа.
import pandas as pd # Создание DataFrame df = pd.DataFrame(< 'A': [1, 2, 3, 4, 5], 'B': ['a', 'b', 'c', 'd', 'e'] >) # Получение количества строк rows_count = df.shape[0]
Использование функции len()
Еще один способ — это использовать встроенную в Python функцию len() . Эта функция возвращает количество строк в DataFrame.
import pandas as pd # Создание DataFrame df = pd.DataFrame(< 'A': [1, 2, 3, 4, 5], 'B': ['a', 'b', 'c', 'd', 'e'] >) # Получение количества строк rows_count = len(df)
c1 c2 0 10 100 1 11 110 2 12 120
Задача состоит в том, чтобы пройти по всем строкам этого DataFrame и иметь возможность обращаться к значениям в каждой ячейке по имени столбца.
for row in df.rows: print(row['c1'], row['c2'])
Однако, в Pandas нет метода .rows , и это вызовет ошибку. Но существуют другие методы, которые позволяют решить эту задачу. Один из подходов – использовать метод .iterrows() . Этот метод возвращает итератор, который генерирует индекс каждой строки и данные этой строки в виде объекта Series.
for index, row in df.iterrows(): print(row['c1'], row['c2'])
В этом примере index будет индексом строки, а row – объектом Series, который содержит данные строки. Обращение к значениям ячеек осуществляется по имени столбца (в данном случае ‘c1’ и ‘c2’). Есть и другой подход – это использовать метод .itertuples() . Этот метод возвращает итератор, который генерирует именованный кортеж с индексом строки и данными.
for row in df.itertuples(): print(row.c1, row.c2)
Использование метода values()
Метод values() возвращает объект dict_values , который является представлением всех значений словаря. Вот как это работает:
grades = < "Alice": 85, "Bob": 90, "Charlie": 92 >print(grades.values())
В результате выполнения этого кода будет выведено:
dict_values([85, 90, 92])
Преобразование в список
Объект dict_values , возвращаемый методом values() , можно легко преобразовать в список с помощью функции list() . Вот как это можно сделать:
grades = < "Alice": 85, "Bob": 90, "Charlie": 92 >values_list = list(grades.values()) print(values_list)
Этот код выведет:
[85, 90, 92]
https://sky.pro/media/ispolzovanie-try-except-s-modulem-python-requests/ requests.ConnectionError , которое будет перехвачено в блоке except и вывод на печать.
Улучшение структуры кода
Структура кода в примере выше является корректной, но ее можно улучшить для более полного и удобного обработывания ошибок. В модуле requests, помимо requests.ConnectionError , существуют и другие виды исключений, которые могут быть полезными для отлавливания ошибок. Например, requests.Timeout для ошибок связанных с тайм-аутом и requests.RequestException для отлавливания любых ошибок, которые могут возникнуть при выполнении запроса. Пример улучшенного кода:
import requests url = "https://www.example.com" try: response = requests.get(url) except requests.ConnectionError as e: print("Ошибка подключения:", e) except requests.Timeout as e: print("Ошибка тайм-аута:", e) except requests.RequestException as e: print("Ошибка запроса:", e)
https://sky.pro/media/ustanovka-biblioteki-python-s-github-cherez-fajl-requirements-txt/ requirements.txt , чтобы другие разработчики могли легко установить все зависимости вашего проекта.
Проблема
Если вы просто добавите следующие строки в файл requirements.txt :
-f git+git://github.com/username/repository.git repository==version
и попытаетесь установить зависимости с помощью команды pip install -r requirements.txt , вы получите ошибку, которая говорит, что pip не может найти указанную версию библиотеки.
Решение
Чтобы решить эту проблему, вам нужно указать ссылку на репозиторий Github прямо в строке с именем библиотеки и ее версией. Версию можно указать после символа @ в ссылке на репозиторий. Также, вы можете указать ветку, тег или коммит после символа @. Если версия не указана, pip установит последнюю версию библиотеки. Вот как должен выглядеть requirements.txt :
git+git://github.com/username/repository.git@version#egg=repository
https://sky.pro/media/jsondecodeerror-expecting-value-line-1-column-1-char-0/ JSONDecodeError: Expecting value: line 1 column 1 (char 0) . Эта ошибка возникает, когда пытаются декодировать или загрузить JSON, но вместо ожидаемого JSON-объекта получают пустую строку или нечто другое. Сначала давайте рассмотрим, что происходит в простом примере. Предположим, есть блок кода, который пытается загрузить JSON из пустой строки:
import json try: data = json.loads('') except json.JSONDecodeError as e: print(f"Ошибка: ")
В этом случае, код выведет на экран сообщение об ошибке «Expecting value: line 1 column 1 (char 0)». Это происходит потому, что функция json.loads() ожидает на вход JSON объект, но получает пустую строку. Теперь, как исправить эту ошибку? Прежде всего, нужно убедиться, что данные, которые передаются в функцию json.loads() , действительно являются JSON. Если данные получены из внешнего источника (например, в результате API запроса), то возможно, что они пришли в неправильном формате или вообще не пришли. В таком случае, стоит проверить корректность работы API. Также можно добавить проверку на пустую строку перед тем, как пытаться загрузить JSON:
import json json_str = '' # предположим, эта строка пришла из API запроса if json_str: try: data = json.loads(json_str) except json.JSONDecodeError as e: print(f"Ошибка: ") else: print("JSON строка пуста")
https://sky.pro/media/zagruzka-bolshih-fajlov-v-python-s-pomoshhyu-biblioteki-requests/ iter_content() , который позволяет читать файл по частям (или «по кускам»). Размер каждого «куска» определяется параметром chunk_size , заданным в байтах. Однако, несмотря на это, весь файл все равно загружается в память перед записью на диск. Чтобы решить эту проблему, необходимо использовать параметр stream в функции requests.get() . Параметр stream позволяет загружать файл по частям, не загружая его целиком в память. Вот как это можно сделать:
import requests def download_file(url): local_filename = url.split('/')[-1] with requests.get(url, stream=True) as r: r.raise_for_status() with open(local_filename, 'wb') as f: for chunk in r.iter_content(chunk_size=8192): if chunk: f.write(chunk)
https://sky.pro/media/izvlechenie-znacheniya-iz-yachejki-dataframe-v-pandas/ value будет храниться DataFrame, который содержит одну строку и один столбец, то есть одну ячейку. Однако, если требуется получить непосредственно значение этой ячейки, а не DataFrame, можно воспользоваться одним из следующих способов.
Использование метода iloc
Можно использовать метод iloc , который позволяет получить доступ к элементу DataFrame по его числовому индексу:
value = df_filtered['target_column'].iloc[0]
В этом случае в переменной value будет храниться значение первой (и единственной) строки извлеченного DataFrame.
Использование метода values
Еще один способ — использовать метод values , который возвращает массив numpy со значениями DataFrame:
value = df_filtered['target_column'].values[0]
В этом случае в переменной value также будет храниться значение первой строки извлеченного DataFrame, но в виде массива numpy.
Использование функции squeeze
Функция squeeze удаляет оси DataFrame с одним элементом:
value = df_filtered['target_column'].squeeze()
https://sky.pro/media/chto-takoe-__pycache__-v-python/ __pycache__ . Это может вызвать вопросы, особенно у тех, кто только начинает изучать язык.
Что такое __pycache__ ?
Папка __pycache__ — это каталог, который создается Python’ом, когда программа запускается. В этой папке хранятся скомпилированные байт-коды файлов.
Зачем нужен __pycache__ ?
Компиляция исходного кода в байт-код позволяет ускорить загрузку модулей при следующем запуске программы. То есть, Python использует __pycache__ для оптимизации работы с кодом.
Можем ли мы удалять __pycache__ ?
Да, можно безопасно удалять содержимое папки __pycache__ или даже саму папку. Python снова создаст все необходимые файлы при следующем запуске программы. Однако, удалять эту папку обычно нет необходимости, так как она не влияет на работу исходного кода.
Должны ли мы распространять __pycache__ ?
https://sky.pro/media/rabota-s-regulyarnymi-vyrazheniyami-v-python-pri-zamene-stroki/ string.replace() , однако он не поддерживает регулярные выражения.
Решение с помощью модуля re
В Python для работы с регулярными выражениями используется модуль re . Для замены текста с использованием регулярных выражений в re есть метод re.sub() .
import re # текст для замены text = 'parameter-name parameter-value' # регулярное выражение для поиска значения параметра pattern = r'parameter-name (\w+)' # новое значение параметра new_value = 'new-value' # замена значения параметра new_text = re.sub(pattern, 'parameter-name ' + new_value, text)
https://sky.pro/media/poluchenie-spiska-klyuchej-iz-slovarya-v-python/ keys() . Результатом его выполнения был список всех ключей:
print(grades.keys()) # Вывод: ['Иван', 'Мария', 'Алексей']
Однако, начиная с версии Python 3.3, поведение этого метода изменилось. Теперь он возвращает не список, а специальный объект dict_keys :
print(grades.keys()) # Вывод: dict_keys(['Иван', 'Мария', 'Алексей'])
Такой объект ведет себя похоже на список: его можно перебирать в цикле, проверять на вхождение элементов и так далее. Однако он не поддерживает некоторые операции, доступные для списков, например, индексацию.
Как получить список ключей словаря в Python 3?
Для получения списка ключей словаря в Python 3 нужно явно преобразовать объект dict_keys в список. Для этого можно использовать встроенную функцию list() . Пример:
grades = keys_list = list(grades.keys()) print(keys_list) # Вывод: ['Иван', 'Мария', 'Алексей']
https://sky.pro/media/pochemu-v-python-net-tuple-comprehension/ (1, 2, 3, 4, 5) получается объект генератора. Почему же так происходит? Причина кроется в синтаксисе Python. Круглые скобки используются для создания генераторов, и это не имеет ничего общего с кортежами. Генераторы — это специальный тип итератора, который генерирует значения «на лету», сохраняя при этом минимальное потребление памяти. Чтобы создать кортеж, используя подход, аналогичный comprehension, нужно просто преобразовать генератор в кортеж, используя функцию tuple() :
numbers_tuple = tuple(i for i in range(1, 6))
https://sky.pro/media/otnositelnye-importy-v-python-3/ my_module.py , в котором определена функция my_function , и main.py , где эта функция необходима. Программист пытается импортировать функцию в main.py одним из следующих способов:
from .my_module import my_function
from my_module import my_function
Однако один из этих способов не работает и вызывает одну из следующих ошибок:
ImportError: attempted relative import with no known parent package
ModuleNotFoundError: No module named 'my_module'
SystemError: Parent module '' not loaded, cannot perform relative import
https://sky.pro/media/zapis-pandas-dataframe-v-csv-fajl/ .to_csv() . Однако, при работе с данными, которые содержат символы юникода, при попытке сохранения может возникнуть ошибка UnicodeEncodeError . Эта ошибка возникает из-за того, что кодировка по умолчанию, используемая для записи в файл, не поддерживает символы юникода.
import pandas as pd data = df = pd.DataFrame(data) df.to_csv('output.csv')
В данном случае, запись в файл вызовет ошибку UnicodeEncodeError , поскольку имена содержат кириллические символы. Для решения этой проблемы можно указать кодировку файла при записи. Для поддержки большинства символов подойдет кодировка ‘utf-8’.
df.to_csv('output.csv', encoding='utf-8')
Также в pandas есть возможность записи данных не только в CSV, но и в другие форматы. Часто возникает вопрос о записи данных в табулированный файл. Для этого можно использовать тот же метод .to_csv() , но указать другой разделитель с помощью параметра sep .
df.to_csv('output.tsv', sep='\t', encoding='utf-8')
https://sky.pro/media/rabota-dekoratora-property-v-python/ property , которую можно использовать как встроенную функцию, так и в качестве декоратора. Для начала рассмотрим простой пример использования property как встроенной функции:
class MyClass: def __init__(self): self._value = None def get_value(self): return self._value def set_value(self, value): self._value = value def del_value(self): del self._value value = property(get_value, set_value, del_value, "This is 'value' property.")
Здесь property принимает четыре аргумента: метод для получения значения ( get_value ), метод для установки значения ( set_value ), метод для удаления значения ( del_value ) и строку документации. Теперь рассмотрим пример использования property как декоратора:
class MyClass: def __init__(self): self._value = None @property def value(self): """This is 'value' property.""" return self._value @value.setter def value(self, value): self._value = value @value.deleter def value(self): del self._value
https://sky.pro/media/otbor-strok-iz-dataframe-po-znacheniyam-v-stolbcze/ .loc[] , который предназначен для доступа к группе строк и столбцов по метке или массиву булевых значений. Для отбора строк, в которых значение в столбце ‘math’ равно 5, можно использовать следующий код:
selected_students = df.loc[df['math'] == 5]
В данном случае df['math'] == 5 создает массив булевых значений (True или False) для каждой строки DataFrame. Если значение в столбце ‘math’ равно 5, то соответствующее значение в массиве будет True, в противном случае — False. Затем метод .loc[] выбирает только те строки, для которых соответствующее значение в массиве равно True. Выбранные строки можно вывести на экран с помощью команды print(selected_students) .
Заключение
https://sky.pro/media/ustanovka-pip-na-macos-ili-os-x/ sudo позволяет выполнить команду от имени администратора (т.е. с правами администратора), easy_install — это инструмент установки пакетов Python, а pip — это название пакета, который мы хотим установить. После ввода этой команды, система запросит пароль администратора, после ввода которого начнется процесс установки pip.
Проверка установки
После установки pip можно проверить его работоспособность, используя следующую команду:
pip --version
https://sky.pro/media/dobavlenie-elementa-v-nachalo-spiska-v-python/ insert() соответствующего класса list . Этот метод позволяет вставить элемент на определенную позицию в списке. Для добавления элемента в начало списка, нужно указать позицию 0 :
numbers.insert(0, 42)
После выполнения этой строки кода, число 42 будет добавлено в начало списка numbers . Также возможно использовать оператор + для конкатенации списков. Это может быть полезно, если требуется добавить несколько элементов в начало списка:
numbers = [42] + numbers
Этот код создаст новый список, который начинается с числа 42, а затем идут элементы исходного списка numbers . Наконец, можно использовать метод extend() для добавления нескольких элементов в начало списка. Однако, в этом случае требуется предварительно преобразовать добавляемый элемент в список:
numbers = [42] numbers.extend([1, 2, 3])
https://sky.pro/media/oshibka-tokenizaczii-dannyh-v-pandas/ pd.read_csv() . Ошибка может выглядеть примерно так:
pandas.parser.CParserError: Error tokenizing data. C error: Expected 2 fields in line 3, saw 12
Эта ошибка указывает на то, что в процессе чтения файла возникли проблемы с токенизацией данных. Токенизация — это процесс разделения входных данных (в данном случае, строк CSV-файла) на отдельные «кусочки» — токены. В случае CSV-файлов токенами обычно являются отдельные значения, разделенные запятыми. Ошибка возникает, когда количество токенов в строке не соответствует ожидаемому. В данном случае, ожидалось 2 поля (токена), но в третьей строке было обнаружено 12 полей. Эта проблема может возникнуть, если в CSV-файле есть строки с большим или меньшим количеством полей, чем в остальных строках. Это может быть вызвано множеством причин, включая некорректное форматирование данных или ошибки при экспорте данных в CSV. Вот пример кода, который может вызвать такую ошибку:
import pandas as pd path = 'my_data.csv' data = pd.read_csv(path)
Чтобы исправить эту проблему, можно воспользоваться параметром error_bad_lines функции pd.read_csv() . Если установить его значение в False , то строки с некорректным количеством полей будут пропущены, и они не окажутся в итоговом DataFrame. Вот как это можно сделать:
import pandas as pd path = 'my_data.csv' data = pd.read_csv(path, error_bad_lines=False)
https://sky.pro/media/reshenie-oshibki-unicodedecodeerror-pri-chtenii-csv-fajla-v-pandas/ encoding функции read_csv :
data = pd.read_csv(filepath, names=fields, encoding='latin1')
Если неизвестно, какая кодировка используется в файле, можно воспользоваться библиотекой chardet , которая позволяет автоматически определить кодировку:
import chardet with open(filepath, 'rb') as f: result = chardet.detect(f.read()) data = pd.read_csv(filepath, names=fields, encoding=result['encoding'])
https://sky.pro/media/preobrazovanie-slovarya-python-v-dataframe/ pandas.DataFrame.from_dict() . В качестве аргументов эта функция принимает исходный словарь и указание на то, что словарь следует интерпретировать как словарь списков, где списки представляют собой строки DataFrame. Пример преобразования словаря в DataFrame:
import pandas as pd # исходный словарь dict_data = < '2022-01-01': 100, '2022-01-02': 200, '2022-01-03': 300, '2022-01-04': 400, '2022-01-05': 500 ># преобразование словаря в DataFrame df = pd.DataFrame(list(dict_data.items()), columns=['Date', 'Value']) # вывод DataFrame print(df)
Результат выполнения данного кода будет выглядеть следующим образом:
Date Value 0 2022-01-01 100 1 2022-01-02 200 2 2022-01-03 300 3 2022-01-04 400 4 2022-01-05 500
https://sky.pro/media/udalenie-strok-v-pandas-dataframe-na-osnove-znacheniya-stolbcza/ DataFrame.loc[] , который позволяет нам получить доступ к группе строк и столбцов по метке(ам). В нашем случае, мы хотим получить все строки, где значение в столбце ‘B’ не равно 0. Это можно сделать следующим образом:
df = df.loc[df['B'] != 0] print(df)
A B 0 foo 1 2 baz 2 4 quux 3
https://sky.pro/media/sohranenie-tekstov-v-formate-utf-8-s-ispolzovaniem-json-dumps/ json.dumps() в Python. Эта функция преобразует объект Python в строку JSON. Однако, при попытке сериализации строк, содержащих не-ASCII символы, функция json.dumps() по умолчанию преобразует их в escape-последовательности вида \uXXXX . Это могут быть, например, символы кириллицы или иероглифы. Представим, что у нас есть следующий код:
import json json_string = json.dumps("Привет, мир!") print(json_string)
Вывод будет следующим:
"\u041f\u0440\u0438\u0432\u0435\u0442, \u043c\u0438\u0440!"
Это некрасиво и неудобно для чтения человеком. Вместо ожидаемого «Привет, мир!» мы получаем набор escape-последовательностей.
Решение проблемы
Существует простое решение этой проблемы. Функция json.dumps() имеет параметр ensure_ascii , который по умолчанию установлен в True . Именно это приводит к преобразованию не-ASCII символов в escape-последовательности. Если установить этот параметр в False , то функция json.dumps() будет сохранять все символы в исходном виде. Вот как выглядит код с установкой ensure_ascii=False :
import json json_string = json.dumps("Привет, мир!", ensure_ascii=False) print(json_string)
Теперь вывод будет выглядеть так, как и ожидалось:
"Привет, мир!"
https://sky.pro/media/sozdanie-spiska-chisel-v-opredelennom-diapazone-v-python/ range() . Эта функция генерирует последовательность чисел в заданном диапазоне.
list(range(5, 11))
В данном случае, функция range() генерирует числа от 5 до 10 включительно. Функция list() используется для преобразования этих чисел в список.
Использование генератора списка
Второй способ — использование генератора списка. Генераторы списков — это удобный инструмент для создания списков в Python. Ниже приведен пример создания списка чисел от 5 до 10 с использованием генератора списка:
[i for i in range(5, 11)]
https://sky.pro/media/znachenie-1-v-metode-reshape-v-numpy/ reshape(-1) . Давайте разберемся, что означает -1 в этом случае. Рассмотрим типичный пример. Есть двумерный массив, который нужно преобразовать в одномерный массив.
import numpy as np a = np.array([[1, 2, 3], [4, 5, 6]]) print(a)
Вывод будет такой:
[[1 2 3] [4 5 6]]
Теперь применим метод reshape(-1) :
b = a.reshape(-1) print(b)
И получим следующий вывод:
[1 2 3 4 5 6]
https://sky.pro/media/vyvod-grafikov-matplotlib-v-ipython-notebook/ . Для решения этой проблемы необходимо использовать магическую команду %matplotlib inline . Эта команда позволяет выводить графики matplotlib прямо в блокноте, а не в отдельном окне. Таким образом, код будет выглядеть следующим образом:
import matplotlib import numpy as np import matplotlib.pyplot as plt %matplotlib inline x = np.linspace(0, 3*np.pi, 500) plt.plot(x, np.sin(x**2)) plt.title('A simple chirp') plt.show()
https://sky.pro/media/rabota-s-vyvodom-series-i-dataframe-v-pandas/ __repr__ для Series возвращает урезанный образец, содержащий только начало и конец данных, а остальные данные пропускаются.
Пример проблемы
Например, есть DataFrame, который содержит данные о студентах и их оценках:
import pandas as pd data = < 'Name': ['Tom', 'Nick', 'John', 'Tom', 'John'], 'Score': [90, 85, 88, 92, 89] >df = pd.DataFrame(data) print(df)
Стандартный вывод представит только начало и конец данных, а не всю серию или dataframe.
Решение
Для получения полного представления о данных, можно использовать функцию pd.set_option .
pd.set_option('display.max_rows', None)
Такой код позволит выводить все строки в DataFrame или Series. Кроме того, с помощью pd.set_option можно настроить множество других параметров вывода, таких как максимальное количество столбцов, ширина столбца и т.д.
pd.set_option('display.max_columns', None) pd.set_option('display.width', None)
Это позволит получить более аккуратный и удобочитаемый вывод данных.
Дополнительные возможности
Pandas также предлагает возможность выделения определенных ячеек или столбцов цветом для лучшего восприятия информации. Для этого используется метод style DataFrame.
df.style.set_properties(**)
https://sky.pro/media/rabota-s-gruppovymi-statistikami-v-pandas-groupby/ groupby() . Он позволяет группировать данные по одному или нескольким столбцам и вычислять различные статистики для каждой группы. Рассмотрим простой пример. Пусть имеется следующий датафрейм:
import pandas as pd data = df = pd.DataFrame(data)
Допустим, требуется найти среднее значение столбцов ‘D’ и ‘E’ для каждой комбинации значений в столбцах ‘A’ и ‘B’. Для этого можно использовать метод groupby() :
df.groupby(['A', 'B']).mean()
В результате получается датафрейм, в котором индексами являются уникальные комбинации значений столбцов ‘A’ и ‘B’, а значениями — средние значения столбцов ‘D’ и ‘E’ для каждой группы. Однако, помимо среднего значения, часто требуется знать количество элементов в каждой группе. В pandas это можно сделать с помощью метода size() :
df.groupby(['A', 'B']).size()
Этот код вернет серию, где индексами будут уникальные комбинации значений столбцов ‘A’ и ‘B’, а значениями — количество строк в каждой группе. Если требуется добавить эту информацию в исходный датафрейм, можно использовать метод merge() :
grouped = df.groupby(['A', 'B']).size().reset_index(name='counts') df = pd.merge(df, grouped, on=['A', 'B'], how='left')
https://sky.pro/media/kak-obedinit-dve-kolonki-teksta-v-dataframe-pandas/ astype(str) для преобразования численных значений в строковые, а затем применить оператор + для объединения двух колонок:
df['Период'] = df['Год'] + df['Квартал']
Теперь, если вы выведете содержимое вашего DataFrame, вы увидите, что появилась новая колонка «Период», которая объединяет «Год» и «Квартал»:
print(df)
Результат будет следующим:
Год Квартал Период 0 2000 q1 2000q1 1 2001 q2 2001q2 2 2002 q3 2002q3
https://sky.pro/media/komandy-vyhoda-v-python-razlichiya-i-sfery-primeneniya/ quit() , exit() , sys.exit() и os._exit() . Все они приводят к завершению процесса, но различаются по нюансам использования и поведения.
Команда quit()
Команда quit() предназначена преимущественно для интерактивного режима и не рекомендуется для использования в реальных скриптах. Она генерирует исключение SystemExit , что приводит к завершению работы интерпретатора Python.
print("Hello World!") quit() print("This line won't be printed")
В этом примере вторая строка с print не будет выполнена, так как перед ней вызвана команда quit() .
Команда exit()
Команда exit() аналогична quit() , и также предназначена в основном для интерактивного режима.
Команда sys.exit()
Команда sys.exit() является наиболее универсальной и предназначена для использования в реальных скриптах. Она также генерирует исключение SystemExit , но в отличие от quit() и exit() , она является частью модуля sys , который предназначен для взаимодействия с интерпретатором Python.
import sys print("Hello World!") sys.exit() print("This line won't be printed")
В этом примере, аналогично предыдущему, вторая строка с print не будет выполнена, так как перед ней вызвана команда sys.exit() .
Команда os._exit()
Команда os._exit() является частью модуля os и используется для немедленного завершения работы процесса без вызова обработчиков очистки. Это довольно «жесткий» способ завершения работы, который следует использовать только в крайних случаях.
import os print("Hello World!") os._exit(0) print("This line won't be printed")
https://sky.pro/media/izvlechenie-spiska-zagolovkov-stolbczov-iz-dataframe-v-pandas/ columns DataFrame. Этот атрибут возвращает индекс с названиями столбцов. При необходимости можно преобразовать этот индекс в список, используя метод tolist() .
column_names = df.columns.tolist()
В результате получается список ['year', 'population', 'GDP'] .
Использование метода keys
Альтернативным способом является использование метода keys() , который также возвращает индекс с названиями столбцов.
column_names = df.keys().tolist()
https://sky.pro/media/raznicza-mezhdu-metodami-map-applymap-i-apply-v-pandas/ map() , applymap() и apply() . Они позволяют выполнять такие операции быстро и эффективно.
Метод map()
Метод map() применяется к объекту типа Series (одномерный массив данных). Это значит, что функция в map() будет применена к каждому элементу в серии. Вот простой пример использования map() . Представим, что есть серия данных, представляющая температуру в градусах Фаренгейта, и нужно перевести ее в градусы Цельсия.
import pandas as pd s = pd.Series([32, 212, 0]) s.map(lambda x: (x-32)*5/9)
Метод applymap()
Метод applymap() применяется к объекту типа DataFrame (двухмерный массив данных) и выполняет функцию для каждого элемента во фрейме данных. Например, есть набор данных о погоде, и нужно округлить все значения до двух знаков после запятой.
import pandas as pd df = pd.DataFrame(< 'temp_c': [0, 100, -40], 'humidity': [0.47, 0.61, 0.56] >) df.applymap(lambda x: round(x, 2))
Метод apply()
Метод apply() может быть использован как для объектов типа Series , так и для DataFrame . Однако, в отличие от applymap() , функция будет применяться к каждой серии внутри фрейма данных, а не к каждому элементу. Предположим, нужно найти максимальное значение в каждом столбце данных.
import pandas as pd df = pd.DataFrame(< 'temp_c': [0, 100, -40], 'humidity': [0.47, 0.61, 0.56] >) df.apply(max)
https://sky.pro/media/zamena-znachenij-v-pandas-dataframe-s-ispolzovaniem-slovarya/ replace() , который предоставляет pandas. Вот как это можно сделать:
df['col1'] = df['col1'].replace(di)
После выполнения кода DataFrame будет выглядеть так: | | col1 | col2 |
|——|——|——|
| 0 | w | a |
| 1 | A | 2 |
| 2 | B | NaN | Метод replace() заменил значения в столбце ‘col1’, которые были ключами в словаре ‘di’, на соответствующие значения из этого словаря. Значения, которые не были в словаре (в данном случае ‘w’), остались без изменений. Важно отметить, что метод replace() не изменяет исходный DataFrame, если не производить присваивание результата обратно в ‘col1’. Если нужно сохранить исходный DataFrame без изменений, можно создать новый столбец для сохранения результата:
df['new_col'] = df['col1'].replace(di)
https://sky.pro/media/filtracziya-dataframe-v-pandas-s-ispolzovaniem-metodov-in-i-not-in-analogichno-sql/ IN и NOT IN . А как сделать то же самое в Pandas? Допустим, у нас есть таблица (DataFrame) с именем df , в которой есть колонка ‘country’, и мы хотим оставить в таблице только те строки, где в колонке ‘country’ стоят значения ‘UK’ или ‘China’. В SQL мы бы написали что-то вроде SELECT * FROM df WHERE country IN ('UK', 'China') . Как сделать то же самое в Pandas? В Pandas для этого есть метод isin() . Вот как можно использовать его на нашем примере:
df = pd.DataFrame() countries_to_keep = ['UK', 'China'] df_filtered = df[df['country'].isin(countries_to_keep)]
Теперь df_filtered — это таблица, в которой остались только те строки из df , где в колонке ‘country’ стоит либо ‘UK’, либо ‘China’. А если мы хотим сделать обратное, то есть оставить только те строки, где в колонке ‘country’ стоят значения, отличные от ‘UK’ и ‘China’? В SQL мы бы написали SELECT * FROM df WHERE country NOT IN ('UK', 'China') . В Pandas для этого есть метод ~isin() . Вот как можно использовать его на нашем примере:
df = pd.DataFrame() countries_to_exclude = ['UK', 'China'] df_filtered = df[~df['country'].isin(countries_to_exclude)]
https://sky.pro/media/ustanovka-vetki-iz-repozitoriya-s-pomoshhyu-pip/ dev репозитория myproject на GitHub содержатся последние изменения, которые еще не вошли в основную ветку (master). Чтобы установить эту ветку, нужно использовать команду pip install , указав ссылку на репозиторий и имя ветки.
pip install git+https://github.com/username/myproject.git@dev
Если в имени ветки присутствуют специальные символы (например, / ), они должны быть закодированы. Для этого можно использовать функцию quote из модуля urllib.parse в Python 3. Например, ветка issue/123 должна быть закодирована как issue%2F123 .
from urllib.parse import quote branch_name = quote('issue/123', safe='') print(branch_name) # Вывод: issue%2F123
Таким образом, чтобы установить ветку issue/123 , нужно выполнить следующую команду:
pip install git+https://github.com/username/myproject.git@issue%2F123
https://sky.pro/media/kak-proverit-versii-modulej-python/ numpy был установлен в проект с помощью инструмента pip :
pip install numpy
И теперь необходимо узнать его версию. Эта задача может быть решена несколькими способами, в зависимости от потребностей и предпочтений.
Использование командной строки
Самый простой способ проверить версию модуля Python — использовать командную строку. Следующая команда покажет версию установленного модуля:
pip show numpy
В результате будет выведена информация о модуле, включая его версию.
Использование Python shell
Если требуется проверить версию модуля прямо во время выполнения программы, можно использовать Python shell. Для этого нужно импортировать модуль и вывести его атрибут __version__ . Вот пример для модуля numpy :
import numpy print(numpy.__version__)
https://sky.pro/media/okruglenie-do-dvuh-desyatichnyh-znakov-v-python/ В работе с числами с плавающей точкой часто возникает ситуация, когда требуется уменьшить количество десятичных знаков после запятой. Например, при расчетах, связанных с финансами, обычно используется два знака после запятой, что соответствует копейкам в рублях или центам в долларах. Представьте ситуацию: при вычислении процентов от числа получается результат, содержащий множество десятичных знаков после запятой. Например, вычисление 7% от 25 дает 1.75, но если к этому числу прибавить 10%, то результат будет 1.925. Если такое число использовать в финансовых расчетах, то необходимо его округлить до двух знаков после запятой. В Python это можно сделать несколькими способами.
Использование функции round()
Самый простой способ — использовать встроенную функцию round() . Вторым аргументом в эту функцию передается количество знаков после запятой, до которого следует округлить число.
num = 1.925 rounded_num = round(num, 2) print(rounded_num) # Output: 1.93
Использование форматирования строк
Еще один способ — использовать форматирование строк с помощью оператора % или метода format() . В обоих случаях в шаблон строки вставляется спецификатор формата, определяющий количество знаков после запятой.
num = 1.925 # Использование оператора % print("%.2f" % num) # Output: 1.93 # Использование метода format() print("".format(num)) # Output: 1.93
Вывод
https://sky.pro/media/proverka-na-pustotu-dataframe-v-pandas/ empty . Этот метод возвращает True , если DataFrame пуст, и False в противном случае.
def check_empty(df): return df.empty print(check_empty(df)) # Выведет: True
https://sky.pro/media/kak-vyzvat-funkcziyu-iz-drugogo-fajla-py/ functions.py , в котором определена функция:
# functions.py def greet(name): print(f"Hello, !")
И мы хотим вызвать эту функцию в другом файле, например, main.py .
Ошибка при импорте
Наиболее распространенной ошибкой при попытке вызвать функцию из другого файла является ошибка ImportError: No module named 'file.py'; file is not a package . Она возникает, когда мы пытаемся импортировать функцию следующим образом:
from functions.py import greet
Правильный способ импорта
Ошибка возникает из-за некорректного использования синтаксиса импорта. В Python при импорте модулей расширение .py не указывается. Правильный способ импортировать функцию из другого файла выглядит следующим образом:
from functions import greet
Теперь функцию greet можно вызвать в файле main.py так:
greet("World")
При выполнении этого кода в консоли будет выведено сообщение «Hello, World!».
Вывод
https://sky.pro/media/kak-preobrazovat-indeks-dataframe-v-stolbecz-v-pandas/ reset_index библиотеки Pandas. Данный метод сбрасывает индекс DataFrame и заменяет его на стандартный индекс из целых чисел. При этом, старый индекс добавляется в DataFrame как новый столбец. Пример кода:
df = df.reset_index()
Если необходимо назвать новый столбец иначе, а не «index», можно использовать параметр name :
df = df.reset_index(name='new_column_name')
Также, если необходимо удалить старый индекс и не добавлять его в DataFrame как новый столбец, можно использовать параметр drop=True :
df = df.reset_index(drop=True)
Заключение
https://sky.pro/media/sbros-indeksa-v-pandas-dataframe/ [1, 5, 6, 10, 11] . Цель — сбросить индекс таким образом, чтобы он выглядел следующим образом: [0, 1, 2, 3, 4] .
Как сбросить индекс
Для переиндексации DataFrame в pandas используется метод reset_index() . Этот метод сбрасывает индекс DataFrame и заменяет его на обычный индекс, состоящий из целых чисел. Пример:
df = df.reset_index()
После выполнения этого кода, в DataFrame появится новый столбец index , содержащий предыдущие индексы. Если этот столбец не нужен, его можно удалить:
del df['index']
Таким образом, индекс будет успешно сброшен.
Неправильный способ сброса индекса
Существует другой метод, reindex() , который может показаться подходящим для сброса индекса, но это не так. Метод reindex() используется для изменения индекса и/или столбцов DataFrame. Пример неправильного использования:
df = df.reindex()
Этот код не приведет к сбросу индекса, так как метод reindex() предназначен для других целей.
Вывод
https://sky.pro/media/konvertacziya-spiska-slovarej-v-pandas-dataframe/ pandas.DataFrame() . В качестве аргумента функция принимает список словарей:
import pandas as pd df = pd.DataFrame(data)
Результат выполнения этого кода будет следующим:
name age city 0 John 30 New York 1 Anna 20 Los Angeles 2 Peter 40 Chicago 3 Linda 50 San Francisco
Как видно из примера, каждый словарь списка преобразовался в строку DataFrame, при этом ключи словаря стали именами столбцов, а их значения — значениями в соответствующих ячейках. Если в некоторых словарях отсутствуют некоторые ключи, то в соответствующих ячейках DataFrame будет значение NaN:
data = [ , , , ] df = pd.DataFrame(data)
В результате получим:
name age city 0 John 30.0 New York 1 Anna NaN Los Angeles 2 NaN 40.0 Chicago 3 Linda 50.0 NaN
https://sky.pro/media/rabota-so-staticheskimi-fajlami-vo-flask/ static , которая автоматически создается при создании нового Flask-проекта. Flask автоматически добавляет эту папку в список мест, где он будет искать статические файлы.
from flask import Flask, send_from_directory app = Flask(__name__) @app.route('/static/<path:path>') def send_static(path): return send_from_directory('static', path) if __name__ == '__main__': app.run(port=80)
https://sky.pro/media/obrabotka-settingwithcopywarning-v-pandas/ SettingWithCopyWarning , так как изменяется копия DataFrame, а не сам исходный DataFrame. Чтобы избежать такого предупреждения, рекомендуется использовать метод .loc для одновременного выбора строки и изменения значения:
df.loc[df['A'] > 1, 'B'] = 0
В этом случае изменяется исходный DataFrame, и предупреждение SettingWithCopyWarning не будет выдано. Если же предупреждение всё равно выдаётся, и есть уверенность в том, что всё сделано правильно, его можно подавить следующим образом:
pd.options.mode.chained_assignment = None
https://sky.pro/media/reshenie-problemy-unresolved-reference-v-pycharm/ main.py мы пытаемся импортировать функции из helper.py следующим образом:
from helpers.helper import some_function
Но PyCharm выдает ошибку «Unresolved reference» и не может найти модуль helpers.helper .
Причина проблемы
В большинстве случаев причина ошибки «Unresolved reference» заключается в том, что PyCharm не может определить путь к модулю, который вы пытаетесь импортировать. Это может произойти, если модуль находится в другом каталоге, и PyCharm не знает, где искать этот каталог.
Решение проблемы
Чтобы решить эту проблему, необходимо сделать так, чтобы PyCharm знал, где искать модули для импорта. Для этого нужно добавить нужный каталог в системный путь Python. В Python пути к модулям хранятся в переменной sys.path . Это список строк, каждая из которых представляет собой путь к каталогу. Когда Python пытается импортировать модуль, он ищет его в этих каталогах. Чтобы добавить каталог в sys.path , можно использовать функцию sys.path.append() . Например, если мы хотим добавить каталог helpers в sys.path , мы можем сделать это следующим образом:
import sys sys.path.append("/path/to/helpers") from helpers.helper import some_function
Заключение
https://sky.pro/media/ispravlenie-oshibki-unicodedecodeerror-ascii-codec-cant-decode-byte/ UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128) . Эта ошибка возникает из-за того, что стандартная кодировка в Python — ASCII, которая поддерживает только символы латиницы. Ошибку можно исправить, указав правильную кодировку при преобразовании строки. В случае с кириллицей или китайскими иероглифами это обычно ‘utf-8’. Используйте метод .encode() или .decode() со строкой ‘utf-8’ в качестве аргумента:
text = "Привет, мир" text.encode('utf-8')
https://sky.pro/media/uvelichenie-shiriny-yachejki-v-jupyter-notebook/ jupyterthemes . Его можно установить через pip:
pip install jupyterthemes
После установки доступно несколько тем оформления, а также возможность настроить ширину ячейки. Для этого используется следующая команда:
jt -t название_темы -f шрифт -fs размер_шрифта -cellw ширина_ячейки
Например, чтобы установить тему oceans16 , шрифт roboto , размер шрифта 12 и ширину ячейки 100% , следует ввести команду:
jt -t oceans16 -f roboto -fs 12 -cellw 100%
https://sky.pro/media/gruppirovka-strok-v-dataframe-i-preobrazovanie-v-spisok-v-pandas/ groupby() библиотеки pandas. Этот метод позволяет группировать данные по одному или нескольким столбцам. Решение задачи с использованием groupby() выглядит следующим образом:
result = df.groupby('x')['y'].apply(list)
- df.groupby('x') группирует данные по столбцу ‘x’;
- ['y'].apply(list) берет значения столбца ‘y’ для каждой группы и преобразует их в список.
Таким образом, с помощью одной строки кода можно достичь желаемого результата.
Однако стоит отметить, что результат выполнения этой операции — это объект Series, а не DataFrame. Если нужно получить результат в виде DataFrame, то можно использовать метод reset_index() , который преобразует индексы в столбец DataFrame:
result = df.groupby('x')['y'].apply(list).reset_index()
Теперь результат выполнения этой операции — это DataFrame с двумя столбцами: ‘x’ и ‘y’, где ‘y’ содержит списки значений.
Таким образом, с помощью метода groupby() и функции apply() можно легко группировать данные в DataFrame по определенному признаку и получать результаты группировки в виде списков.
https://sky.pro/media/poluchenie-spiska-iz-stolbcza-ili-stroki-dataframe-v-pandas/ tolist() преобразует столбец DataFrame в список. Теперь переменная list_of_goods содержит список товаров. Для получения содержимого строки как списка, можно сначала транспонировать DataFrame (поменять местами строки и столбцы), а затем применить тот же метод tolist() . Например, чтобы получить первую строку DataFrame в виде списка, можно сделать так:
list_of_first_row = df.T[0].tolist()
https://sky.pro/media/kak-proverit-yavlyaetsya-li-slovar-pustym-v-python/ users изначально пуст. Затем идет проверка: если словарь users пуст, то выводится сообщение «Nobody is online».
Почему это работает?
https://sky.pro/media/rabota-s-nonetype-v-python/ get_value возвращает значение None, так как в словаре dict нет ключа ‘key3’. Часто возникает необходимость проверить, является ли значение переменной None. Это можно сделать с помощью оператора is .
if value is None: value = '#' print(value)
В этом примере, если значение переменной value равно None, то оно заменяется на ‘#’. Также можно использовать сокращенный вариант этой проверки с использованием оператора or :
value = get_value(dict, 'key3') or '#' print(value)
https://sky.pro/media/poluchenie-korrektnogo-vvoda-ot-polzovatelya-v-python/ int() пытается преобразовать введенные данные в число, но не может сделать это, если данные не представляют собой число. Чтобы избежать такой ситуации, можно использовать цикл while и обработку исключений. Вот как это можно сделать:
while True: try: age = int(input("Введите ваш возраст: ")) if age >= 18: print("Вы можете голосовать на выборах!") else: print("Вы не можете голосовать на выборах.") break except ValueError: print("Извините, я не понял вас. Попробуйте снова.")
https://sky.pro/media/pravilnoe-ispolzovanie-assert-dlya-proverki-vozniknoveniya-isklyuchenij-v-pytest/ pytest.raises . Это специальный блок кода, который проверяет, что внутри него возникает определенное исключение. Используя pytest.raises , тест для функции divide_by_zero будет выглядеть так:
def test_divide_by_zero(): with pytest.raises(ZeroDivisionError): divide_by_zero()
https://sky.pro/media/sozdanie-obuchayushhih-i-testovyh-vyborok-iz-odnogo-dataframe-s-pomoshhyu-pandas/ train_test_split() из библиотеки sklearn.model_selection . Это функция позволяет быстро и легко разделить данные на обучающие и тестовые выборки.
from sklearn.model_selection import train_test_split # предполагается, что df - это исходный dataframe train_df, test_df = train_test_split(df, test_size=0.2)
В этом коде df — это исходный dataframe, который нужно разделить. test_size — это параметр, который определяет размер тестовой выборки. В данном случае он равен 0.2, что означает, что 20% исходных данных будут использоваться для тестирования модели, а остальные 80% — для обучения.
Вывод
https://sky.pro/media/opczii-low_memory-i-dtype-v-pandas-read_csv/ read_csv для чтения csv-файлов. Однако, иногда при чтении больших файлов может возникнуть предупреждение:
DtypeWarning: Columns (4,5,7,16) have mixed types. Specify dtype option on import or set low_memory=False.
Это предупреждение говорит о том, что в некоторых столбцах файла смешаны разные типы данных. В данном случае, в столбцах с номерами 4,5,7,16.
Типы данных и опция dtype
В Pandas каждый столбец DataFrame имеет определенный тип данных. Несмотря на то, что Python является динамически типизированным языком, Pandas предпочитает работать с однородными типами данных для эффективности. Поэтому, когда в одном столбце смешиваются разные типы данных, например, числа и строки, Pandas выводит предупреждение. Опция dtype в функции read_csv позволяет указать тип данных для каждого столбца. Например, можно прочитать csv-файл, указав, что все столбцы должны быть типа строки:
df = pd.read_csv('somefile.csv', dtype=str)
Опция low_memory
Опция low_memory в функции read_csv связана с оптимизацией использования памяти при чтении файла. По умолчанию она установлена в True , и Pandas пытается уменьшить использование памяти, читая файл по частям и определяя тип данных для каждой части отдельно. Однако, это может приводить к предупреждению о смешанных типах данных, если типы данных в разных частях файла отличаются. Установка low_memory=False говорит Pandas читать весь файл целиком и определить типы данных для столбцов на основе всего файла. Это может помочь избежать предупреждения о смешанных типах данных, но увеличит использование памяти.
df = pd.read_csv('somefile.csv', low_memory=False)
https://sky.pro/media/reshenie-oshibki-ssl-certificate_verify_failed-pri-ustanovke-paketov-s-pomoshhyu-pip/ pip install , например:
pip install requests
В результате вы получите сообщение об ошибке, которое выглядит примерно так:
Could not fetch URL https://pypi.python.org/simple/requests/: connection error: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:598)
Это означает, что pip не может установить защищенное соединение с сервером PyPi, который хранит все пакеты Python. Существует несколько способов решения данной проблемы.
Использование параметра —trusted-host
Один из самых простых способов – добавить параметр --trusted-host , указав в качестве значения домен, с которым pip не может установить защищенное соединение. Например:
pip install --trusted-host pypi.python.org requests
Таким образом, вы говорите pip, что домен pypi.python.org является надежным, и pip должен игнорировать ошибки SSL при соединении с этим доменом.
Обновление pip
Если вы используете старую версию pip, то возможно, она не поддерживает некоторые более новые функции SSL. В этом случае обновление pip может помочь решить проблему. Обновить pip можно следующей командой:
pip install --upgrade pip
Использование параметра -i
Еще один способ – использовать параметр -i для указания зеркала PyPi, которое не использует SSL. Например:
pip install -i http://pypi.python.org/simple/ requests
Но стоит помнить, что такой способ может повлечь за собой риски безопасности, так как соединение будет не защищено.
Использование сертификата SSL
Если у вас есть собственный сертификат SSL, вы можете указать его при помощи параметра --cert . Например:
pip install --cert /path/to/your/certificate.pem requests
https://sky.pro/media/proverka-nalichiya-stolbcza-v-dataframe-biblioteki-pandas/ in . В примере ниже проверяется наличие столбца «A» в DataFrame df :
if 'A' in df: df['sum'] = df['A'] + df['C'] else: df['sum'] = df['B'] + df['C']
Для извлечения значения из определенной строки и колонки используется метод .loc[] . Этот метод принимает два аргумента: индекс строки и имя колонки. В нашем случае, чтобы извлечь значение из первой строки и колонки ‘B’, мы можем написать следующий код:
>>> value = df.loc[0, 'B'] >>> value 5
Таким образом, с помощью метода .loc[] можно извлечь значение из любой строки и колонки DataFrame.
Извлечение значения с помощью метода .iloc[]
Еще один способ извлечь значение — использовать метод .iloc[] , который принимает индексы строки и колонки. Этот метод полезен в том случае, если имена колонок неизвестны или неудобны для использования. В нашем случае, чтобы извлечь значение из первой строки и второй колонки (индекс 1, соответствует колонке ‘B’), мы можем написать следующий код:
>>> value = df.iloc[0, 1] >>> value 5
https://sky.pro/media/izvlechenie-imenovannyh-parametrov-iz-url-s-ispolzovaniem-flask/ username и password передаются через URL веб-приложению. Таким образом, веб-приложение может обрабатывать эти параметры для выполнения определенных действий, таких как аутентификация пользователя. Flask, популярный микрофреймворк для разработки веб-приложений на Python, предоставляет простой способ обработки именованных параметров из URL. В Flask для получения именованных параметров URL используется объект request . Вот простой пример того, как это можно сделать:
from flask import Flask, request app = Flask(__name__) @app.route('/login', methods=['GET', 'POST']) def login(): username = request.args.get('username') password = request.args.get('password') # Теперь можно использовать значения username и password
https://sky.pro/media/kak-podschitat-kolichestvo-znachenij-nan-v-stolbcze-dataframe-v-pandas/ В работе с данными на Python, особенно с большими наборами данных, часто приходится сталкиваться с пропущенными или неполными данными. Эти данные обычно представлены как NaN (Not a Number) в DataFrame библиотеки pandas. Важным этапом предварительной обработки данных является обнаружение и обработка таких значений. Рассмотрим следующий DataFrame в pandas:
import pandas as pd import numpy as np data = df = pd.DataFrame(data)
При выводе этого DataFrame на экран, значения NaN будут отображены, указывая на места пропущенных или недостающих данных. Чтобы подсчитать количество значений NaN в каждом столбце DataFrame, можно использовать метод isnull() , который возвращает DataFrame того же размера, заполненный булевыми значениями. Этот метод возвращает True там, где первоначальные данные были NaN , и False в противном случае. Затем, чтобы подсчитать количество True (то есть NaN в исходных данных) в каждом столбце, можно использовать метод sum() . В итоге, для подсчета значений NaN в каждом столбце достаточно применить следующую команду:
df.isnull().sum()
https://sky.pro/media/sozdanie-novogo-stolbcza-na-osnove-znachenij-iz-drugih-stolbczov-v-pandas/ apply() . Этот метод позволяет применить функцию ко всему DataFrame или к отдельным столбцам. В данном случае функцию следует применить ко всем строкам DataFrame, что можно сделать, указав параметр axis=1 . Ниже приведен пример функции, которую можно использовать для создания нового столбца. Эта функция принимает строку DataFrame и возвращает строку в соответствии с определенными условиями.
def assign_ethnicity(row): if row['ERI_Hispanic'] == 1: return 'Hispanic' elif row[['ERI_AmerInd_AKNatv', 'ERI_Asian', 'ERI_Black_Afr.Amer', 'ERI_HI_PacIsl', 'ERI_White']].sum() > 1: return 'Two or More' elif row['ERI_AmerInd_AKNatv'] == 1: return 'A/I AK Native' elif row['ERI_Asian'] == 1: return 'Asian' elif row['ERI_Black_Afr.Amer'] == 1: return 'Black/AA' elif row['ERI_HI_PacIsl'] == 1: return 'Haw/Pac Isl.' elif row['ERI_White'] == 1: return 'White' else: return 'Unknown'
Чтобы применить эту функцию к DataFrame и создать новый столбец, следует использовать метод apply() . Ниже приведен пример кода, который создает новый столбец ‘Ethnicity’, основанный на значениях других столбцов.
df['Ethnicity'] = df.apply(assign_ethnicity, axis=1)
Эта команда должна быть выполнена в командной строке, и перед выполнением необходимо перейти в каталог, где расположен скачанный .whl файл.
После выполнения команды пакет будет установлен и готов к использованию.
Важно отметить, что для работы с .whl файлами необходим pip не ниже версии 1.4. Если установлена более старая версия, то ее можно обновить следующей командой:
pip install --upgrade pip
Эта команда обновит pip до последней доступной версии.
Заключение
Таким образом, установка Python пакетов с помощью .whl файлов — это простой и удобный способ добавить в проект необходимые библиотеки. Этот подход особенно полезен, когда нет возможности собрать пакет из исходного кода или когда пакет доступен только в формате .whl.
https://sky.pro/media/preobrazovanie-slovarya-v-json-i-obratno/ json . Однако, может возникнуть проблема при попытке обратиться к данным в JSON, преобразованным из словаря. Рассмотрим пример:
import json data = json_data = json.dumps(data) print(json_data['key'])
Здесь код пытается обратиться к значению по ключу в JSON, однако вместо этого возникает ошибка: TypeError: string indices must be integers, not str . Это происходит потому, что json.dumps() преобразует словарь в строку, а не в JSON. В результате, при попытке обратиться к элементу по ключу, интерпретатор Python считает, что обращение идет к индексу строки, а не к ключу словаря. Чтобы избежать этой ошибки, необходимо преобразовывать JSON обратно в словарь после его создания. Это можно сделать с помощью метода json.loads() .
import json data = json_data = json.dumps(data) data = json.loads(json_data) print(data['key'])
https://sky.pro/media/peremeshivanie-strok-dataframe-v-python/ sample из библиотеки pandas . Эта функция возвращает случайную выборку из переданного ей DataFrame. Для получения перемешанного DataFrame достаточно передать в эту функцию исходный DataFrame и указать размер выборки равным количеству строк в исходном DataFrame.
import pandas as pd # Создание исходного DataFrame df = pd.DataFrame(< 'Product': ['Apple', 'Banana', 'Pear', 'Tomato', 'Cucumber', 'Potato'], 'Category': ['Fruit', 'Fruit', 'Fruit', 'Vegetable', 'Vegetable', 'Vegetable'] >) # Перемешивание строк DataFrame df = df.sample(frac=1) print(df)
На выходе будет получен DataFrame с перемешанными строками: | | Product | Category |
|—|———|———-|
| 4 | Cucumber| Vegetable|
| 2 | Pear | Fruit |
| 1 | Banana | Fruit |
| 3 | Tomato | Vegetable|
| 0 | Apple | Fruit |
| 5 | Potato | Vegetable| Важно отметить, что индексы строк после перемешивания сохраняются. Если требуется сбросить индексы и присвоить новые, это можно сделать с помощью функции reset_index :
df = df.sample(frac=1).reset_index(drop=True)
https://sky.pro/media/podschet-kolichestva-opredelennogo-elementa-v-ndarray/ 2 в данный массив. Попытка использовать метод count приводит к ошибке, поскольку данный метод отсутствует в классе numpy.ndarray :
arr.count(2)
AttributeError: 'numpy.ndarray' object has no attribute 'count'
Вместо этого, можно использовать функцию numpy.count_nonzero() , которая подсчитывает количество ненулевых элементов в массиве. Для подсчета количества определенного элемента в массиве, можно сначала преобразовать массив в булевый, где True обозначает места, где находится интересующий нас элемент, а False — все остальные. Затем применить numpy.count_nonzero() к получившемуся массиву. Пример подсчета количества элементов 2 в массиве arr :
import numpy as np arr = np.array([2, 3, 2, 2, 5, 3, 2, 4, 5, 2, 5, 5, 5]) # Преобразование массива в булев bool_arr = (arr == 2) # Подсчет количества искомых элементов count = np.count_nonzero(bool_arr) print(count) # Выводит: 5
https://sky.pro/media/proverka-na-nalichie-nan-v-dataframe-v-pandas/ isnull() . Она возвращает другой DataFrame, где каждое значение это булево значение, указывающее, является ли соответствующее значение в исходном DataFrame NaN.
df.isnull()
Чтобы узнать, есть ли хотя бы одно значение NaN в DataFrame, можно использовать функцию any() , которая возвращает True, если хотя бы одно значение в DataFrame является True.
df.isnull().any().any()
Вариант 2: isna() и values
Альтернативный способ — использовать функцию isna() , которая аналогична isnull() . Однако, вместо второго any() можно использовать свойство values и функцию any() из numpy.
np.any(df.isna().values)
https://sky.pro/media/isklyuchenie-opredelennyh-stolbczov-iz-dataframe-v-pandas/ drop . Этот метод удаляет указанные строки или столбцы из DataFrame. В данном случае, необходимо указать имя столбца и axis=1 (что указывает на столбцы, поскольку axis=0 относится к строкам).
df.drop('b', axis=1)
Использование оператора del
Еще один способ — это использование оператора del , который удаляет указанный столбец из исходного DataFrame.
del df['b']
Использование индексации DataFrame
Также можно использовать индексацию DataFrame с помощью метода loc или iloc и выбрать все столбцы, кроме одного.
df.loc[:, df.columns != 'b']
https://sky.pro/media/bystrota-raboty-funkczii-range-v-python-3/ range() , выполняется так быстро в Python 3. В качестве примера можно привести следующий код:
1000000000000000 in range(1000000000000001)
https://sky.pro/media/raznicza-mezhdu-select_related-i-prefetch_related-v-django-orm/ select_related и prefetch_related . Предположим, есть две модели: Author и Book , где Author имеет множество Book . Если необходимо получить все книги определенного автора, можно использовать select_related или prefetch_related . Но в чем разница?
select_related
Метод select_related следует по отношениям «один ко многим» и «один к одному», выбирая дополнительные данные связанных объектов при выполнении своего запроса. Это означает, что Django выполнит запрос SQL с соединением (JOIN) для извлечения связанных данных. К примеру, если есть запрос Author.objects.select_related('book').get(id=1) , Django выполнит SQL-запрос, который получит все данные об авторе и его книгах одним запросом. Это может быть очень эффективно, если есть необходимость получить связанные данные, так как это уменьшает количество запросов к базе данных.
prefetch_related
С другой стороны, prefetch_related выполняет отдельный поиск для каждого отношения и выполняет «соединение» на Python. Это означает, что Django сначала выполнит запрос для получения всех авторов, а затем отдельный запрос для каждой книги, связанной с автором. В случае Author.objects.prefetch_related('book').get(id=1) , Django сначала выполнит запрос для получения автора, а затем отдельный запрос для получения всех его книг. Затем Django «соединит» эти данные в Python, чтобы предоставить удобный способ доступа к связанным данным.
Вывод
- Интерпретатор ищет в своей таблице имен функцию с указанным именем.
- Он вызывает найденную функцию.
- Функция возвращает новый пустой список или словарь.
Как видно, во втором случае процесс создания объекта включает в себя дополнительные шаги, что и приводит к увеличению времени выполнения.
Это не значит, что использование функций list() и dict() — это плохой стиль. Наоборот, в некоторых случаях они могут быть предпочтительнее, так как позволяют явно указать тип создаваемого объекта, что может улучшить читаемость кода. Однако, если важна максимальная производительность, то следует отдавать предпочтение литералам.
https://sky.pro/media/oshibka-popytka-otnositelnogo-importa-za-predelami-paketa-verhnego-urovnya-v-python/ test.py есть следующая строка кода:
from ..A import foo
При попытке запуска этого кода из папки package с помощью команды python -m test_A.test возникает ошибка «попытка относительного импорта за пределами пакета верхнего уровня». Однако, если запустить тот же код из родительской директории пакета package , ошибки не будет. Причина этого в том, что Python считает текущую директорию верхним уровнем пакета. То есть при запуске из папки package , Python считает, что test_A — это пакет верхнего уровня, и попытка импорта из ..A выходит за его пределы, что вызывает ошибку.
Решение проблемы
Чтобы избежать этой ошибки, рекомендуется запускать код из родительской директории пакета. В данном примере, вместо команды python -m test_A.test следует использовать команду python -m package.test_A.test , запущенную из родительской директории для package .
Заключение
https://sky.pro/media/oshibka-dict-object-has-no-attribute-iteritems/ iteritems() для словарей. Этот метод был доступен в Python 2.x для итерации по элементам словаря, что позволяло эффективно работать с большими словарями. Код, использующий iteritems() , может выглядеть следующим образом:
my_dict = for key, value in my_dict.iteritems(): print(key, value)
При попытке выполнения такого кода в Python 3.x возникает ошибка AttributeError: 'dict' object has no attribute 'iteritems' , поскольку в Python 3 этот метод был удален. Вместо него теперь следует использовать метод items() , который ведет себя аналогично iteritems() в Python 2.x. Таким образом, код для Python 3.x будет выглядеть следующим образом:
my_dict = for key, value in my_dict.items(): print(key, value)
https://sky.pro/media/razlichie-mezhdu-iloc-i-loc-v-pandas/ iloc и loc . Рассмотрим пример. Допустим, есть DataFrame с данными о студентах и их оценками.
import pandas as pd data = < 'Name': ['John', 'Anna', 'Peter', 'Linda', 'James'], 'Score': [85, 90, 78, 92, 88], >df = pd.DataFrame(data) print(df)
Пример вывода DataFrame:
Name Score 0 John 85 1 Anna 90 2 Peter 78 3 Linda 92 4 James 88
Если необходимо получить данные об Anna и ее оценке, вот как можно это сделать с помощью loc и iloc .
print(df.loc[1]) print(df.iloc[1])
В обоих случаях будет получен один и тот же результат:
Name Anna Score 90 Name: 1, dtype: object
Здесь возникает вопрос: если результат одинаковый, то в чем разница между iloc и loc ? Основное различие между этими двумя методами заключается в том, что iloc применяется для обращения по численному индексу, тогда как loc используется для обращения по метке (label). В приведенном выше примере индексы и метки совпадают, поэтому результаты выглядят одинаковыми. Но что если индексы изменить?
df.index = ['a', 'b', 'c', 'd', 'e'] print(df)
Теперь DataFrame выглядит следующим образом:
Name Score a John 85 b Anna 90 c Peter 78 d Linda 92 e James 88
Если попытаться получить данные об Anna, используя те же методы, результаты будут различными.
print(df.loc['b']) print(df.iloc[1])
https://sky.pro/media/avtomaticheskoe-sozdanie-fajla-requirements-txt/ requirements.txt , то приходится создавать его вручную, что может быть трудоемким и затруднительным. Пример. Если в проекте используются библиотеки numpy , pandas и matplotlib , то вручную необходимо создать файл requirements.txt и записать в него следующее:
numpy==1.19.5 pandas==1.1.5 matplotlib==3.3.4
Это означает, что проект зависит от этих трёх библиотек, и указаны конкретные версии каждой из них. Оптимальным решением для этой проблемы может стать автоматическое создание файла requirements.txt .
Автоматическое создание файла requirements.txt
Существуют инструменты, которые позволяют автоматически создать файл requirements.txt на основе импортов в исходном коде. Однако, стоит отметить, что такой подход не всегда является оптимальным, поскольку не все зависимости могут быть указаны в импортах. Один из таких инструментов — это pipreqs . Для его установки достаточно выполнить команду:
pip install pipreqs
После установки, для создания файла requirements.txt нужно перейти в директорию с исходным кодом и выполнить команду:
pipreqs .
Пример отмены последней миграции
Предположим, была произведена миграция с именем 0004_add_new_table . Для ее отмены необходимо ввести следующую команду в командной строке:
python manage.py migrate app_name 0003_previous_migration
В этом примере app_name — это имя приложения, для которого была выполнена миграция, а 0003_previous_migration — это имя предыдущей миграции, до которой необходимо откатиться.
Удаление файла миграции
После отмены миграции файл с ней можно просто удалить из каталога миграций.
rm app_name/migrations/0004_add_new_table.py
https://sky.pro/media/podskazki-tipov-dlya-metodov-vnutri-klassa-v-python/ Position , который имеет метод add . Метод add должен принимать другой объект Position в качестве аргумента и возвращать новый объект Position . Однако, некоторые редакторы кода, такие как PyCharm, могут выдать предупреждение, что ссылка Position не может быть разрешена. Это происходит, потому что класс Position еще не был полностью определен в точке, где мы пытаемся использовать его в качестве подсказки типа. В Python существует специальный синтаксис для обращения к типам, которые еще не были полностью определены в момент использования. Этот синтаксис использует строковые литералы для обозначения типов. Вместо того чтобы напрямую использовать класс Position в качестве подсказки типа, мы можем использовать строку 'Position' :
class Position: def __init__(self, x: int, y: int): self.x = x self.y = y def add(self, other: 'Position') -> 'Position': return Position(self.x + other.x, self.y + other.y)
https://sky.pro/media/poluchenie-tekushhej-daty-v-formate-yyyy-mm-dd-v-python/ isoformat() из модуля datetime.
import datetime current_date = datetime.date.today().isoformat() print(current_date)
Метод isoformat() возвращает строковое представление даты в формате YYYY-MM-DD, что и требовалось. Но если есть необходимость использовать другой формат даты, можно использовать метод strftime() , который позволяет форматировать дату по заданной маске. Вот пример использования этого метода:
import datetime current_date = datetime.datetime.now().strftime('%Y-%m-%d') print(current_date)
https://sky.pro/media/sohranenie-i-vosstanovlenie-modelej-v-tensorflow/ tf.train.Saver() . Создается объект этого класса, который затем используется для сохранения переменных сессии.
import tensorflow as tf # предположим, что у нас есть некоторая модель model = . # создаем сессию sess = tf.Session() # инициализируем переменные sess.run(tf.global_variables_initializer()) # обучаем модель . # создаем Saver saver = tf.train.Saver() # сохраняем модель saver.save(sess, 'my_model')
В результате модель будет сохранена в файл ‘my_model’.
Восстановление модели
Для восстановления модели также используется класс tf.train.Saver() . При этом важно отметить, что для корректного восстановления модели, структура модели должна быть идентичной структуре модели на момент сохранения.
import tensorflow as tf # предположим, что у нас есть некоторая модель model = . # создаем сессию sess = tf.Session() # создаем Saver saver = tf.train.Saver() # восстанавливаем модель saver.restore(sess, 'my_model')
https://sky.pro/media/effektivnoe-primenenie-funkczii-k-massivu-numpy/ np.vectorize , которая преобразует обычную функцию Python в функцию, которую можно применить к массивам numpy.
import numpy as np x = np.array([1, 2, 3, 4, 5]) # Получаем массив квадратов каждого элемента в x squarer = np.vectorize(lambda t: t ** 2) squares = squarer(x)
Еще одним вариантом является использование универсальных функций numpy (ufuncs), которые предназначены для выполнения элементарных операций над массивами. В данном случае можно использовать функцию np.square для получения квадратов элементов массива.
import numpy as np x = np.array([1, 2, 3, 4, 5]) # Получаем массив квадратов каждого элемента в x squares = np.square(x)
https://sky.pro/media/rabota-s-pytest-vybor-testov-dlya-vypolneniya/ tests.txt , который содержит список тестов для выполнения:
tests_directory/test1.py::test_func1 tests_directory/test2.py::test_func2
Как запустить эти тесты при помощи pytest?
Выбор тестов по шаблону
Простейший способ выбрать тесты для запуска — использовать ключ -k и указать шаблон имени теста. Например, команда pytest -k test_func1 запустит все тесты, имя которых содержит test_func1 . Однако, этот метод позволяет указать только один шаблон, что ограничивает его использование.
Выбор тестов по меткам
Еще один способ — использовать метки (marks). Метка — это способ пометить определенный тест или группу тестов. Затем эти тесты можно выбрать для запуска с помощью ключа -m . Например, если есть тест, помеченный как test_func1 , то его можно запустить командой pytest -m test_func1 . Этот метод позволяет группировать тесты по произвольным критериям.
Выбор тестов из файла
Если есть файл, который содержит список тестов для выполнения, то его можно использовать для выбора тестов. Для этого нужно передать этот файл в качестве аргумента команде pytest. Например, команда pytest -m @tests.txt запустит все тесты, указанные в файле tests.txt .
Использование хук-функций
https://sky.pro/media/oshibka-invalid-command-bdist_wheel-v-python/ bdist_wheel , возникает сообщение об ошибке invalid command 'bdist_wheel' . Это обычно происходит в тех случаях, когда на компьютере не установлен модуль wheel . Пример:
python setup.py bdist_wheel usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] . ] or: setup.py --help [cmd1 cmd2 . ] or: setup.py --help-commands or: setup.py cmd --help error: invalid command 'bdist_wheel'
Эта ошибка означает, что команда bdist_wheel не была распознана интерпретатором Python, что, в свою очередь, обычно свидетельствует о том, что не установлен модуль wheel . Модуль wheel в Python используется для создания так называемых «колесных» дистрибутивов (wheel distributions), которые являются бинарной альтернативой Source Archives. Колесные дистрибутивы обычно создаются для ускорения установки пакетов Python. Для устранения этой проблемы, обычно достаточно установить модуль wheel . Это можно сделать с помощью системы управления пакетами pip, выполнив следующую команду:
pip install wheel
Эта команда установит модуль wheel , после чего команда bdist_wheel должна начать работать. Важно отметить, что в некоторых случаях может потребоваться обновить setuptools до последней версии. Это можно сделать с помощью команды:
pip install --upgrade setuptools
https://sky.pro/media/ukazanie-neskolkih-tipov-vozvrashhaemyh-znachenij-v-python-s-ispolzovaniem-type-hints/ list ) или логическое значение ( bool ), в зависимости от того, выполняется ли определенное условие. Вот пример такой функции:
def example_function(id): if isinstance(id, int): return [id] else: return False
В этом случае, как указать типы возвращаемых значений функции при использовании подсказок типов? Просто указать list or bool не сработает, потому что Python не будет понимать такой записи. Решение этой проблемы предлагает модуль typing в Python, который предоставляет специальный класс Union для указания нескольких возможных типов возвращаемых значений. Этот класс позволяет объединить несколько типов в один с использованием оператора , (запятая). Вот как это работает:
from typing import Union def example_function(id) -> Union[list, bool]: if isinstance(id, int): return [id] else: return False
https://sky.pro/media/sortirovka-dataframe-v-pandas-po-odnoj-kolonke/ sort_values() . Этот метод принимает на вход имя колонки, по которой нужно отсортировать данные, и возвращает DataFrame, отсортированный по указанной колонке. Вернемся к нашему примеру с данными об отпусках. Допустим, данные загружены в DataFrame df . Чтобы отсортировать эти данные по месяцам, можно использовать следующий код:
df = df.sort_values('Месяц')
Теперь данные в df отсортированы по месяцам: | Сотрудник | Месяц | Количество дней |
|————|——-|——————|
| Сидоров | Февраль| 7 |
| Иванов | Май | 14 |
| Петров | Август| 10 | Однако, в данном случае месяцы отсортированы в алфавитном порядке, а не в календарном. Чтобы решить эту проблему, можно добавить вторую колонку с номерами месяцев и отсортировать данные уже по ней:
df['Номер месяца'] = [5, 2, 8] df = df.sort_values('Номер месяца')
https://sky.pro/media/razbiraemsya-s-oshibkoj-truth-value-of-a-series-is-ambiguous/ or ), как это делается в Python.
df = df[(df['col'] < -0.25) or (df['col'] > 0.25)]
Однако, при попытке выполнить такой код, Python выдает ошибку: «Truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all()». Что она означает и как с ней справиться? Ошибка говорит о том, что не ясно, как интерпретировать логическое значение всего объекта Series. В Python операторы and и or ожидают получить одно булево значение с каждой стороны, но в случае с pandas Series с каждой стороны есть целый набор значений. И Python не знает, что делать дальше. Поэтому в pandas для подобных ситуаций предусмотрены специальные операторы & и | , которые выполняют поэлементные логические операции «и» и «или» соответственно. Кроме того, при использовании этих операторов необходимо каждое условие оборачивать в круглые скобки, чтобы избежать проблем с приоритетом операций. Исправленный код будет выглядеть так:
df = df[(df['col'] < -0.25) | (df['col'] > 0.25)]
https://sky.pro/media/dobavlenie-znachenij-po-umolchaniyu-dlya-funkczij-s-ispolzovaniem-podskazok-tipov-v-python/ greeting имеет значение по умолчанию «Привет». Но что делать, если мы хотим использовать подсказки типов (type hinting) вместе со значениями по умолчанию? Python позволяет это делать, но синтаксис может показаться немного неочевидным для новичков. Для добавления подсказок типов в функцию Python, вам нужно использовать двоеточие ( : ) после имени параметра, затем указать тип данных. Если вы хотите добавить значение по умолчанию, это делается после подсказки типа с использованием знака равенства ( = ).
def greet(name: str, greeting: str = "Привет") -> str: return f", !"
После выполнения этих шагов ошибка с отсутствием geckodriver в PATH должна быть решена. Теперь вы сможете успешно использовать Selenium с браузером Firefox на Python.
https://sky.pro/media/ukazanie-tipa-funkczii-v-podskazkah-tipov-python/ typing нет типа Function , а в соответствующем Python Enhancement Proposal (PEP), PEP 483, информации по этой теме не находится. Рассмотрим типичную задачу. Предположим, есть функция, которая принимает другую функцию в качестве аргумента:
def apply_func(func, x): return func(x)
В этом случае может возникнуть необходимость указать, что func должен быть функцией. В Python для этого можно использовать подсказки типов.
Тип Callable
Для указания типа функции в подсказках типов в Python используется тип Callable из модуля typing . Этот тип принимает два параметра: список типов аргументов функции и тип возвращаемого значения.
from typing import Callable def apply_func(func: Callable[[int], float], x: int) -> float: return func(x)
В этом примере func ожидается быть функцией, которая принимает один аргумент типа int и возвращает значение типа float . Если функция принимает несколько аргументов, их типы перечисляются в списке. Если функция не возвращает значение (возвращает None ), вторым параметром Callable указывается None .
from typing import Callable, Any, None def apply_func(func: Callable[[int, Any], None], x: int, y: Any) -> None: return func(x, y)
https://sky.pro/media/ispolzovanie-matplotlib-inline-v-python/ %matplotlib inline . Она может вызывать недоумение, поскольку выглядит иначе, по сравнению с большинством команд Python. Например, в следующем блоке кода:
import matplotlib.pyplot as plt import numpy as np %matplotlib inline x = np.linspace(0, 10, 100) plt.plot(x, np.sin(x)) plt.plot(x, np.cos(x)) plt.show()
Здесь команда %matplotlib inline может показаться загадочной. Что это и для чего она нужна?
Что такое %matplotlib inline?
%matplotlib inline — это команда, специфичная для IPython (или Jupyter notebook), которая позволяет вам встроить графики прямо внутрь ноутбука, а не в новом окне.
Зачем она нужна?
Когда вы работаете с Matplotlib в интерактивных режимах, таких как Jupyter notebook или IPython, графики, которые вы создаете, отображаются в отдельном окне. Это может быть неудобно, особенно если вы хотите сохранить свою работу в виде ноутбука с результирующими графиками. Команда %matplotlib inline решает эту проблему, позволяя вам встроить графики прямо в ноутбук. Это удобно, поскольку позволяет сохранить все в одном месте, а также делиться своими ноутбуками с другими.
Как это работает?
%matplotlib inline является примером использования так называемых «магических» функций в IPython. «Магические» функции — это набор команд, предоставляемых IPython, которые имеют синтаксис, отличающийся от обычного синтаксиса Python. Эти функции начинаются с символа % или %% и предназначены для выполнения определенных операций. В случае с %matplotlib inline , когда математическая библиотека выполняет команду для создания графика, «магическая» функция перехватывает вывод и встраивает его в ноутбук.
Вывод
https://sky.pro/media/rabota-s-obedineniem-dannyh-v-pandas/ merge() . Она может выполнять четыре типа объединений: «INNER», «LEFT», «RIGHT» и «FULL OUTER». Пример использования функции merge() :
import pandas as pd df1 = pd.DataFrame(, index=['K0', 'K1', 'K2']) df2 = pd.DataFrame(, index=['K0', 'K2', 'K3']) result = pd.merge(df1, df2, left_index=True, right_index=True, how='outer')
Добавление NaN для отсутствующих строк после слияния
При выполнении объединения типа «LEFT», «RIGHT» или «FULL OUTER» могут появиться значения NaN. Это происходит, когда данные в ключевых столбцах не совпадают. В этом случае Pandas автоматически заполняет отсутствующие значения NaN.
Удаление NaN после слияния
После слияния можно использовать функцию dropna() для удаления строк, содержащих значения NaN. Пример:
result = result.dropna()
Слияние по индексу
Pandas позволяет выполнять слияние как по столбцам, так и по индексу. Для слияния по индексу можно использовать параметры left_index=True и/или right_index=True .
Объединение нескольких DataFrame
Для объединения нескольких DataFrame можно использовать функцию concat() . Она принимает список DataFrame и объединяет их вместе. Пример:
result = pd.concat([df1, df2, df3])
Перекрестное объединение с Pandas
Перекрестное объединение, или cartesian product, можно выполнить с помощью функции merge() с параметром how='cross' .
Разница между merge, join, concat и update
- merge() используется для объединения двух DataFrame по общему ключу.
- join() используется для объединения двух DataFrame по индексу.
- concat() используется для объединения двух или более DataFrame вместе вдоль оси.
- update() используется для модификации DataFrame путем замены старых значений новыми значениями.
В зависимости от задачи, может быть использован один или несколько из этих методов.
https://sky.pro/media/formatirovanie-chisel-s-fiksirovannym-chislom-znakov-posle-zapyatoj-s-ispolzovaniem-f-strok-v-python/ value так, чтобы было только два знака после запятой, стандартное преобразование в строку не подойдет. Существует несколько способов форматирования строк в Python, в том числе с использованием метода format и оператора % . Однако, с версии Python 3.6 в языке присутствует еще один, более современный и удобный способ форматирования строк — f-строки. F-строки — это строковые литералы, которые предваряются символом f или F и в которых выражения в фигурных скобках заменяются их значениями. Для форматирования чисел с плавающей точкой с использованием f-строк и задания количества знаков после запятой можно использовать следующий синтаксис:
value = 10.1234567 formatted_value = f""
https://sky.pro/media/oshibka-pri-obnovlenii-pip-ne-udaetsya-importirovat-imya-main/ main была перемещена, и она больше не может быть импортирована напрямую из pip. Вместо этого она теперь доступна из pip._internal.main . Попробуйте отредактировать файл «/usr/bin/pip3» так, чтобы он выглядел следующим образом:
#!/usr/bin/python3 # GENERATED BY DEBIAN import sys # Run the main entry point, similarly to how setuptools does it, but because # we didn't install the actual entry point from setup.py, don't use the # pkg_resources API. from pip._internal.main import main if __name__ == '__main__': sys.exit(main())
Это должно решить проблему с импортом. Заметим, что редактирование системных файлов является не лучшей практикой, и в большинстве случаев лучше обновить pip до последней версии или использовать виртуальное окружение. Но если это все еще не решает проблему, можно применить метод, который заключается в удалении pip и повторной установке его. Это можно сделать с помощью следующих команд:
sudo apt-get remove python3-pip sudo apt-get install python3-pip
https://sky.pro/media/udalenie-sredy-conda/ testenv и пытается удалить её командой:
$ conda env remove --name testenv
В ответ на это система выдает указанную выше ошибку. При попытке деактивировать среду командой source deactivate и повторном удалении, ошибка все равно остается. Данная проблема возникает из-за того, что Conda не позволяет удалить среду, если она в данный момент активна. Для успешного удаления среды необходимо сначала деактивировать ее, а затем выполнить команду удаления. Важно отметить, что для деактивации среды в Conda используется команда conda deactivate , а не source deactivate . Последняя команда устарела и может не работать в некоторых случаях. Таким образом, для удаления среды testenv необходимо выполнить следующие команды:
$ conda deactivate $ conda env remove --name testenv
https://sky.pro/media/kak-obnovit-paket-s-ispolzovaniem-pip/ pip update или pip upgrade , возникает ошибка, так как таких команд не существует. Но не стоит пугаться, решение проблемы есть и оно довольно простое. Для обновления пакета используется менеджер пакетов pip, который является стандартным для Python. Для обновления пакета в Python, можно использовать следующую команду:
pip install --upgrade имя_пакета
Вместо имя_пакета необходимо подставить название пакета, который требуется обновить. Например, чтобы обновить пакет numpy, нужно ввести следующую команду:
pip install --upgrade numpy
Если требуется обновить все установленные пакеты, можно использовать pip-review, утилиту для обновления пакетов. Сначала ее нужно установить:
pip install pip-review
Затем можно обновить все пакеты одной командой:
pip-review --auto
https://sky.pro/media/proverka-ispolzovaniya-gpu-v-pytorch/ nvidia-smi . Однако, возможно, есть желание проверить использование GPU прямо из скрипта на Python. Например, для автоматизации процесса проверки или для внедрения такой проверки в код проекта. В PyTorch есть встроенные средства для проверки того, используется ли GPU. Для этого можно воспользоваться следующим кодом:
import torch print(torch.cuda.is_available())
Если этот код возвращает True , это означает, что PyTorch успешно обнаруживает GPU и может использовать его для обучения моделей. Если возвращается False , значит, GPU либо отсутствует, либо PyTorch не может его обнаружить. Также можно проверить, на каком устройстве находится тензор. Для этого можно использовать метод .device у тензора:
x = torch.Tensor([1, 2, 3]) print(x.device)
https://sky.pro/media/ispolzovanie-funkczii-itertools-groupby-v-python/ itertools.groupby() , которая позволяет группировать элементы итерируемого объекта по определенному критерию.
Применение функции itertools.groupby()
Рассмотрим пример использования itertools.groupby() на примере вышеуказанного списка чисел. Предположим, что условием для группировки является увеличение числа по сравнению с предыдущим.
from itertools import groupby numbers = [1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4, 5] groups = [] for k, g in groupby(numbers, key=lambda x: x): groups.append(list(g)) print(groups)
https://sky.pro/media/vyzov-funkczii-modulya-po-eyo-imeni-stroke/ getattr() , которая возвращает значение атрибута объекта. Эта функция принимает два аргумента: объект и имя атрибута в виде строки.
import math func_name = "sqrt" func = getattr(math, func_name) print(func(16)) # выводит 4.0
В этом примере getattr() возвращает функцию sqrt() из модуля math , и эта функция затем вызывается с аргументом 16 .
Использование словаря
Ещё одним способом является использование словаря, в котором ключами являются имена функций, а значениями — сами функции.
import math func_dict = func_name = "sqrt" print(func_dict[func_name](16)) # выводит 4.0
class Cat: def __init__(self, name): self.name = name
И экземпляр этого класса:
my_cat = Cat('Fluffy')
Скажем, мы хотим добавить метод meow , который будет выводить на экран «Meow!». Однако, мы не хотим менять определение класса Cat . Сделать это можно с помощью так называемых функций-первоклассников в Python, которые позволяют присваивать функции переменной и использовать ее как объект.
def meow(self): print('Meow!') my_cat.meow = meow.__get__(my_cat)
Теперь метод meow доступен для нашего экземпляра my_cat :
my_cat.meow()
Meow!
https://sky.pro/media/opredelenie-operaczionnoj-sistemy-v-python/ os . В этом модуле есть функция os.name , которая возвращает имя операционной системы, на которой запущен скрипт.
import os print(os.name)
https://sky.pro/media/skachivanie-fajlov-iz-interneta-s-ispolzovaniem-python/ requests . Она предоставляет простой в использовании интерфейс для отправки HTTP-запросов и обработки ответов. Для скачивания файла можно использовать метод get() , который отправляет GET-запрос на указанный URL и возвращает ответ сервера. Затем полученные данные можно записать в файл с помощью стандартных средств Python. Вот простой пример кода, который скачивает mp3-файл по URL:
import requests url = 'http://example.com/song.mp3' response = requests.get(url) with open('song.mp3', 'wb') as file: file.write(response.content)
https://sky.pro/media/kak-najti-metody-obekta-v-python/ dir() , которая возвращает список атрибутов и методов для любого объекта. Вот пример ее использования:
# Создание объекта класса list my_object = list() # Получение списка всех методов и атрибутов этого объекта methods = dir(my_object) # Вывод списка print(methods)
В результате выполнения этого кода будет выведен список всех методов и атрибутов объекта list .
Проверка наличия метода у объекта
Если вам нужно проверить, есть ли у объекта конкретный метод, вы можете воспользоваться функцией hasattr() . Она принимает два аргумента: объект и строку, содержащую имя атрибута или метода, который нужно проверить. Вот пример использования этой функции:
# Создание объекта класса list my_object = list() # Проверка наличия метода append у объекта has_method = hasattr(my_object, 'append') # Вывод результата print(has_method)
- __init__ — инициализирует итератор, устанавливая максимальное значение и начальное число;
- __iter__ — возвращает итератор. В данном случае, это сам объект;
- __next__ — возвращает следующее значение в последовательности. Если итератор достигнет максимального значения, он вызовет исключение StopIteration , что сигнализирует о завершении итерации.
Теперь этот итератор можно использовать как любой другой итератор в Python:
for i in EvenNumbers(10): print(i)
На выходе получим:
0 2 4 6 8
Таким образом, создание собственного итератора в Python — это весьма прямолинейный процесс. Важно помнить о необходимости реализовать методы __iter__ и __next__ , а также об обработке исключения StopIteration .
https://sky.pro/media/ispolzovanie-args-i-kwargs-v-python-2/ *args и **kwargs .
Механизм *args
*args используется для передачи произвольного числа безымянных аргументов. Важно отметить, что args в данном контексте — это просто переменная, а звездочка ( * ) указывает на то, что мы хотим получить кортеж из всех переданных аргументов. Имя args является общепринятым, но можно использовать любое другое. Пример функции, принимающей любое количество аргументов:
def func(*args): for arg in args: print(arg) func('Hello', 'world', '!') # Выведет: Hello \n world \n !
Механизм **kwargs
**kwargs используется для передачи произвольного числа именованных аргументов. Как и в предыдущем случае, kwargs — это просто переменная, а две звездочки ( ** ) указывают на то, что мы хотим получить словарь из всех переданных аргументов. Имя kwargs также является общепринятым, но можно использовать любое другое. Пример функции, принимающей любое количество именованных аргументов:
def func(**kwargs): for key, value in kwargs.items(): print(f': ') func(name='John', age=25) # Выведет: name: John \n age: 25
Вывод
https://sky.pro/media/predstavlenie-enum-v-python/ enum , который предоставляет поддержку Enum. Используя этот модуль, можно легко определить Enum следующим образом:
from enum import Enum class Direction(Enum): Up = 1 Down = 2 Left = 3 Right = 4
Теперь Direction является Enum, и его значения можно использовать в коде. Каждому значению автоматически присваивается целочисленное значение, начиная с 1, но можно задать и свои значения, как показано выше. Чтобы использовать значения Enum, можно обращаться к ним напрямую:
print(Direction.Up)
https://sky.pro/media/sliyanie-dvuh-slovarej-v-odnom-vyrazhenii-v-python/ update() . Однако, данный метод изменяет существующий словарь. Чтобы создать новый словарь, необходимо использовать специальный синтаксис, который доступен, начиная с Python 3.5.
dictionary1 = dictionary2 = merged_dictionary =
В результате получим следующий словарь:
https://sky.pro/media/preobrazovanie-spiska-strok-v-stroku-s-razdelitelyami/ join() , который объединяет все элементы списка в одну строку с заданным разделителем. Вот как это выглядит на практике:
list = ['яблоко', 'банан', 'апельсин'] string = ','.join(list)
В результате выполнения этого кода переменная string будет содержать строку ‘яблоко,банан,апельсин’. Метод join() не ограничивается использованием только запятой в качестве разделителя. Вместо запятой можно использовать любую строку. Например, если нужно, чтобы элементы были разделены точкой с запятой, код будет выглядеть так:
list = ['яблоко', 'банан', 'апельсин'] string = ';'.join(list)
https://sky.pro/media/opredelenie-poslednego-dnya-mesyacza-v-python/ В работе с датами и временем в Python часто возникают разные задачи. Одна из таких — определить последний день месяца для заданной даты. Примером может быть задача, где необходимо определить последний день месяца для даты 15 марта 2022 года. Python предоставляет несколько способов решения этой задачи.
Стандартная библиотека Python
В стандартной библиотеке Python есть модуль calendar , который предоставляет функцию monthrange(year, month) , возвращающую количество дней в месяце и день недели первого дня месяца.
import calendar year = 2022 month = 3 _, last_day = calendar.monthrange(year, month) print(last_day)
В этом случае, last_day будет содержать значение 31, что соответствует последнему дню марта 2022 года.
Библиотека dateutil
Библиотека dateutil предоставляет более удобный способ нахождения последнего дня месяца с помощью функции relativedelta .
from datetime import datetime from dateutil.relativedelta import relativedelta date = datetime(year=2022, month=3, day=15) last_day = date + relativedelta(day=31) print(last_day)
https://sky.pro/media/vse-o-__all__-v-python/ __all__ . Обычно она встречается в начале файла, например, __init__.py . Особенно часто она используется в больших библиотеках или модулях, где требуется организовать импорт определенных функций, классов или переменных. Рассмотрим пример. Предположим, что есть модуль mymodule со следующим содержимым:
def public_function(): return "I'm a public function." def _private_function(): return "I'm a private function." __all__ = ['public_function']
https://sky.pro/media/poluchenie-puti-i-imeni-tekushhego-ispolnyaemogo-fajla-python/ main_script.py , helper_1.py и helper_2.py . В main_script.py вызывается helper_1.py , который в свою очередь запускает helper_2.py . Задача — узнать путь и имя файла helper_2.py , прямо из кода этого файла, без передачи этой информации в качестве аргументов из helper_1.py . Существует простой способ получить эту информацию, используя встроенный модуль Python, под названием os .
import os current_file = os.path.realpath(__file__) current_directory = os.path.dirname(current_file)
https://sky.pro/media/poluchenie-absolyutnogo-puti-k-fajlu-v-python/ "mydir/myfile.txt" , и вы хотите получить полный путь к этому файлу в вашей файловой системе. Python предоставляет модуль os , который содержит множество функций для работы с операционной системой, включая функции для работы с файловыми путями. В частности, функция os.path.abspath() позволяет получить абсолютный путь к файлу. Вот пример использования этой функции:
import os relative_path = "mydir/myfile.txt" absolute_path = os.path.abspath(relative_path) print(absolute_path)
https://sky.pro/media/raznicza-mezhdu-starym-i-novym-stilem-klassov-v-python/ object . Они имеют некоторые ограничения, такие как отсутствие некоторых магических методов и свойств.
Новый стиль классов
Начиная с версии Python 2.2, был введен новый стиль классов. Это основной способ определения классов в Python 3. Пример нового стиля класса:
class NewStyleClass(object): def __init__(self, name): self.name = name
Новые классы создаются путем явного наследования от object или от другого нового стиля класса. Они имеют множество преимуществ по сравнению со старым стилем, включая поддержку множественного наследования, дескрипторов, свойств, магических методов и декораторов.
Когда использовать старый и новый стиль классов?
https://sky.pro/media/odinarnye-i-dvojnye-kavychki-v-python/ 'Hello, World!' и "Hello, World!" являются одним и тем же. Однако, несмотря на то, что они взаимозаменяемы, могут возникнуть ситуации, в которых удобнее использовать один тип кавычек над другим. Например, когда строка содержит кавычки или апостроф. Сравните два следующих примера:
quote1 = "He said, 'Hello, World!'" quote2 = 'He said, "Hello, World!"'
https://sky.pro/media/zamena-operatora-switch-v-python/ switch . Этот оператор позволяет реализовать выбор одного из множества возможных вариантов исполнения программы в зависимости от значения переменной. Оператор switch может выглядеть так:
switch (day)
Однако в Python оператора switch нет. Это может вызвать затруднения у тех, кто переходит на Python с других языков программирования. Тем не менее, в Python есть несколько альтернатив switch , которые могут быть использованы в зависимости от конкретной ситуации.
Использование условного оператора if-elif-else
Самый прямой и очевидный способ реализовать функционал switch в Python — использовать условный оператор if-elif-else . Этот оператор позволяет проверить несколько условий и выполнить различные действия в зависимости от результата.
day = "Monday" if day == "Monday": work() elif day == "Saturday" or day == "Sunday": rest() else: work()
Использование словарей
Еще один способ реализации функции switch в Python — использование словарей. В Python словари — это структуры данных, которые хранят пары «ключ-значение». Ключи в словаре уникальны, и каждому ключу соответствует некоторое значение.
def work(): pass def rest(): pass switch = < "Monday": work, "Saturday": rest, "Sunday": rest >day = "Monday" action = switch.get(day, work) action()
https://sky.pro/media/raspolozhenie-modulej-testirovaniya-v-python/ module1 в пакете package1 , вы можете создать файл теста test_module1.py внутри package1 . Это позволяет легко импортировать module1 внутри файла теста. Важно помнить, что все файлы тестов должны начинаться с префикса test_ , чтобы инструменты автоматического тестирования, такие как pytest , могли легко обнаруживать и запускать эти тесты. Таким образом, структура вашего проекта может выглядеть следующим образом:
myproject/ package1/ __init__.py module1.py test_module1.py package2/ __init__.py module2.py test_module2.py
https://sky.pro/media/izvlechenie-elementa-iz-mnozhestva-bez-ego-udaleniya-v-python/ pop() , который одновременно удаляет элемент из множества. Возьмем для примера множество целых чисел:
s = set([1, 2, 3])
Чтобы получить элемент из множества, можно использовать метод pop() , однако он удалит элемент из множества:
elem = s.pop()
Если же нужно оставить элемент в множестве, то после применения метода pop() можно добавить элемент обратно в множество с помощью метода add() . Это выглядит так:
elem = s.pop() s.add(elem)
Этот подход работает, но он не является особенно эффективным, поскольку требует двух операций вместо одной. Существует более простой и эффективный способ получить элемент из множества без его удаления. Для этого достаточно преобразовать множество в список, а затем обратиться к первому элементу списка:
elem = list(s)[0]
https://sky.pro/media/privatnost-metodov-v-python/ __myPrivateMethod() . Однако, в действительности, эти «приватные» методы и переменные не являются полностью приватными в традиционном понимании этого термина. В качестве примера возьмем следующий код:
class MyClass: def myPublicMethod(self): print('public method') def __myPrivateMethod(self): print('this is private!!') obj = MyClass() obj.myPublicMethod()
Здесь мы создали класс с публичным методом myPublicMethod() и приватным методом __myPrivateMethod() . При попытке вызвать приватный метод, мы столкнемся с ошибкой AttributeError , которая говорит о том, что у экземпляра класса MyClass нет атрибута __myPrivateMethod .
obj.__myPrivateMethod()
На первый взгляд, все выглядит ожидаемо — мы не можем вызвать приватный метод. Однако, при использовании функции dir() на объекте, мы обнаружим новый «магический» метод, который Python создает автоматически для всех ваших «приватных» методов.
dir(obj)
Название этого нового метода всегда начинается с подчеркивания, за которым следует имя класса и имя метода.
obj._MyClass__myPrivateMethod()
https://sky.pro/media/staticheskie-peremennye-i-metody-v-klassah-python/ Animal , который содержит атрибут name и метод say_hello() . При создании каждого нового объекта этого класса, name и say_hello() будут принадлежать конкретному объекту.
class Animal: def __init__(self, name): self.name = name def say_hello(self): print(f"Hello, I'm ")
Теперь, допустим, нужно добавить атрибут, который будет относиться ко всему классу Animal , а не к отдельному его объекту. Пусть это будет количество созданных объектов класса — count . Как это сделать?
class Animal: count = 0 def __init__(self, name): self.name = name Animal.count += 1 def say_hello(self): print(f"Hello, I'm ")
В данном случае count — это статическая переменная класса Animal . Она принадлежит самому классу, а не объекту. Таким образом, при создании каждого нового объекта Animal , count увеличивается на 1. Теперь перейдем к статическим методам. Статический метод в классе — это метод, который принадлежит всему классу, а не отдельному объекту. Он не может модифицировать состояние объекта, но может работать с данными класса. Для создания статического метода используется декоратор @staticmethod .
class Animal: count = 0 def __init__(self, name): self.name = name Animal.count += 1 def say_hello(self): print(f"Hello, I'm ") @staticmethod def get_count(): return Animal.count
https://sky.pro/media/dinamicheskij-import-modulej-v-python-po-polnomu-puti/ my_module.py , который расположен по пути /home/user/projects/my_project/my_module.py . Мы хотим импортировать этот модуль в наш скрипт main.py , который находится в другом месте файловой системы. При попытке обычного импорта import my_module интерпретатор Python в данном случае не сможет найти модуль, так как его директория не находится в системном пути Python.
Решение проблемы
Для решения данной проблемы можно воспользоваться модулем importlib.util и его функцией spec_from_file_location для создания спецификации модуля, а затем функцией module_from_spec для создания самого модуля.
import importlib.util def import_module_from_path(path): spec = importlib.util.spec_from_file_location("my_module", path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module
В данной функции на вход передается полный путь до файла с модулем, а на выходе получается объект модуля, который можно использовать в дальнейшем.
path_to_module = "/home/user/projects/my_project/my_module.py" my_module = import_module_from_path(path_to_module) # теперь можно использовать функции и классы из my_module result = my_module.my_function()
https://sky.pro/media/rabota-so-vhodnymi-dannymi-v-python/ input() . Она позволяет ввести данные с клавиатуры и возвращает их в виде строки. Например:
text = input() print(text)
В этом случае введенные пользователем данные будут сохранены в переменной text , а затем выведены на экран. Если нужно вывести какой-то текст перед вводом данных, можно передать этот текст в функцию input() :
name = input('Введите ваше имя: ') print('Привет,', name)
Здесь перед вводом данных будет выведено сообщение «Введите ваше имя: «, а затем введенное имя будет использовано в приветствии.
Аргументы командной строки
Другой распространенной задачей является передача параметров скрипту при его запуске из командной строки. Для этого в Python используется модуль sys . Аргументы командной строки доступны в виде списка sys.argv . Нулевой элемент этого списка — это имя выполняемого скрипта. Остальные элементы — это аргументы, переданные скрипту.
import sys print('Имя скрипта:', sys.argv[0]) print('Первый аргумент:', sys.argv[1]) print('Второй аргумент:', sys.argv[2])
Если запустить этот скрипт из командной строки с двумя аргументами, то он выведет их на экран:
$ python3 script.py arg1 arg2 Имя скрипта: script.py Первый аргумент: arg1 Второй аргумент: arg2
https://sky.pro/media/sortirovka-spiska-slovarej-v-python-po-znacheniyu-klyucha/ sorted() , которая может принимать в качестве аргумента ключ для сортировки. Этот ключ может быть функцией, которая будет применена к каждому элементу перед сравнением. В случае со списком словарей, можно использовать лямбда-функцию, которая возвращает значение ключа «имя» для каждого словаря.
sorted_list = sorted(list_dicts, key=lambda x: x['имя'])
В результате выполнения этого кода sorted_list будет содержать список словарей, отсортированный по ключу «имя»:
Если требуется отсортировать список в обратном порядке, можно добавить аргумент reverse=True :
sorted_list = sorted(list_dicts, key=lambda x: x['имя'], reverse=True)
https://sky.pro/media/kak-zavershit-rabotu-skripta-v-python/ exit() из модуля sys . Эта функция принимает один необязательный аргумент — статус завершения, который по умолчанию равен нулю. Если вызвать функцию без аргумента или с аргументом 0 , то это будет воспринято как успешное завершение работы скрипта. Любое другое значение аргумента будет воспринято как код ошибки. Вот как можно доработать пример выше с использованием exit() :
import os import sys if not os.path.exists('data.txt'): print('Файл данных не найден') sys.exit(1)
https://sky.pro/media/raznicza-mezhdu-funkcziyami-range-i-xrange-v-python-2-x/ range и xrange . Они играют важную роль в циклах и итерациях. Обе функции принимают три аргумента: начальное значение, конечное значение и шаг. Например:
range(0, 10, 2) xrange(0, 10, 2)
Оба этих вызова вернут последовательность [0, 2, 4, 6, 8]. Однако важно отметить, что range и xrange работают по-разному. Функция range возвращает список, который содержит всю последовательность чисел сразу. Таким образом, если диапазон, который вы создаете, достаточно большой, range может занять много памяти. С другой стороны, xrange создает объект, который генерирует каждое число в последовательности по требованию, т.е. по мере итерации. Это называется «ленивой» генерацией, и она более эффективна по памяти, особенно для больших диапазонов. Вот пример:
for i in range(0, 10000000): pass
for i in xrange(0, 10000000): pass
https://sky.pro/media/vypolnenie-vneshnih-komand-v-python/ os . Модуль os в Python предоставляет функции для взаимодействия с операционной системой. Функция os.system() позволяет выполнять системные команды.
import os os.system('ls')
В этом примере команда ls будет выполнена, и результат ее выполнения будет выведен на экран. Однако, этот способ имеет свои недостатки. В частности, он не позволяет обрабатывать вывод команды как строку в Python. Для этого лучше использовать модуль subprocess . Модуль subprocess предназначен для запуска новых приложений или программ, которые можно контролировать из своего кода. Этот модуль позволяет создавать дополнительные процессы, соединяться с их вводом/выводом/ошибками и получать их коды возврата.
import subprocess output = subprocess.run(['ls', '-l'], capture_output=True, text=True) print(output.stdout)
https://sky.pro/media/skrytye-vozmozhnosti-python/ if x > 0 and x < 10 , можно написать if 0 < x < 10 .
Декораторы
Декораторы — это специальные функции, которые «оборачивают» другую функцию и модифицируют ее поведение. Они могут быть использованы для добавления функциональности к функциям или методам, например, для логирования, кэширования, тайминга и т.д.
Генераторы
Генераторы — это специальный тип итераторов, который позволяет вам создавать итераторы с помощью простого и компактного синтаксиса. Они могут быть использованы для создания больших последовательностей данных, не загружая все данные в память сразу.
Использование __missing__
Метод __missing__ можно определить в пользовательском словаре, и он будет вызываться, когда попытка доступа к ключу, который отсутствует в словаре. Это может быть полезно для создания словарей с автоматическим заполнением.
Форматирование строк
Python предоставляет множество способов форматирования строк, включая использование метода .format() и f-строки. Они позволяют вставлять значения переменных прямо в строку, делая код более читаемым и удобным для работы.
Использование with
Оператор with используется для оборачивания выполнения блока кода с помощью методов контекстного менеджера. Это особенно полезно при работе с ресурсами, которые требуют правильного закрытия, например, файлы или сетевые соединения. Использование with гарантирует, что ресурс будет корректно закрыт, даже если в процессе выполнения блока кода произойдет ошибка.
Вывод
https://sky.pro/media/kak-proverit-sushhestvovanie-fajla-bez-ispolzovaniya-isklyuchenij-v-python/ try/except . Однако в некоторых случаях его использование может быть излишним или неудобным, и возникает потребность в поиске других способов решения этой задачи. В Python существуют встроенные функции, которые позволяют проверить существование файла без использования исключений. Например, функция os.path.isfile() из модуля os.path возвращает True , если указанный файл существует, и False в противном случае. Пример использования этой функции:
import os if os.path.isfile("myfile.txt"): print("Файл существует") else: print("Файл не существует")
Еще один способ — использование функции os.path.exists() , которая также возвращает True , если файл или директория с указанным именем существуют, и False в противном случае:
import os if os.path.exists("myfile.txt"): print("Файл существует") else: print("Файл не существует")
https://sky.pro/media/metaklassy-v-python/ MyClass является экземпляром класса type , который является встроенным метаклассом в Python. Иными словами, type создаёт MyClass , а MyClass создаёт obj . Метаклассы используются для создания классов в Python, причем они описывают, как класс должен вести себя. В большинстве случаев не требуется изменять поведение метакласса, но иногда это может быть полезно. Например, для изменения способа создания классов, или для внесения изменений в созданные классы. Создание собственного метакласса в Python требует определенного уровня понимания принципов работы классов в Python, и важно помнить, что использование метаклассов может увеличить сложность кода и сделать его более трудным для понимания и поддержки. Основное применение метаклассов в Python — это создание API. Как пример, Django ORM использует метаклассы для создания классов, которые представляют данные в базе данных. Вот пример создания метакласса в Python:
class Meta(type): def __new__(cls, name, bases, attrs): print('Creating a class named %s' % name) return super().__new__(cls, name, bases, attrs) class MyClass(metaclass=Meta): pass
https://sky.pro/media/standartnyj-sposob-dobavleniya-n-sekund-k-datetime-time-v-python/ datetime.time не поддерживается. Прямое сложение datetime.time(11, 34, 59) + 3 вызовет ошибку TypeError: unsupported operand type(s) for +: 'datetime.time' and 'int' . Также не подойдет и использование datetime.timedelta , так как это тоже вызовет ошибку TypeError: unsupported operand type(s) for +: 'datetime.time' and 'datetime.timedelta' . Нельзя также сложить два объекта datetime.time , это тоже вызовет ошибку. Необходимо преобразовать время в секунды, добавить к нему нужное количество секунд и затем преобразовать обратно в формат времени. Для этого можно использовать следующую функцию:
import datetime def add_secs_to_time(timeval, secs_to_add): secs = timeval.hour * 3600 + timeval.minute * 60 + timeval.second secs += secs_to_add return datetime.time(secs // 3600, (secs % 3600) // 60, secs % 60)
https://sky.pro/media/otklyuchenie-buferizaczii-vyvoda-v-python/ sys.stdout . Однако существуют способы ее отключить. Далее будут рассмотрены некоторые из них.
Использование параметра -u
Один из способов отключить буферизацию вывода — использовать параметр -u в командной строке при запуске программы. К примеру, если у вас есть скрипт с именем script.py , вы можете запустить его с отключенной буферизацией следующим образом:
python -u script.py
Обертка sys.stdout в объект, который сбрасывает буфер после каждой записи
Это еще один способ отключить буферизацию вывода. В этом случае sys.stdout оборачивается в объект, который сбрасывает буфер после каждой записи.
class Unbuffered(object): def __init__(self, stream): self.stream = stream def write(self, data): self.stream.write(data) self.stream.flush() def writelines(self, datas): self.stream.writelines(datas) self.stream.flush() def __getattr__(self, attr): return getattr(self.stream, attr) import sys sys.stdout = Unbuffered(sys.stdout)
Установка переменной окружения PYTHONUNBUFFERED
Переменная окружения PYTHONUNBUFFERED может быть установлена для отключения буферизации вывода. Это можно сделать следующим образом:
export PYTHONUNBUFFERED=TRUE
Использование функции os.fdopen()
Функция os.fdopen() может быть использована для открытия файла дескриптора и его последующей ассоциации с новым файловым объектом без буферизации.
import os import sys sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
В приведенных выше способах отключение буферизации вывода происходит глобально и влияет на все операции вывода. Тем не менее, в некоторых случаях может быть нужно сбросить буфер после конкретной операции вывода. В этом случае можно использовать функцию flush() :
print('Hello, World!', flush=True)
https://sky.pro/media/generacziya-vseh-perestanovok-spiska-v-python/ itertools . В этом модуле есть функция permutations() , которая принимает два аргумента: итерируемый объект и длину перестановок. Если длина перестановок не указана, то по умолчанию она равна длине итерируемого объекта. Пример использования функции permutations() :
import itertools my_list = [1, 2, 3] permutations = list(itertools.permutations(my_list)) for p in permutations: print(p)
https://sky.pro/media/kak-najti-raspolozhenie-direktorii-site-packages-v-python/ site-packages . Эта директория является основной для установки сторонних библиотек и расширений. Например, при установке библиотеки numpy через pip , файлы этой библиотеки будут располагаться именно в директории site-packages .
Поиск директории site-packages
Для того чтобы найти расположение директории site-packages , можно воспользоваться стандартным модулем site , который входит в состав Python. Этот модуль содержит функции для работы с определенными путями, включая путь до директории site-packages .
import site print(site.getsitepackages())
Этот код выведет список путей до директории site-packages . В большинстве случаев, этот список будет содержать один путь, но в некоторых случаях, например, при наличии виртуального окружения, может содержать несколько путей.
Работа с директорией site-packages
https://sky.pro/media/rabota-s-fajlami-i-direktoriyami-v-python/ os.listdir() . Эта функция возвращает список всех файлов и директорий в указанной директории.
import os # Получить список всех файлов и директорий в текущем каталоге files_and_directories = os.listdir('.')
Однако эта функция не рекурсивна, то есть она не возвращает файлы и директории внутри поддиректорий. Для рекурсивного обхода можно использовать функцию os.walk() .
import os # Рекурсивный обход всех файлов и директорий в текущем каталоге for dirpath, dirnames, filenames in os.walk('.'): print(f'Найден каталог: ') for filename in filenames: print(f'Найден файл: ')
https://sky.pro/media/kak-skopirovat-fajly-v-python/ source.txt , который находится в директории dir1 . Требуется скопировать этот файл в директорию dir2 под именем destination.txt .
Решение
В Python для копирования файлов можно использовать модуль shutil . Ниже представлен пример кода, который копирует файл source.txt из директории dir1 в директорию dir2 под именем destination.txt .
import shutil shutil.copy2('dir1/source.txt', 'dir2/destination.txt')
В этом примере используется функция copy2() , которая, помимо копирования содержимого файла, также пытается скопировать всю метаинформацию о файле. Если требуется просто скопировать содержимое файла без метаинформации, можно использовать функцию copy() :
import shutil shutil.copy('dir1/source.txt', 'dir2/destination.txt')
Обратите внимание, что если файл destination.txt уже существует, он будет перезаписан.
Заключение
https://sky.pro/media/rabota-s-datami-v-formate-iso-8601-v-python/ datetime в Python. Такие строки могут выглядеть, например, так: «2021-09-15T15:30:00Z». Однако использование стандартной функции strptime из модуля datetime может быть неудобным и затратным по времени, особенно для новичков. Эта функция требует точного знания формата даты и времени и его ручного ввода. Более удобным и быстрым способом преобразования строк в формате ISO 8601 в объекты datetime является использование библиотеки dateutil .
Установка библиотеки dateutil
Для установки библиотеки dateutil достаточно ввести следующую команду в консоли:
pip install python-dateutil
Использование библиотеки dateutil
После установки библиотеки для преобразования строки в формате ISO 8601 в объект datetime достаточно воспользоваться функцией parse из модуля dateutil.parser . Например:
from dateutil.parser import parse date_string = "2021-09-15T15:30:00Z" date = parse(date_string) print(date)
Вывод этого кода будет следующим:
2021-09-15 15:30:00+00:00
https://sky.pro/media/vyzov-funkczij-iz-c-c-v-python/ hello() из этой библиотеки в вашем коде на Python. Как это можно сделать?
1. Использование ctypes
Один из способов — использовать модуль ctypes , который является частью стандартной библиотеки Python. ctypes предоставляет возможность создавать и манипулировать C-совместимыми типами данных в Python, и также вызывать функции в динамических библиотеках/разделяемых библиотеках. Вот как можно использовать ctypes для вызова функции hello() :
from ctypes import cdll # Загрузка библиотеки mylib = cdll.LoadLibrary('./mylib.so') # Вызов функции mylib.hello()
Обратите внимание, что вам потребуется скомпилировать ваш код C++ в разделяемую библиотеку ( mylib.so в Unix-системах или mylib.dll в Windows) перед тем, как вы сможете загрузить его с помощью ctypes .
2. Использование SWIG
- Напишите интерфейсный файл для SWIG:
// mylib.i %module mylib % < extern void hello(); %>extern void hello();
- Затем вы можете сгенерировать обертку с помощью SWIG:
swig -python mylib.i
- Это создаст два файла: mylib_wrap.c и mylib.py . Вы можете скомпилировать mylib_wrap.c в разделяемую библиотеку, а затем использовать mylib.py в вашем коде на Python:
import mylib mylib.hello()
Это простые способы вызова C/C++ кода из Python, но они могут быть недостаточно гибкими для сложных случаев использования. Для более продвинутых сценариев могут потребоваться другие инструменты, такие как Cython или Boost.Python.
Fri, 07 Jul 2023 14:42:29 +0000
Fri, 07 Jul 2023 14:42:07 +0000
Один из самых простых способов — это использовать встроенную функцию dir() . Эта функция возвращает список имен, определенных в модуле. Вот пример:
import math print(dir(math))
В результате будет выведен список всех функций и переменных, определенных в модуле math .
Однако, эта функция возвращает все имена, включая служебные атрибуты и методы классов. Если нужно получить только имена функций, можно использовать следующий код:
import math import types functions = [f for f in dir(math) if isinstance(getattr(math, f), types.FunctionType)] print(functions)
В этом примере используется функция getattr() , которая возвращает значение указанного атрибута объекта, и функция isinstance() , которая проверяет, является ли объект экземпляром указанного типа.
Использование модуля inspect
Еще один способ — это использовать модуль inspect . Этот модуль содержит функции для получения информации о живых объектах, таких как модули, классы, функции и др.
import math import inspect functions = inspect.getmembers(math, inspect.isfunction) print(functions)
В этом примере функция getmembers() возвращает все члены объекта, которые удовлетворяют предикату inspect.isfunction , то есть являются функциями.
Оба этих способа позволяют получить список всех функций в модуле Python.
Fri, 07 Jul 2023 14:42:04 +0000
https://sky.pro/media/testirovanie-na-isklyucheniya-v-python/ unittest в Python предоставляет инструмент для тестирования исключений.
import unittest class TestSqrt(unittest.TestCase): def test_negative_number(self): with self.assertRaises(ValueError): sqrt(-1)
В этом примере тестовый метод test_negative_number проверяет, что функция sqrt вызывает исключение ValueError , когда вызывается с отрицательным числом. Если исключение вызывается, тест проходит, если исключение не вызывается, тест не проходит.
Использование self.assertRaises является стандартным способом тестирования вызова исключений в Python. Этот метод принимает тип исключения и вызываемую функцию, и проверяет, что функция вызывает исключение указанного типа.
Fri, 07 Jul 2023 14:42:04 +0000
