Программирование на языке Python: год 2¶
Игра «Виселица»¶
«Виселица» — игра на угадывание слов. Компьютер загадывает слово. Игроку известны первая и последняя буквы, а также количество букв. Игрок угадывает слово по буквам. Если буква есть в слове, то открываются все ее вхождения. Если буквы нет, то к виселице дорисовывается часть человека. Если слово открыто полностью, игрок побеждает. Если набрано максимальное количество ошибок и человек на виселице нарисован полностью, игрок проигрывает.
Пример интерфейса игры:
+---+
| |
O |
/|\ |
/ \ |
|
=========
sh..p
.bc.efg.ijk.m.o.qrstuvwxy.
You lose
В примере мы видим последнюю стадию — игрок не смог угадать слово и проиграл. Перед нами:
Полностью нарисованный человек.
Слово, в котором часть букв закрыто и отображается точками.
Алфавит, в котором вместо использованных букв отображаются точки.
Перед началом работы нужно ответить на вопросы:
Какие структуры данных будут полезны при реализации игры?
Какие подзадачи можно выделить, глядя на пример интерфейса?
Посмотрите на заготовку кода. Расскажите, какие объекты уже созданы. Назовите их типы.
После реализации основной функциональности проверьте, как приложение реагирует на некорректный ввод.
Примеры некорректного ввода:
Пользователь вводит букву в верхнем регистре.
Пользователь вводит не букву (цифру или спецсимвол).
Пользователь многократно вводит уже использованную букву (присутствующую или отсутствующую в слове).
Пользователь вводит несколько букв сразу.
Как вы считаете, какое поведение было бы ожидаемым в данном случае? Доработайте приложение таким образом, чтобы оно было более устойчиво в некорректному вводу.
Доработайте приложение так, чтобы у пользователя была возможность назвать слово целиком.
Заготовка: hangman.py
HANGMANPICS = ['''
+---+
| |
|
|
|
|
=========''', '''
+---+
| |
O |
|
|
|
=========''', '''
+---+
| |
O |
| |
|
|
=========''', '''
+---+
| |
O |
/| |
|
|
=========''', '''
+---+
| |
O |
/|\ |
|
|
=========''', '''
+---+
| |
O |
/|\ |
/ |
|
=========''', '''
+---+
| |
O |
/|\ |
/ \ |
|
=========''']
words = ('ant baboon badger bat bear beaver camel cat clam cobra cougar '
'coyote crow deer dog donkey duck eagle ferret fox frog goat '
'goose hawk lion lizard llama mole monkey moose mouse mule newt '
'otter owl panda parrot pigeon python rabbit ram rat raven '
'rhino salmon seal shark sheep skunk sloth snake spider '
'stork swan tiger toad trout turkey turtle weasel whale wolf '
'wombat zebra ').split()
import random
import string
def main():
pass
if __name__ == '__main__':
main()
Домашнее задание¶
Зарегистрироваться в сервисе https://checkio.org/. Решать задачи в своем темпе. На занятиях сообщать, какие задачи решены.
Обработка текстовых данных¶
Цель этого занятия — разработать несколько приложений, предполагающих обработку текстов. В начале вам предлагается набор функций для повторения, которые помогут вам в решении задачи. Вспомните предложенные функции, опробуйте их в интерпретаторе.
Функции для повторения¶
-
str.
split
()¶ Метод возвращает список слов в строке. Словом считается последовательность непробельных символов:
>>> '1 2 3'.split() ['1', '2', '3']
-
str.
join
(iterable)¶ Возвращает сроку, которая является конкатенацией строк из
iterable
. Строка, у которой вызван метод, становится разделителем.
-
str.
isalpha
()¶ Возвращает
True
, если все символы в строке — буквы.
-
sorted
(iterable)¶ Возвращает новый отсортированный список, состоящий из элементов последовательности
iterable
.
Для примера в методах словарей используется словарь
d = {'Name': 'John', 'Last Name': Connor}
.
-
d.
keys
()¶ Получение списка всех ключей
>>> d.keys() dict_keys(['Name', 'Last Name'])
-
d.
values
()¶ Получение списка всех значений в словаре
>>> d.values() dict_values(['John', 'Connor'])
-
d.
items
()¶ Получение списка пар ключ-значение, представленных в виде кортежей:
>>> d.items() dict_items([('Name', 'John'), ('Last Name', 'Connor')])
-
key in dict
Возвращает True, если ключ есть в словаре:
>>> 'Name' in d: True
-
random.
shuffle
(seq)¶ Перемешивает последовательность
seq
.
-
open
(file_path, mode='r')¶ Открыть файл
file_path
в режимеmode
. Функция возвращаетfile object
. Полная сигнатура функции приведена в официальной документации: open. Режим может принимать значения:'r'
— read, чтение'w'
— write, запись
-
f.
write
(text)¶ Записать строку
text
в файл.
-
f.
close
()¶ Закрыть файл
f
.
Пример работы с файлом:
# Открываем файл для чтения
in_file = open('data.txt', 'r')
# Файл — итерируемый объект. Следовательно, для
# его построчного чтения можно использовать цикл for
for line in in_file:
print(f'Got line: {line}')
in_file.close()
Недостаток такой работы с файлами в том, что приходится помнить о необходимости закрытия файла. Управление некоторыми ресурсами, в том числе файлами, можно автоматизировать с помощью менеджера контекста:
# Открытие файла. Файл будет закрыт при выходе из блока with.
with open('data.txt', 'r') as in_file:
for line in in_file:
print(f'Got line: {line}')
Упражнение¶
В модуле string
определена строка, содержащая символы английского алфавита:
string.ascii_lowercase
. Используя эту строку, создайте словарь со следующей
структурой:
{
'vowels': ['a', 'e', 'i', 'o', 'u'],
'consonants': ['b', 'c', 'd', ...]
}
Задание 1¶
Разработайте приложение для поиска анаграмм в тексте. На входе у приложения текстовый файл, на выходе — списки анаграмм.
Пример входного текста:
Аз есмь строка, живу я, мерой остр.
За семь морей ростка я вижу рост.
Я в мире — сирота.
Я в Риме — Ариост.
Вывод (каждая строка содержит анаграммы):
['Аз', 'За']
['есмь', 'семь']
['строка', 'ростка']
['живу', 'вижу']
['я', 'я', 'Я', 'Я']
['мерой', 'морей']
['остр', 'рост']
['в', 'в']
['мире', 'Риме']
['сирота', 'Ариост']
Задание 2¶
По рзелульаттам илссеовадний одонго анлигйсокго унвиертисета, не иеемт занчнеия, в кокам пряокде рсапожолены бкувы в солве. Галвоне, чотбы преавя и пслоендяя бквуы блыи на мсете. Осатьлыне бкувы мгоут селдовтаь в плоонм бсепордяке, все-рвано ткест чтаитсея без побрелм. Пичрионй эгото ялвятеся то, что мы не чиатем кдаужю бкуву по отдльенотси, а все солво цликеом.
Разработайте приложение, которое принимает на вход текстовый файл с обычным текстом и выводит этот же текст, но с перемешанными буквами внутри слов. Первая и последняя буквы каждого слова должны остаться на своих местах.
Для работы с файлом используйте менеджер контекста.
Примеры входных текстов.
Начало повести Айзека Азимова «Профессия»:
Джордж Плейтен сказал с плохо скрытой тоской в голосе: - Завтра первое мая. Начало Олимпиады!
Он перевернулся на живот и через спинку кровати пристально посмотрел на своего товарища по комнате. Неужели он не чувствует того же? Неужели мысль об Олимпиаде совсем его не трогает?
У Джорджа было худое лицо, черты которого еще более обострились за те полтора года, которые он провел в приюте. Он был худощав, но в его синих глазах горел прежний неуемный огонь, а в том, как он сейчас вцепился пальцами в одеяло, было что-то от затравленного зверя.
Его сосед по комнате на мгновение оторвался от книги и заодно отрегулировал силу свечения стены, у которой сидел. Его звали Хали Омани, он был нигерийцем. Темно-коричневая кожа и крупные черты лица Хали Омани, казалось, были созданы для того, чтобы выражать только одно спокойствие, и упоминание об Олимпиаде нисколько его не взволновало.
Текст из начала задания с восстановленным порядком букв:
По результатам исследований одного английского университета, не имеет значения, в каком порядке расположены буквы в слове. Главное, чтобы первая и последняя буквы были на месте. Остальные буквы могут следовать в полном беспорядке, все равно текст читается без проблем. Причиной этого является то, что мы не читаем каждую букву по отдельности, а все слово целиком.
Домашнее задание¶
Реализуйте приложение «наборщик». Процесс игры:
При старте приложение запрашивает некоторое слово.
Игроки ходят по очереди. Каждый игрок пытается составить новое слово из букв, входящих в исходное слово. Слова не должны повторяться.
После хода одного игрока, второму игроку задается вопрос: является ли вариант предыдущего игрока словом. Если второй игрок отвечает отрицательно, то ходить снова должен первый игрок.
Чтобы сдаться, игрок вводит символ
#
.
Пример игровой сессии:
Enter word: world
P1> old
P2, is it a word? [yn]: y
P2> rd
P1, is it a word? [yn]: n
P2> #
Winner: P1
Классы¶
Класс — это тип данных, определяющий свойства и поведение некоторой сущности. Свойства определяются атрибутами (переменными), а поведение — методами.
Разберите следующий пример. Какие определены классы? Какие атрибуты они содержат? Какие методы реализованы? Какие созданы экземпляры классов?
class Wheel:
def __init__(self):
self.is_good = True
def puncture(self):
self.is_good = False
class Car:
def __init__(self, wheel_count):
self.wheels = []
for _ in range(wheel_count):
self.wheels.append(Wheel())
def drive(self):
if self.is_good():
print('Driving')
else:
print('Car is broken')
def is_good(self):
for wheel in self.wheels:
if not wheel.is_good:
return False
return True
car = Car(4)
car.drive()
car.wheels[0].puncture()
car.drive()
Задание 1¶
Разработайте программу для ухода за виртуальным домашним животным. Состояние
и поведение животного оформите в класс Pet
. Определите данные, которые
будут характеризовать потребности животного. Например, голод, радость, сон.
Пусть каждый параметр принимает значения от 0 до 100, где 100 — отсутствие
потребности, 0 — острая необходимость.
Пусть счетчик каждой потребности уменьшается со временем. Скорость изменения
потребности удобно определить в методе __init__
вместе с остальными
атрибутами.
Для вычисления значения текущих характеристик будет удобно хранить время
последнего обновления состояния животного. Для получения текущего времени
воспользуйтесь вспомогательной функцией cur_time
.
Создайте меню для ухода за животным, пусть оно включает пункты, соответствующие потребностям: «Покормить», «Поиграть» и другие. Пусть для простоты потребности восполняются моментально до максимального уровня.
На каждом шаге следует выводить изображение животного и его состояние.
Используйте заготовку:
import textwrap
from time import time
def cur_time():
return int(time())
class Pet:
def __str__(self):
view = textwrap.dedent(r"""
|\---/|
| ,_, |
\_`_/-..----.
___/ ` ' ,""+ \
(__...' __\ |`.___.';
(_,...'(_,.`__)/'.....+
""")
return view
def main():
pass
if __name__ == '__main__':
main()
Задание 2¶
Разработайте простой планировщик. Создайте классы Event
и Calendar
.
Пусть событие содержит описание, а также дату и время начала и окончания.
Календарь включает в себя список событий и методы для управления ими. Создайте
меню со следующими пунктами:
Добавить мероприятие
Удалить мероприятие
Показать все мероприятия
Показать мероприятия на день
Показать мероприятия на сегодня
Выход
Для работы с датой и временем импортируйте функции:
from datetime import date, datetime
Декомпозиция¶
Controlling complexity is the essence of computer programming.
—Brian Kernighan
Можно выделить два инструмента управления сложностью в программировании:
Абстракция
Декомпозиция
Абстрагирование — сосредоточение на важных сторонах и отказ от неважных.
Декомпозиция — разделение целого на части.
Один из инструментов построения абстракций и декомпозиции задач в языке Python — функции. При разработке своих функций рекомендуется руководствоваться следующими принципами:
Принцип единственной ответственности (The Single Responsibility Principle, SRP). Функция или класс должны решать только одну задачу.
По возможности старайтесь разрабатывать чистые функции.
Чистая функция — это функция, результат работы которой зависит только от входных аргументов.
Функция не является чистой, если в ней присутствует ввод-вывод или обращение к глобальным переменным.
Do not repeat yourself (DRY). Каждая часть знания должна иметь единственное, непротиворечивое и авторитетное представление в рамках системы. В простых случаях нарушение этого принципа проявляется в дублировании кода, использовании неименованных констант (magic numbers).
Рефакторинг — процесс изменения внутренней структуры программы, не затрагивающий её внешнего поведения и имеющий целью облегчить понимание её работы.
Упражнения¶
Какие принципы нарушают приведенные ниже функции? Как их исправить?
1:
import math
def calculate_area_and_perimeter(height, width):
area = height * weight
perimeter = 2 * height + 2 * width
return area, perimeter
2:
def calculate_area(height, width):
area = height * weight
print(area)
3:
flag = True
def f(n):
global flag
flag = not flag
if flag:
return n * 2
return n
def test():
print(f'f(1) + f(2) = {f(1) + f(2)}')
print(f'f(2) + f(1) = {f(2) + f(1)}')
Выполните рефакторинг функции:
def weekday(day_number):
if day_number == 1:
return 'Monday'
if day_number == 2:
return 'Tuesday'
if day_number == 3:
return 'Wednesday'
if day_number == 4:
return 'Thursday'
if day_number == 5:
return 'Friday'
if day_number == 6:
return 'Saturday'
if day_number == 7:
return 'Sunday'
Что делает этот код? Найдите в нем ошибки. Выполните его рефакторинг.
def main():
# Subject data = [weight_kg, height_m]
subject1 = [80, 1.62]
subject2 = [69, 1.53]
subject3 = [80, 1.66]
subject4 = [80, 1.79]
subject5 = [72, 1.60]
bmi_subject1 = int(subject1[0] / subject1[1] ** 2)
print(f"bmi subject1 = {bmi_subject1}")
bmi_subject2 = int(subject2[0] / subject2[1] ** 2)
print(f"bmi subject2 = {bmi_subject2}")
bmi_subject3 = int(subject3[0] / subject3[1] ** 3)
print(f"bmi subject2 = {bmi_subject3}")
bmi_subject4 = int(subject4[0] / subject4[1] ** 2)
print(f"bmi subject4 = {bmi_subject4}")
bmi_subject5 = int(subject5[0] / subject5[1] ** 2)
print(f"bmi subject5 = {bmi_subject5}")
if __name__ == '__main__':
main()
Задание 1¶
Даны фигуры: окружность, треугольник, прямоугольник. Окружность задана координатами центра и радиусом. Треугольник и прямоугольник заданы координатами вершин. Разработать приложение для вычисления площади фигур.
Для вычисления площади треугольника воспользуйтесь формулой Герона
Где p — полупериметр.
Расстояние между двумя точками можно вычислить по формуле:
Задание 2¶
Разработайте простую модель пиццерии.
В пиццерии готовят несколько разновидностей пиццы по известным рецептам:
recipes = {
'Пепперони': {'орегано', 'томатная паста', 'базилик', 'пепперони'},
'Четыре сыра': {'томаты', 'моцарелла', 'фонтин', 'пармезан', 'фета'},
'Вегетарианская': {'лук', 'сельдерей', 'редис', 'брокколи', 'морковь'},
}
Сейчас работают два повара: стажер и профессионал. За заказ может взяться любой из поваров. Профессионал всегда строго следует рецепту: он знает все рецепты, принимает заказ и готовит пиццу из правильных компонентов. Стажер путает рецепты: принимает заказ и готовит пиццу, выбирая набор компонентов случайно. Посетитель знает рецепты и оставляет заказ, выбирая случайное название пиццы. Клиент ест пиццу и оставляет отзыв. Отзыв состоит из оценки от 1 до 5 и текста. Если клиент получил свой заказ, то он оставляет положительный отзыв с оценкой 5, иначе — отрицательный с оценкой 1.
Для решения этой задачи ответьте на вопросы:
Какие сущности фигурируют в модели?
Какие они содержат данные?
Какое у них поведение?
Реализуйте необходимые классы и составьте сценарий работы пиццерии. В результате работы программы пользователь должен увидеть текстовый лог происходящих событий. Например:
В пиццерию заходит клиент и заказывает Пепперони.
За работу берется Профессионал.
Пицца готова! В нее выходят томатная паста, орегано, базилик и пепперони.
Клиент оставил отзыв: ***** — Все было очень вкусно!
В пиццерию заходит клиент и заказывает Четыре сыра.
За работу берется Стажер.
Пицца готова! В нее входят сельдерей, морковь, лук, редис и брокколи.
Клиент оставил отзыв: * — Повар перепутал заказ!
Задание 3¶
Разработайте игру «Сто спичек». Из кучки, состоящей из ста спичек, двое игроков поочередно берут от 1 до 10 спичек. Выигрывает взявший последнюю спичку. Реализуйте режим для двух игроков и игру с компьютером.
Игра «Крестики-нолики»¶
«Крестики-нолики» — логическая игра между двумя противниками на квадратном поле 3 на 3 клетки. Игроки по очереди ставят на свободные клетки поля 3х3 знаки (один всегда крестики, другой всегда нолики). Первый, выстроивший в ряд 3 своих фигуры по вертикали, горизонтали или диагонали, выигрывает. Первый ход делает игрок, ставящий крестики.
Обычно по завершении партии выигравшая сторона зачёркивает чертой свои три знака (нолика или крестика), составляющих сплошной ряд.
Задание¶
Разработайте игру «Крестики-нолики». Игра ведется между двумя живыми игроками. Победитель определяется по результатм одного раунда. В качестве инстумента для разработке графического интерфейса используйте библиотеку Pygame.
Заранее продумайте структуру вашей программы. Какие классы можно выделить при разработке игры, какие у них будут свойства? При разработке руководствуйтесь принципами, описанными в занятии по теме «Декомпозиция».
Дополнительные задания¶
Реализуйте ввод имени игрока
Добавьте возможность игры против ПК
Ведите подстчет очков
Сохраняйте результаты игр в отдельный файл
Передача данных по сети. Стек протоколов TCP/IP.¶
Теория¶
Обмен информацией между компьютерами (по проводному соединению или нет) происходит путем передачи пакетов (фрагментов) данных через сеть. Такая передача должна проходить по определённым правилам или стандартам, которые и называют протоколом передачи данных.
Протокол передачи данных - это набор соглашений интерфейса логического уровня, которые определяют обмен данными между различными программами.
Для передачи данных в сети Интернет используется стек протоколов TCP/IP. В протоколе TCP/IP строго описаны правила передачи информации от отправителя к получателю и обратно. Передача сообщений проходит несколько уровней, каждый из которых работает независимо от предыдущего, используя т.н. инкапсуляцию.
Уровни модели TCP/IP:
Уровень |
За что отвечает |
Протоколы |
---|---|---|
4 уровень - Прикладной |
Формат данных, их шифрование |
HTTP (веб-страницы), SMTP (почта), FTP (файлы) |
3 уровень - Транспортный |
Способ передачи данных |
TCP, UDP |
2 уровень - Сетевой |
Маршрутизация в сети |
IP |
1 уровень - Сетевых интерфейсов |
Физическая передача данных |
Ethernet, Wi-Fi |
Общий ход передачи информации выглядит следующим образом:
Данные от приложения отправляются протоколу транспортного уровня.
Получив данные от приложения, протокол разделяет всю информацию на небольшие блоки (пакеты). К каждому пакету добавляется адрес назначения, а затем пакет передается на следующий уровень - уровень протоколов Интернет (сетевой уровень).
На сетевом уровне пакет помещается в дейтаграмму протокола Интернет (IP), к которой добавляется заголовок и концевик. Протокол сетевого уровня определяет адрес следующего пункта назначения IP-дейтаграммы и отправляет его на уровень сетевого интерфейса.
Уровень сетевого интерфейса принимает IP-дейтаграмму и передает их в виде кадров с помощью аппаратного обеспечения (например, сетевой карты).
Пакеты доставляются на компьютер получателя, после чего проходят все уровни протоколов в обратном порядке. На каждом уровне удаляются соответствующие этому уровню заголовки, после чего данные передаются на уровень приложения.
Практика¶
1. Напишите функции для передачи данных с помощью модели TCP/IP от клиента сети. Каждая функция должна имитировать работу одного из уровней передачи данных. Используйте заготовку кода ниже:
''' 4 уровень, функция application() - отвечает за формат данных, их шифрование
input функции - данные, которые хотим отправить по сети, протокол данных ('SMTP','HTTP','FTP')
output: данные + информация про формат данных и шифрование '''
def application (data, protocol):
...
''' 3 уровень, функция transport() - отвечает за способ передачи данных и их транспортировку
input: результат работы функции application(), протокол транспортировки данных ('TCP', 'UDP')
output: данные + информация про транспортировку'''
def transport(data, protocol):
...
''' 2 уровень, функция internet() - отвечает за маршрутизацию в сети. Она ищет кому и как доставить данные
input: результат работы функции transport()
output: данные + информация про адресата и маршрут доставки'''
def internet(data):
...
'''1 уровень, функция network_interface - отвечает за соединение с сетью
input: результат работы функции internet(), информация о соединении
output: кортеж из 2 значений:
1. данные, преобразованные в бинарный формат
2. информация о типе соединения (1 - 'Ethernet', 2 -'Wi-Fi', другие числа - "No connection")'''
def network_interface (data, connection):
...
data = '01101'
print (data)
data_app = application(data,'SMTP')
print ('application level - ', data_app)
data_transport = transport (data_app,'TCP')
print ('transport level - ', data_transport)
data_ip = internet (data_transport)
print ('internet level - ', data_ip)
data_from_network = network_interface (data_ip,'Wi-Fi')
print ('network_interface level - ', data_from_network)
2. Напишите функции для получения данных из сети клиентом с помощью модели TCP/IP. Каждая функция должна имитировать работу одного из уровней передачи данных. Используйте заготовку кода ниже, учтите потери данных:
'''1 уровень, функция network_interface - отвечает за соединение с сетью
передаём в функцию 2 аргумента:
1 аргумент, data - это данные в виде бинарного числа
2 аргумент, connection - это тип соединения (1 - 'Ethernet', 2 -'Wi-Fi', другие числа - "No connection")
возвращаем:
- если удалось установить соединение - то кортеж из 2 значений: данные в виде строки из нулей и единиц и тип соединения
- если не удалось установить соединение, то строку "No connection"'''
def network_interface(data, connection):
...
''' 2 уровень, функция internet() - отвечает за маршрутизацию в сети. Она ищет кому и как доставить данные
передаём в функцию результат работы функции network_interface()
возвращаем данные в виде строки, если данные переданы в кортеже и адресованы нам, иначе - передаём None'''
def internet(data):
...
''' 3 уровень, функция transport() - отвечает за способ передачи данных и их транспортировку
передаём в функцию результат работы функции internet()
возвращаем:
- если c предыдущего уровня получены данные, то формируем кортеж из 2 значений: данные, протокол передачи
- None - если данные не получены'''
def transport(data):
...
''' 4 уровень, функция application() - отвечает за формат данных, их расшифровку
передаём в функцию результат работы функции transport()
возвращаем: '''
def application (data):
...
data = 0b110110111
print (bin(data))
data_from_network = network_interface (data,1)
print ('network_interface level - ', data_from_network)
data_ip = internet (data_from_network)
print ('internet level - ', data_ip)
data_transport = transport (data_ip)
print ('transport level - ', data_transport)
data_app = application(data_transport)
print ('application level - ', data_app)
Введение в сетевое программирование. Сокеты.¶
Теория¶
Сокет — это программный интерфейс для обеспечения информационного обмена между процессами.
- Виды сокетов:
Серверный - сокет, который принимает сообщения.
Клиентский - сокет, который отправляет сообщения.
- Сокеты работают на транспортном уровне протоколв и соответственно бывают 2 типов:
Потоковые (на основе TCP, в коде обозначаются
SOCK_STREAM
) - сокеты с установленным соединением на основе протокола TCP, передают поток байтов, который может быть двунаправленным - т.е. приложение может и получать и отправлять данные.Дейтаграммные (на основе UDP, в коде обозначаются
SOCK_DGRAM
) - сокеты, не требующие установления явного соединения между ними. Сообщение отправляется указанному сокету и, соответственно, может получаться от указанного сокета.
Сокет состоит из IP-адреса и порта.
IP-адрес - уникальный сетевой адрес узла в компьютерной сети, построенной по протоколу IP. В версии протокола IPv4 IP-адрес имеет длину 4 байта (например, 192.168.0.3), а в версии протокола IPv6 IP-адрес имеет длину 16 байт (например, 2001:0db8:85a3:0000:0000:8a2e:0370:7334). IP-адрес должен быть уникален.
Порт - натуральное число, записываемое в заголовках протоколов транспортного уровня (TCP, UDP и др.). Порт используется для определения процесса-получателя пакета в пределах одного хоста.
В python для работы с сокетами используется встроенная библиотека socket
.
Одной из основных функций модуля является функция socket()
, которая возвращает объект типа сокет, обладающий соответствующими функциями для работы с соединением.:
class socket.socket
sock = socket.socket()
- Функции объекта socket
socket.bind(address)
- Привязывает сокет к адресу address (инициализирует IP-адрес и порт). Сокет не должен быть привязан до этого.socket.listen([backlog])
- Переводит сервер в режим приема соединений. Параметр``backlog (int)`` – количество соединений, которые будет принимать сервер.socket.accept()
- Принимает соединение и блокирует приложение в ожидании сообщения от клиента. В результате возвращает кортеж:conn
: объект соединения (сокет), который можно использовать для отправки/получения данных;address
: адрес клиента.
socket.recv(bufsize[, flags])
- Читает и возвращает данные в двоичном формате (набор байтов) из сокета. Параметрbufsize (int)
– максимальное количество байтов в одном сообщении.socket.send(bytes[, flags])
- Отправляет данные клиенту и возвращает количество отправленных байт. Параметрbytes (bytes)
– двоичные данные.socket.close()
- Закрывает сокет.
Работа с сокетом во многом схожа с работой с файловым объектом. Принцип - открыли соединение - считали данные - закрыли соединение.
Создание серверного сокета: .. code
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # создаем сокет
sock.bind(('', 55000)) # связываем сокет с портом, где он будет ожидать сообщения
sock.listen(10) # указываем сколько может сокет принимать соединений
print('Server is running, please, press ctrl+c to stop')
while True:
conn, addr = sock.accept() # начинаем принимать соединения
print('connected:', addr) # выводим информацию о подключении
data = conn.recv(1024) # принимаем данные от клиента, по 1024 байт
print(str(data))
conn.send(data.upper()) # в ответ клиенту отправляем сообщение в верхнем регистре
conn.close() # закрываем соединение
Создание клиентского сокета: .. code
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # создаем сокет
sock.connect(('localhost', 55000)) # подключемся к серверному сокету
sock.send(bytes('Hello, world', encoding = 'UTF-8')) # отправляем сообщение
data = sock.recv(1024) # читаем ответ от серверного сокета
sock.close() # закрываем соединение
print(data)
Important
Обратите внимание, клиент отправляет байтовую строку, а не обычную. Сервер так же принимает в качестве сообщения байтовую строку и должен в ответе вернуть объект того же типа.
Задания¶
Реализовать чат без графического интерфейса, который позволит обмениваться сообщениями только между клиентом и сервером. Клиент должен получать сообщения сервера в том числе.
С помощью модуля easygui (см. полезные ссылки), добавьте в разработанный чат простой графический интерфейс.
Разработайте приложение, которое будет запрашивать у пользователя название файла, а затем отправлять содержимое этого файла серверу. Сервер будет подсчитывать количество слов и возвращать ответ.
Добавьте к чату из задачи 2 чат-бота на стороне сервера. Добавьте 4-5 фраз, которые сервер будет отправлять по определённым условиям.
Полезные ссылки:¶
Кодировки и шифрование¶
Кодировки¶
Кодировка - это правила перевода одного набора символов в другой. В отношении компьютерных программ речь идёт о правилах кодирования последовательности из нулей и единиц в текст, число или что-либо другое.
Наиболее распространённые кодировки
Обозначение в python |
Название кодировки |
Описание |
---|---|---|
|
ASCII |
Латинские буквы, цифры и простые символы |
|
windows-1251 |
Кириллическая кодировка (русский и другие языки) |
|
KOI-8 |
Кодировка для русского языка |
|
UTF-8 |
Юникод-кодировка, все языки (длина символа - 8 бит) |
|
UTF-16 |
Юникод-кодировка, все языки (длина символа - 16 бит) |
Unicode — стандарт кодирования символов, включающий в себя знаки почти всех письменных языков мира. В настоящее время стандарт является преобладающим в Интернете.
стандарт включает более 138 тысяч символов;
каждый символ имеет определённое название и код (номер);
коды состоят из латинских букв и шестнадцатеричных цифр, например:
U+0073
.
Примеры кодов, имен и соответствующих символов:
U+0073
, «LATIN SMALL LETTER S» - s
U+1F383
, «JACK-O-LANTERN» - 🎃
U+2615
, «HOT BEVERAGE» - ☕
Конвертация данных между байтам и строками¶
Данные по сети передаются, как правило, в байтах. Например, метод socket.recv()
получает данные в байтах.
Чтобы преобразовывать данные из байт в строки и наоборот используются специальные методы:
метод
encode('encoding_name')
- позволяет перевести данные из строки в байты (str -> bytes);метод
decode('encoding_name')
- позволяет перевести данные из байт в строку (bytes -> str);
В коде будет выглядеть так:
# кодирование строки в байты
s = 'привет'
s_bytes = s.encode ('utf-8')
print (s_bytes)
Out: b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82'
# декодирование байт в строку
s_dec = s_bytes.decode ('utf-8')
print (s_dec)
Out: привет
При работе с кодировкой важно помнить:
Если вы кодируете строку в байты кодировкой UTF-8, то и перекодировать её из байт нужно этой же кодировкой. Некоторые кодировки совместимы, но в большинстве случаев, нарушения этого правила ведёт к потере данных.
В своём коде всегда используйте кодировки Unicode, оптимально UTF-8, она используется по умолчанию в большинстве методов и функций, так что это снижает риск ошибок.
Декодирование байтовых данных в строки лучше производить сразу после их получения, а кодирование в байты - только перед отправкой. В коде лучше работать с привычными типами данных (строки, списки, числа), не с байтами, ведь большинство методов в Python с байтовыми строками не работают (или работают не так, как можно ожидать).
Шифрование¶
Шифр Цезаря
Шифр Цезаря — это вид шифра подстановки, в котором каждый символ в открытом тексте заменяется символом, находящимся на некотором постоянном числе позиций левее или правее него в алфавите. Например, в шифре со сдвигом вправо на 3, A была бы заменена на D, B станет E, и так далее.
Повторить шифр можно в занятии
Формула для кодирования символа:
symbol_encoded = ((((symbol) - first + key) % size) + first)
Шифр пар
Алфавит случайным образом записывают в 2 строки, и шифрование текста происходит заменой буквы на соседнюю ей по вертикали. Например:
['v', 'q', 'f', 's', 'p', 'u', 'n', 'a', 'm', 'j', 'c', 'k', 'h']
['l', 'r', 'o', 'e', 'x', 'd', 'z', 'g', 'b', 'w', 't', 'i', 'y']
In: hello
Out: ysvvf
Шифр Виженера
На алфавите длиной N вводят операцию добавления (циклического сдвига) букв. Пронумеровав буквы, добавляем их по модулю N (для англ. алфавита N=26).
Выбираем слово-ключ (пускай pass) и подписываем его под сообщением сколько нужно раз:
Сообщение: search
Ключ: passpa
Шифр: hesjrh
Задания¶
Доработайте прототип чата из прошлого урока таким образом, чтобы он корректно работал с русским языком (используйте методы кодирования и декодирования байтовых строк).
2. Напишите функцию для шифрования файла шифром Цезаря. Расшифруйте:
key = 2
s = 'Oquv uvctu ctg qdugtxgf vq dg ogodgtu qh dkpctb uvct ubuvgou'
3. Напишите функцию для шифрования файла шифром пар. Расшифруйте:
d_1 = [109, 122, 106, 115, 100, 99, 105, 120, 110, 98, 121, 118, 107]
d_2 = [112, 103, 108, 104, 111, 102, 119, 117, 97, 101, 116, 113, 114]
s = 'A hynk wh na nhykdadpwfnj delbfy fdahwhywaz dc n jxpwadxh hmsbkdwo dc mjnhpn sbjo ydzbysbk et wyh dia zknqwyt.'
4.* Напишите функцию для шифрования файла шифром Виженера. Расшифруйте. 5. Добавьте в чат (с кодировкой) возможность выполнять шифрование и дешифрование сообщения одним из шифров по выбору пользователя. 6. Доработайте чат таким образом, чтобы пользователь отправлял серверу имя зашифрованного файла и шифр, а сервер дешифровал его и отправлял содержимое файла обратно пользователю.
Полезные ссылки¶
Потоки. Передача файлов. Клиент-серверный чат¶
Потоки¶
Потоки (thread) в Python позволяет одновременно запускать разные подпрограммы, таким образом становится возможно выполнять несколько подпрограмм в одно и то же время. thread - это отдельный поток выполнения.
На самом деле подпрограммы даже при использовании потоков не выполняются одновременно:
Работа с потоками - это не работа с разными ядрами процесса, мы фактически работаем внутри того же процесса, что и без потоков, только дробим его на подзадачи, так что о более эффективном распределении ресурсов речи не идёт.
В Python для обеспечения стабильности и снижения конфликтов работает специальный шлюз - Global Interpreter Lock (GIL). Он и контролирует потоки.
GIL переключает потоки по умолчанию раз в 5 миллисекунд, так что потоки работают не одновременно, а последовательно, постоянно сменяя друга.
Самая близкая идея работе потоков в Питоне - сеанс одновременной игры в шахматы, когда один шахматист играет с несколькими. Сеанс хоть и называется “одновременным”, но фактически шахматист обходит всех по очереди и делает свой ход в каждой партии.
Для работы с потоками используются модули _thread и threading (более новый).
Рассмотрим простой код для работы с потоками.:
import threading
import time
def thread_function(number):
print(f"Thread {number}: starting\n")
time.sleep(2)
print(f"Thread {number}: finishing\n")
threads = list()
for index in range(3):
print(f"Create and start thread {index}\n")
x = threading.Thread(target=thread_function, args=(index,))
threads.append(x)
x.start()
for i,thread in enumerate (threads):
print (f'Join thread {i} \n')
thread.join()
Передача файлов¶
- Передачу файлов с помощью сокетов можно реализовать по следующей схеме:
Настраиваем сокет, устанавливаем соединение.
На отправляющей стороне:
открываем файл на чтение в режиме байтового чтения (
'rb'
).построчно передаём файл принимающей стороне.
На принимающей стороне:
открываем файл на запись в режиме байтовой записи (
'wb'
).построчно пишем в файл всё, что получаем от передающей стороны.
Закрываем файлы и соединение.
Передача файлов байтовыми строками позволяет пересылать любые файлы, независимо от их типа, расширения и т.п.
Пример кода для отправки файла с клиента серверу.
Сервер:
import socket
import sys
# создаём сокет и связываем его с IP-адресом и портом
sock = socket.socket()
ip = "localhost"
port = 9999
sock.bind((ip, port))
# сервер ожидает передачи информации
sock.listen(10)
while True:
# начинаем принимать соединения
conn, addr = sock.accept()
# выводим информацию о подключении
print('connected:', addr)
# получаем название файла
name_f = (conn.recv(1024)).decode ('UTF-8')
# открываем файл в режиме байтовой записи в отдельной папке 'sent'
f = open('sent/' + name_f,'wb')
while True:
# получаем байтовые строки
l = conn.recv(1024)
# пишем байтовые строки в файл на сервере
f.write(l)
if not l:
break
f.close()
conn.close()
print('File received')
sock.close()
Клиент:
import socket
import sys
ip = "localhost"
port = 9999
# создаём сокет для подключения
sock = socket.socket()
sock.connect((ip,port))
# запрашиваем имя файла и отправляем серверу
f_name = input ('File to send: ')
sock.send((bytes(f_name, encoding = 'UTF-8')))
# открываем файл в режиме байтового чтения
f = open (f_name, "rb")
# читаем строку
l = f.read(1024)
while (l):
# отправляем строку на сервер
sock.send(l)
l = f.read(1024)
f.close()
sock.close()
Задания¶
Используя код из урока, реализуйте возможность отправлять файл с сервера клиенту.
Реализуйте возможность передачи файлов как от клиента серверу, так и от сервера клиенту:
должна быть возможность выбрать режим передачи (1 - от клиента серверу, 2 - от сервера клиенту);
оформите код с помощью функций;
добавьте графический интерфейс с помощью модуля easygui.
Используя потоки напишите клиент-серверный чат, который будет принимать и отправлять сообщения. Для этого потребуется оформить код в функции.
Многопользовательский чат¶
Шаблон кода¶
Используя шаблон кода, напишите многопользовательский чат. Сервер:
import socket
from threading import Thread
# функция нужна для старта приёма сообщений
def accept_incoming_connections():
while True:
client, client_address = sock.accept()
# выведите информацию о подключении
# попросите ввести имя
# добавьте адрес клиента в словарь addresses
Thread(target=handle_client, args=(client,)).start()
# функция обрабатывает сообщения одного клиента
def handle_client(client):
# получите сообщение с именем клиента и поприветсвуйте его
# используя функцию broadcast() напишите всем участникам чата, что к нему присоединился текущий клиент
broadcast(msg)
# добавьте имя клиента в словарь clients (в качестве ключей - сокеты клиентов)
# получайте сообщения от клиентов в чате
while True:
msg = client.recv(1024).decode("utf-8")
print(msg)
# используя функцию broadcast() отправляйте сообщения всем участникам чата
# обработайте ситуацию выхода клиента из чата:
#предупредите, что участник вышел из чата
#закройте соединение
#удалите участника из clients
# функция отправляет сообщения всем клиентам
def broadcast(msg, name =""):
# отправляйте сообщения все клиентам из словаря clients
clients = {}
addresses = {}
sock = socket.socket()
sock.bind(("", 10001))
sock.listen(5)
print("Waiting for connection...")
accept_thread = Thread(target=accept_incoming_connections)
accept_thread.start()
accept_thread.join()
sock.close()
Клиент:
import socket
# создаем сокет
# подключемся к серверному сокету
while True:
# читаем и выводим ответ от серверного сокета
# просим ввести сообщение в чат
# отправляем сообщение
# обрабатываем ситуацию выхода из чата
# закрываем соединение
Задачи¶
Используя примеры кода напишите многопользовательский чат.
Добавьте к чату функцию send_file с возможностью отправлять файлы.
Добавьте интерфейс с помощью модуля easygui.
Простой http сервер.¶
Теория¶
Любой online-сервис начинается с того, что необходимо приложение, которое способно принимать и обрабатывать http-запросы.
В python
есть встроенный модуль, который полвзоляет создавать простой http-сервер.
from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
def run(server_class=HTTPServer, handler_class=BaseHTTPRequestHandler):
server_address = ('', 8000)
httpd = server_class(server_address, handler_class)
try:
httpd.serve_forever()
except KeyboardInterrupt:
httpd.server_close()
Если запустить функцию run()
, то будет запущен http-сервер, и если в браузере
Вы введете 127.0.0.1:8000
, то Вам вернутся ответ, но ответ будет содержать ошибку,
потому что сервер не знает как обрабатывать запросы.
Для того что бы исправить эту ошибку трубется в обработчике(BaseHTTPRequestHandler
)
реализовать метод по обработке GET-запросов:
class HttpGetHandler(BaseHTTPRequestHandler):
"""Обработчик с реализованным методом do_GET."""
def do_GET(self):
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write('<html><head><meta charset="utf-8">'.encode())
self.wfile.write('<title>Простой HTTP-сервер.</title></head>'.encode())
self.wfile.write('<body>Был получен GET-запрос.</body></html>'.encode())
Теперь, если запустить сервер с новым обработчиком run(handler_class=HttpGetHandler)
,
то при переходе по адресу 127.0.0.1:8000
можно будет увидеть ответ на запрос.
Important
Обратите внимание, что при формировании ответа обработчика был использован метод
encode()
у строк. Сделано этого потому что в теле ответа могут быть
использованы только байтовые строки.
Фреймворки для создания web-приложений. Описание.¶
Теория¶
В предыдущем уроке был реализован простой http сервер с использованием стандартных средств python
.
Как можно было заметить, для того что бы сформировать ответ требуется задать не только тело ответа, но и вручную устанавливать все необходимые http-заголовки.
Так же, любое современное web-приложение может обрабатывать запросы не только по корневому пути
(http://127.0.0.1:8000/
как было с простым http сервером). Задание нестандартных путей для обработчиков
из встроенной библиотеки, а так же маршрутизация между обработчиками, накладывает дополнительные временные расходы
на реализацию сервера.
Для упращения процесса создания серверных приложений были разработаны фреймворки:
Flask¶
В рамках курса будет рассмотрена работа с фреймворком Flask.
Реализуем приложение, аналогичное серверу из предыдущего раздела:
# Файл app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def sample():
return 'Был получен GET-запрос.'
if __name__ == '__main__':
app.run(host='127.0.0.1', port='8000')
Сохраните код, перейдите в директорию где находится файл с кодом и выполните: python app.py
, в результате вы должны увидеть:
Running on http://127.0.0.1:8000/ (Press CTRL+C to quit)
Теперь, если перейти в браузере по адресу 127.0.0.1:8000
, то должна отобразиться уже знакомая страница.
Как видно, теперь не потребовалось создавать отедельный сервер, к которому мы будем привязывать обработчики для запросов. Вместо этого мы можем описать обычную функцию, и указать приложению использовать ее в качестве обработчика GET-запроса по указанному пути.
Для более детального разбора обработчиков и практических задач переходите к следующему разделу
Flask. Обработчики запросов.¶
Общее¶
В предыдущем разделе было реализовано web-приложение с использованием Flask, которое может обрабатывать GET-запросы по корневому пути.
@app.route('/')
def sample():
return 'Был получен GET-запрос.'
Обработчик - обычная функция, которая отмечается как обработчик с указанием пути: @app.route('/')
.
HTTP-методы¶
При реализации простого сервера требовалось реализовывать специальные методы для обработки различных HTTP-методов.
do_GET
do_POST
и т.д.
При работе с Flask достаточно передать список методов, которые должен принимать тот или иной обработчик:
@app.route('/', methods=['GET'])
def sample():
return 'Был получен GET-запрос.'
Таким образом, обработчик sample
будет обрабатывать исключительно GET-запросы. При попытке обратиться по указанному пути
с другим HTTP-методом будет вызвана ошибка.
Обработка параметров¶
Любое приложение работает с данными. Данные в приложение могут поступать несколькими способами:
В пути запроса(GET)
В теле запроса(POST, PUT и т.д.)
Обработка параметров в пути запросов¶
Для запроса с GET-методом возможно передать данные только в пути самого запроса, и Flask предоставляет удобный способ для передачи их в обработчик:
@app.route('/say/<phrase>')
def say(phrase):
return f'Была получена фраза {phrase}'
Для передачи параметра в обработчик достаточоно добавить его имя в путь @app.route('/say/<phrase>')
. Важно, что бы оно начиналось
со знака <
, и заканчивалось >
. Затем это же имя указывается в качестве аргумента функции-обработчика.
Flask так же предоставляет возможность указать и тип принимаемого параметра:
@app.route('/sum/<int:number>')
def sum_numbers(number):
return f'Результат сложения 5 и {number} = {5+number}'
Для указания типа достаточно добавить его название перед именем параметра: <int:number>
.
Обработка параметров в теле запросов¶
Как было видно из предыдущих пунктов, в обрабочик не передается сам запрос. Для того что бы получить к нему доступ требуется
импортировать специальный объект Flask - request
, в нем то и будет содержаться вся информация по запросу:
from flask import request
@app.route('/calc/', methods=['GET', 'POST'])
def calc():
if request.method == 'POST':
a = int(request.form['a'])
b = int(request.form['b'])
result = a + b
return f'{a} + {b} = {result}'
return f'Был получен {request.method} запрос.'
Задания¶
Разработайте приложение по просмотру и добавлению информации об учениках курса. Функции приложения:
Просмотр списка учащихся(ФИО, курс).
Просмотр полной информации о конкретном учащемся.
Добавление нового учащегося.
Информация об учащемся должна содержать: ФИО, курс, сколько лет.
Для хранения данных используйте словари(dict
).
Flask. Шаблоны.¶
Общее¶
Простые текстовые ответы подходят для реализации API, для взаимодействия программы с программой.
Для того что бы приложение было более дружелюбно к пользователю стоит возвращать ответ используя язык разметки HTML.
Самый простой способ - вернуть из обработчика строку, в которой будет содержаться требуемая разметка и информация:
@app.route('/html/', methods=['GET'])
def html():
html_response = '<html><body>Был получен <p>{}</p> запрос.</body></html>'
return html_response.format(request.method)
Но что если требуется выводить список сохраненных записей, количество которых заранее неизвестно? Реализуем такой обработчик:
data = (
dict(name='Python', released='20.01.1991'),
dict(name='Java', released='23.06.1995'),
dict(name='GO', released='10.11.2009'),
)
@app.route('/table/', methods=['GET'])
def table():
start = '<html><body><table border=1>'
caption = '<caption>Языки программирования</caption>'
header = '<tr><th>Название</th><th>Первый релиз</th></tr>'
end = '</table></body></html>'
tr_list = list()
for item in data:
tr_list.append(
f'<tr><td>{item["name"]}</td><td>{item["released"]}</td></tr>'
)
content = ''.join(tr_list)
html_response = ''.join((start, caption, header, content, end))
return html_response.format(request.method)
В этом случае заметно усложняется реализация самого обработчика, а так же усложняется и отладка HTML разметки. Не проще ли хранить разметку отдельно от кода?
Шаблоны¶
Для решения описанной выше проблемы используются так называемые “Шаблоны”. По большому счету, шаблоны - обычные html-файлы с добавлением специальных синтаксических конструкций для установки изменяемых(динамических) данных.
Доработаем пример с таблицей с применением шаблонов. Для этого в директории с файлом Flask-приложения создайте новую директорию с названием “templates” (по умолчанию, Flask ищет в ней файлы шаблонов). Внутри новой директории создайте файл “table.html” со следующим содержимым:
<html>
<body>
<table border="1">
<caption>Языки программирования</caption>
<tr>
<th>Название</th>
<th>Первый релиз</th>
</tr>
{% for item in items %}
<tr><td>{{ item.name }}</td><td>{{ item.released }}</td></tr>
{% endfor %}
</table>
</body>
</html>
Как можно было заметить, помимо знакомых html-тэгов в файле добавились выражения вида:
{% for item in items %}
- выражение обозначает начало блока цикла.{{ item.name }}
- выражение указывает что требуется вывести значение.{% endfor %}
- окончание блока цикла.
Если разделить на группы такие выражения, то получится:
Выражение вида
{% ... %}
- обозначает начало или окончание блока(for
,if
и т.д.).Выражение вида
{{ item }}
- обозначает вывод значения.
Теперь реализуем обработчик, который будет использовать описанный выше шаблон:
from flask import render_template
data = (
dict(name='Python', released='20.01.1991'),
dict(name='Java', released='23.06.1995'),
dict(name='GO', released='10.11.2009'),
)
@app.route('/template/', methods=['GET'])
def template():
return render_template('table.html', items=data)
Для формирования ответа с использованием шаблона мы применили функцию render_template('table.html', items=data)
, где
в качетве первого аргумента передали имя файла с шаблоном, а в качестве именованного второго - кортеж данных для вывода.
Important
Обратите внимание, что именованный аргумент(items
из примера) должен иметь то же имя, что и переменная в шаблоне
{% for item in items %}
Задания¶
Доработайте пользовательский и реализуйте API интерфейс для работы с приложением из предыдущего раздела.
Для пользовательского интерфейса требуется отображать список учащихся в таблице, ФИО должно быть ссылкой на просмотр детальной информации об учащемся.
Детальная информация об учащемся выводить так же в таблице.
Для API интерфйеса используйте префикс
api
в пути для обрабтчиков, а для пользовательского -ui
.
Для проверки API интерфейса используйте библиотеку requests.