Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Программирование на языке Python. Учебное пособие.pdf
X
- •ПРЕДИСЛОВИЕ
- •I. ВВЕДЕНИЕ В ПРОГРАММИРОВАНИЕ
- •1. Начало работы
- •2. Основные понятия
- •Ввод и вывод данных
- •Логические операторы
- •Условные инструкции
- •Инструкции
- •Типы данных
- •Константы и переменные
- •Арифметические операторы
- •Операторы сравнения
- •3. Функции
- •Определение функций
- •Встроенные функции
- •Многократное использование функций
- •Типы параметров функции
- •Область видимости
- •Обработка исключений
- •Строки документации
- •4. Контейнеры
- •Методы
- •Списки
- •Кортежи
- •Словари
- •Контейнеры внутри контейнеров
- •5. Операции со строками
- •6. Циклы
- •Цикл for
- •Цикл while
- •Операторы break и continue
- •Функция Enumerate
- •Вложенные циклы
- •Настройка функции print()
- •7. Модули
- •8. Файлы
- •Запись в файлы
- •Использование конструкции with
- •Чтение из файлов
- •CSV-файлы
- •II. ВВЕДЕНИЕ В ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ (ООП)
- •9. Парадигмы программирования
- •Состояние
- •Процедурное программирование
- •Функциональное программирование
- •Объектно-ориентированное программирование
- •10. Базовые понятия ООП
- •Наследование
- •Полиморфизм
- •Абстракция
- •Инкапсуляция
- •Композиция
- •11. Дополнительные понятия ООП
- •Как работают переменные
- •Ключевое слово is
- •Константа None
- •Классы – это объекты
- •Переменные класса и переменные экземпляра класса
- •Приватные переменные
- •Методы переопределения
- •Функция super
- •Переопределение встроенных методов
- •12. Создание игр
- •Карты
- •Колода
- •Игрок
- •Игра
- •Игра «War»
- •13. Bash
- •Запуск Bash
- •Использование Bash
- •Относительные и абсолютные пути
- •Навигация
- •Флаги
- •Редактор vim
- •Команда touch
- •Просмотр файлов командой less
- •Пользователи
- •Разрешения
- •Программы Bash
- •Скрытые файлы
- •Переменные окружения
- •Переменная окружения $PATH
- •Вертикальная черта (пайп)
- •Команда cat
- •Последние команды
- •Быстрые перемещения
- •Экранирование (escaping)
- •Регулярные выражения в Python
- •15. Менеджеры пакетов
- •Вкладка «Завершение»
- •Функция wildcard (подстановочный знак)
- •Другие инструменты
- •14. Регулярные выражения
- •Настройка
- •Простое совпадение
- •Игнорирование регистра
- •Вывод только конкретных совпадений слов
- •Совпадение в начале и в конце
- •Поиск совпадений с несколькими символами
- •Повторение
- •Диапазон
- •16. Управление версиями
- •Репозитории
- •Помещение и извлечение данных
- •Пример помещения данных
- •Пример извлечения данных
- •Откат версий
- •Команда git diff
- •Другой запрос на включение
- •IV. ВВЕДЕНИЕ В ИНФОРМАТИКУ
- •17. Структуры данных и алгоритмы
- •Что такое алгоритмы и структуры данных?
- •Нотация Big-O
- •Оператор деления по модулю
- •Сортировка методом пузырька
- •Поисковый алгоритм
- •Рекурсия
- •Абстрактные типы данных
- •Узлы
- •Стеки
- •Связанные списки
- •Массивы
- •Бинарные деревья
- •Поиск в ширину и в глубину
- •Хэш-таблицы
- •ЗАКЛЮЧЕНИЕ
- •БИБЛИОГРАФИЧЕСКИЙ СПИСОК

курсией, но ключом к пониманию рекурсии является глубокое понимание того,
как работает стек.
Кроме того, стеки используются для реверсирования. Все, что кладется
в стопку, выходит в обратном порядке, когда ее снимают. Например, предположим, нужно перевернуть строку. Ожно было бы перевернуть строку, сначала
поместив ее в стек, а затем извлекая ее, как в следующем фрагменте:
my_string = “Hello”
stack = s()
for c in my_string:
stack.push(c)
new_string = “”
for c in stack:
new_string += c
print new_string
>> olleH
В этом примере просмотрели каждый символ в слове «Hello» и поместили
его в стек. Затем перебрали стек и взяли из него все, что только что поместили
в него, и сохранили порядок в переменной new_string. К тому времени, когда дойдем до последней строки, слово будет перевернуто и программа напечатает «olleH».
Нужно написать программу, которая проверяет строку на наличие сбалан-
сированных скобок. Так, например, «(Hello)» пройдет, но «(Hello» не пройдет;
«()()()» пройдет, но «()(» не пройдет. Сначала это выглядит просто, пока не получим что-то вроде этого "((()((()((()((((()))))".
Как отслеживать все скобки? Ключ к решению этой проблемы заключается
в использовании стека. Каждый раз, когда мы сталкиваемся с открытой скобкой, помещаем ее в стек. Если сталкиваемся с закрытой скобкой, вытаскиваем
из стека открытую скобку.
def balanced_paren(expression):
stack = []
for c in expression:
if c == ‘(‘:
161

stack.append(c)
elif c == ‘)’:
if len (stack) < 1:
return False
stack.pop()
if len (stack) == 0:
return True
return False
Если скобки сбалансированы, стек будет пуст после цикла, и можно вернуть «True»; если скобки не сбалансированы, а попытаться извлечь данные из
пустого стека, то это вызовет ошибку. Вот почему, когда сталкиваемся с закрытой скобкой, должны сначала убедиться, что стек не пуст, прежде чем извлечь
ее из стека. Если сталкиваемся с закрытой скобкой, а стек пуст, и знаем, что
скобка не сбалансирована, можно вернуть «False». Если в конце цикла в стеке
еще остались открытые скобки, также можно вернуть «False».
Связанные списки
Связанный список состоит из последовательности узлов, каждый из которых указывает на следующий узел в списке. Вот отличная метафора для размышлений о связных списках. Представьте, что вы в мафии и хотите отдавать
приказы так, чтобы никто не знал, кто вы такой. Можно создать структуру,
в которой анонимно отдаете приказ следующему человеку в цепочке подчинения. Вы знаете, кто они, но они не знают вас, а знают только следующего человека, которому они должны отдать приказ. Человек, которому они отдают следующий приказ, не знает их, а знает только следующего человека, который получит приказ. Эта цепочка информации и есть односвязный список. В односвязном списке все узлы в списке знают только о следующем узле. Они не отслеживают узел позади себя. Можно добраться до каждой части данных в связанном списке, начав с начала списка и перемещаясь один за другим к каждому
следующему узлу. Двусвязный список – это то же самое, за исключением того,
что каждый узел отслеживает узел, стоящий за ним, в дополнение к отслеживанию следующего узла (т.е. двусвязный список отслеживает как следующий
элемент, так и предыдущий). Связанный список также может быть упорядочен-
162

ным или неупорядоченным. Далее рассмотрим неупорядоченный односвязный
и двусвязный список:
class Node:
“““Class representing one node in a linked list.”””
def __init__(self, data):
self.data = Node(data)
self.next = None
class LinkedList:
“““Class representing a linked list data structure”””
def __init__(self, head):
self.head = head
def add(self, data):
“““Add a new node to the linked list.”””
previous_head = self.head
self.head = Node(data)
self.head.next = previous_head
Класс связанных списков прост. Он хранит заголовок связанного списка и
имеет метод под названием «add» для добавления нового узла в список. Поскольку список неупорядочен, нам все равно, куда мы поместим следующий
узел, поэтому проще всего поместить его в начало списка. Метод add сохраняет
текущий заголовок в переменной previous_head, чтобы не потерять его из виду,
создает новый узел с переданными данными и устанавливает новый узел в качестве заголовка списка. Узел, который раньше был головным, затем устанавливается в качестве следующего узла после нового головного. Теперь можно
создать связанный список и добавить в него данные:
linked_list = LinkedList()
linked_list.add(1)
linked_list.add(2)
linked_list.add(3)
Чтобы получить данные из связанного списка, начинайте с головы и посещайте каждый узел, пока не наткнетесь на следующий узел, который не равен нулю.
163

node = linked_list.head
while node:
print node.data
node = node.next
>> 3
>> 2
>> 1
Можно изменить односвязный список на двусвязный, продолжая изменять
класс узла, чтобы отслеживать узел, стоящий за ним.
class Node:
“““Class representing one node in a linked list.”””
def __init__ (self, data):
self.data = data
self.next = None
self.previous = None
class LinkedList :
“““Class representing a linked list data structure”””
def __init__ (self, data):
self.head = Node(data)
def add(self, data):
“““Add a new node to the linked list.”””
previous_head = self.head
self.head = Node(data)
previous_head.previous = self.head
self.head.next = previous_head
Новый связанный список такой же, как и предыдущий, за исключением того, что теперь каждый узел знает узел перед ним и позади него.
Массивы
Массив – это реализация абстрактного типа данных списка. Каждый
фрагмент данных в массиве должен быть одного типа. Так, например, есть массив, состоящий из строк или целых чисел, но не может быть массива, состоящего из обоих типов данных. Встроенная в Python структура данных списка реализована внутри как массив указателей.
164

Бинарные деревья
Дерево – это еще одна структура данных, которая получила свое название из-
за того, что выглядит как дерево. Отличным примером древовидной структуры
данных является файловая система на компьютере, реализованная с использованием дерева. Существует множество различных видов деревьев, таких как красные
и черные деревья, деревья AVL и бинарные деревья. Построим бинарное дерево.
Двоичное дерево состоит из узлов, содержащих данные. У каждого узла
может быть левый дочерний элемент и правый дочерний элемент. Точно так
же, как связанный список указывает на следующий элемент в списке, узел может указывать на два других узла, называемых левым и правым дочерними
элементами. Подобно двусвязному списку, левый и правый потомки отслеживают своего родителя. Вот пример бинарного дерева, реализованного на Python:
class Node:
“““Binary tree node.”””
def __init__(self, value):
self.value = value
self.left_child = None
self.right_child = None
class BinaryTree:
“““This class represents a binary tree
data structure.”””
def __init__(self, root):
”””
:param root: Binary tree node.
”””
self.root = root
Бинарное дерево просто состоит из корня.
Поиск в ширину и в глубину
Если нужно посетить каждый узел бинарного дерева, можно использовать
два алгоритма поиска: поиск в ширину и поиск в глубину. Если представить
себе дерево, состоящее из строк и столбцов, то при поиске в ширину нужно посетить каждую строку одну за другой, тогда как при поиске в глубину – посе-
165

тить каждый столбец один за другим. Используем бинарное дерево из предыдущего примера, чтобы создать дерево для обхода:
tree = BinaryTree(“a”)
tree.left = TreeNode(“b”)
tree.right = TreeNode(“c”)
tree.left.left = TreeNode(“d”)
tree.right.right = TreeNode(“e”)
Теперь можно написать функцию, которая принимает дерево в качестве пара-
метра и выполняет поиск в ширину, выводя значение каждого посещаемого узла:
def breadth_first (tree):
””” Breadth first search of binary tree print out each
node.
:param root: BinaryTree
”””
current_level = [root]
next_level = []
while current_level:
for node in current_level:
print node.val
if node.left_child:
next_level.append(node.left_child)
if node.right_child:
next_level.append(node.right_child)
current_level = next_level
next_level = []
Используем список current_level для отслеживания всех узлов на уровне
дерева, в котором сейчас находимся. Когда текущего уровня больше нет, алгоритм останавливается. Дерево выглядит так:
166

«current_level» начинается с [a], перемещается в [b, c], переходит
и могут хранить ключи и значения
в [d, e] и, наконец, становится пустым списком [], на этом алгоритм завершается. Можно сделать это, отслеживая следующий уровень узлов в списке
next_level.
Перед циклом while добавляем корень дерева в current_level, затем
сообщаем циклу, чтобы он продолжался, пока current_level не будет пуст.
В цикле while перебираем каждый узел в current_level, выводя его. За-
тем проверяем, есть ли у узла дочерние элементы. Если это так, добавляем эти
дочерние элементы в список next_level. В конце цикла for переключаем
два списка так, чтобы список current_level устанавливался на список
next_level, а список next_level становился пустым. Это позволяет переходить с одного уровня на другой. Наконец, когда достигнем последнего уровня, у узлов не будет дочерних узлов, next_level будет пустым,
current_level будет установлен на next_level, и поскольку он пуст, алгоритм остановится.
Поиск в глубину выполняет поиск по дереву вертикально, а не пересекает
его по горизонтали. В дереве из предыдущего примера будет произведен поиск
“a”, “b”, “d”, “e”, “c”. Таким образом можно реализовать поиск в глубину с помощью рекурсии.
Хэш-таблицы
В главе «Контейнеры» рассматривался встроенный в Python тип данных
словаря. Словари полезны, потому что он
и невероятно быстро получать и устанавливать данные. Напомним, словари сопоставляют ключи со значениями. Например, можно добавить ключ
«super_computer» в словарь со значением «Watson» с помощью следую-
щего кода:
my_dictionary = {}
my_dictionary[“super_computer”] = “Watson”
Теперь можно получить ключ «super_computer» с помощью:
print my_dictionary[“super_computer”]
>> Watson
167

Самое удивительное в словарях то, что они могут устанавливать и полу-
чать данные за постоянное время. Неважно, сколько строк данных в словаре.
В нем может быть миллиард строк, но при этом добавление и извлечение значения «super_computer» реализуется за фиксированное время O(1).
Python использует хэш-таблицу для реализации своего словаря. Хеш-
таблица – это структура данных, которая использует список и хэш-функцию
для хранения данных за время O(1). Когда добавляете значение в хеш-таблицу,
она использует хеш-функцию, чтобы получить индекс в списке для хранения
данных. Когда извлекаете данные из хеш-таблицы, она использует ту же хешфункцию для поиска индекса, чтобы получить его из списка. В этом примере
хеш-таблица будет хранить только числа. Хэш-функция вернет результат числа
по модулю одиннадцать. Так, например, хэш-функция для числа 1 вернет единицу, поэтому сохраняется число 1 в индексе один в списке. Хеш-функция для
числа 5 вернет пять, поэтому сохраняется число 5 в индексе пять в списке. Вот
пример хеш-таблицы:
class HashTable :
“““Hash table data structure”””
def __init__ ( self ):
self .list = [ None ] * 11
@ staticmethod
def hash (n):
”””
:param n: int
:return : return index in list to store number.
”””
return n % 11
def set ( self , n , v):
”””
:param n: int
:param v: can be any type.
”””
self .list[ self .hash(n)] = v
def get ( self , n):
”””
168

:param n: int
:return : int value from list
”””
return self .list[ hash (n)]
hash_table = HashTable()
hash_table.set(1, ‘Disrupted’)
hash_table.set(5, ‘HubSpot’)
print(hash_table.get(1))
print(hash_table.get(5))
Это упрощенный пример, в котором явно есть проблемы. Однако цель состоит в том, чтобы проиллюстрировать, как работает хэш-таблица.
Таким образом, хеш-таблица – это структура данных, в которой все элементы хранятся в виде пары ключ-значение,
где
• ключ – уникальное число, которое используется для индексации значений;
• значение – данные, которые с этим ключом связаны.
В хеш-таблице обработка новых индексов производится при помощи ключей, а элементы, связанные с этим ключом, сохраняются в индексе. Этот процесс называется хешированием.
169

ЗАКЛЮЧЕНИЕ
К сожалению, ограниченный объем пособия не позволяет осветить еще несколько важных тем, но автор надеется, что в ближайшее время будет написано
еще одно пособие по Python, где будут рассмотрены не вошедшие в данное пособие вопросы. Для усвоения изложенного материала приведем несколько
практических рекомендаций начинающим программистам.
1. Ознакомьтесь со сборником методических рекомендаций по написанию
кода на Python. На английском языке документ PEP8 доступен по адресу
https://peps.python.org/pep-0008/, а на русском – https://defpython.ru/pep8
2. Используйте мощную интегрированную среду разработки (IDE). В качестве одной из наиболее продвинутых IDE автор рекомендует использовать оболочку PyCharm, разработанную компанией JetBrains. Руководство по использованию PyCharm доступно по адресу
start-guide.html на сайте JetBrains.
3. Пользуйтесь логированием – практикой записи данных во время работы
программного обеспечения. Python поставляется с модулем logging, позволяю-
щим записывать процессы работы в консоль или файл. С модулем logging можно ознакомиться на сайте https://docs.python.org/3/howto/logging.html
4. Активно тестируйте свой код с помощью фреймворка модульного тестирования Python unittest. Прочитать об этом можно на странице
https://docs.python.org/3/library/unittest.html
5. Анализируйте код сообществом программистов на сайте Code Review,
www.jetbrains.com/help/pycharm/2016.1/quick-
.
.
.
расположенном по адресу https://codereview.stackexchange.com/
6. Найти работу для Python-программиста можно в разделе Job Board на
сайте Python www.python.org/jobs
7. Получите начальный опыт работы. Для этого можно начать деятельность в качестве фрилансера либо в области открытого программного обеспечения open source.
8. Обретя опыт программирования, можно записываться на собеседование.
Для этого используйте сайт LinkedIn. Чтобы подготовиться к собеседованию,
можно использовать ресурс LeetCode: https://leetcode.com/
9. Не переставайте совершенствоваться. Рекомендую к прочтению статью
Дэвида Биттоу «НПП: Не переставай программировать» на сайте
.
.
170
.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
