Читайте оригинальную статью в блоге здесь.
На этой неделе мы решаем последний набор задач курса. Мы так много прошли, решили много красивых задач, и после этой недели готовы реализовать наш собственный финальный проект! Я знаю, что это немного эмоционально, но позвольте мне в последний раз предупредить, что эта серия была задумана как руководство к размышлению над проблемами, а не просто как предоставление решений. И, как всегда, предполагается, что вы уже прочитали объяснения проблем. Предыдущие посты этой серии вы можете найти в архиве.
До сих пор мы писали код процедурно, но на этот раз мы будем использовать парадигму объектно-ориентированного программирования, что, безусловно, интересно, поскольку мы будем исследовать каждую проблему. Давайте погрузимся!
Времена любви
В первой задаче этой недели мы работаем с объектом datetime
, вместо того чтобы писать собственные классы с нуля. Нам нужно преобразовать дни в минуты; например, 365 дней дают 525600 минут.
Для решения этой задачи дается много подсказок, но давайте посмотрим. Для начала, на этой неделе мы хотим, чтобы наш код был более модульным, поскольку мы учимся ценить важность модульности в программировании. Нам нужно получить входные данные (в формате YYYY-MM-DD
), преобразовать их в объект даты, вычислить разницу дней между этой датой и сегодняшним днем, преобразовать дни в минуты и, наконец, преобразовать минуты в слова. Может показаться, что это много, но такое мышление на самом деле облегчает нашу работу.
Во-первых, как всегда, мы можем попытаться получить входные данные, и если у нас возникнет ValueError
, мы можем выйти из программы со строкой типа Invalid date
, что приведет к статусу выхода 1. Если мы наберем help(sys.exit)
, чтобы получить информацию из документации в этом случае, она буквально скажет вам об этом:
Help on built-in function exit in module sys:
exit(status=None, /)
Exit the interpreter by raising SystemExit(status).
If the status is omitted or None, it defaults to zero (i.e., success).
If the status is an integer, it will be used as the system exit status.
If it is another kind of object, it will be printed and the system
exit status will be one (i.e., failure).
Мы делаем это уже много недель, так что проблем нет.
Если заданный формат устраивает (что-то вроде 2021-06-02
, например), мы подсчитаем, сколько дней прошло с сегодняшнего дня. Перед этим вспомните, что input возвращает строку, а для построения объекта даты нам нужны целые числа, поэтому год, месяц и день, которые отделяются от input, должны быть целыми числами.
Чтобы подсчитать, сколько дней прошло, в datetime.date
есть несколько методов, которые могут здесь пригодиться. Он уже находится в разделе подсказок, поэтому если мы вычтем заданную дату из сегодняшнего дня, то у нас вернется объект timedelta
, который имеет свои собственные атрибуты экземпляра, скажем, дни.
После того, как мы получили дни, нам нужно преобразовать их в минуты, и как это сделать, также указано в объяснении задачи. Буквально это выглядит следующим образом:
def days_to_minutes(days):
return days * 24 * 60
После того как мы получили минуты, нам нужно преобразовать их в красивый вид. Библиотека inflect
, в которую я успел влюбиться, очень помогает. Нам нужно буквально преобразовать число в слова, но нам нужны только запятые и никаких «и слов».
Поэтому вместо того, чтобы выглядеть так:
'five hundred and twenty-five thousand, six hundred'
Наш вывод должен выглядеть следующим образом:
'Five hundred twenty-five thousand, six hundred minutes'
Обратите внимание, что мы также хотим, чтобы наш вывод был написан заглавными буквами. Опять же, слишком очевидная подсказка, но помните, что документация — ваш друг.
После этого нам осталось сделать совсем немного, кроме написания тестов для нашего кода. Как тестировать, также указано в объяснении задачи, все спецификации тестов должны быть достаточными для реализации. Мы уже знакомы с тестированием и привыкли к нему уже много недель, так что это также должно быть довольно легко. Теперь давайте проверим следующую задачу.
Баночка для печенья
Вот и пришло время написать наш первый урок в этом курсе. В этой задаче мы храним печенье в банке. Просто и очень весело, как кажется.
Нам уже дан шаблон, и нам нужно реализовать функции __init__
, __str__
, deposit
, withdraw
, capacity
и size
. Прежде всего, помните, что функция __init__
инициализирует нашу конструкцию объекта. Итак, предположим, вы только что купили банку с печеньем в магазине, сколько печенья в ней было, когда вы ее только купили? У нее есть емкость (в данном случае по умолчанию 12), но поскольку в ней нет печенья, ее размер равен 0, когда вы ее покупаете. По мере того как вы помещаете в него печенье, его размер увеличивается, а по мере того как вы изымаете печенье, его размер уменьшается. Поэтому при инициализации наша банка печенья имеет вместимость, равную заданной в качестве вместимости, и размер 0.
Для функций deposit
и withdraw
нам нужно быть осторожными с некоторыми крайними случаями. Например, если общее количество печенек после пополнения счета превышает емкость, мы должны выдать сообщение ValueError
. В противном случае мы увеличиваем размер. И, если количество для снятия больше, чем размер, мы также поднимаем ValueError
. В противном случае мы уменьшаем размер.
А теперь самое интересное. Работая с емкостью и размером, вы понимаете, что мы не только читаем их значения, но и устанавливаем их. Как уже указано в шаблоне, для функций getter мы будем использовать декоратор @property
. Как же нам тогда быть с сеттерами? Прежде чем ответить на этот вопрос, давайте рассмотрим пример. Допустим, мы создали класс для домашнего сочинения по Хогвартсу, и нас в основном волнует количество слов в нем. По умолчанию у нас стоит ограничение в 2000 слов (что, я полагаю, для Гермионы было бы ничем), но его можно изменить. Мы можем добавлять или удалять слова, но главное — это то, как мы обращаемся с лимитом слов и количеством слов, которые мы уже написали. Это звучит сложно, но давайте посмотрим:
class HogwartsEssay:
def __init__(self, word_limit=2000):
self.word_limit = word_limit
self.words_written = 0
def __str__(self):
return f'Number of words written: {self.words_written}'
def add_words(self, number_of_words_to_add):
...
def remove_words(self, number_of_words_to_remove):
...
@property
def word_limit(self):
return self._word_limit
@word_limit.setter
def word_limit(self, word_limit):
if word_limit < 0:
raise ValueError
self._word_limit = word_limit
@property
def words_written(self):
return self._words_written
@words_written.setter
def words_written(self, words_written):
self._words_written = words_written
Как видите, функции add_words
и remove_words
опущены, чтобы не подходить слишком близко к решению. Но идея проста. Мы можем получить лимит слов, а также количество написанных слов, мы также можем установить лимит слов, если он не меньше 0, и установить количество написанных слов. Возможно, это странный пример, но идея похожа на пример, приведенный в лекции. Если вы смотрели лекцию, то вы уже знакомы с использованием геттеров и сеттеров. Это действительно забавная задача, хотя на первый взгляд она может показаться немного сложной. Это действительно все, что нам нужно сделать. А для тестов раздел «Как тестировать» снова станет вашим другом, поскольку в нем почти шаг за шагом рассказывается, что нужно делать.
И, прежде чем мы перейдем к следующему, как напечатать печенье? Ну, мы печатаем печенье-эмодзи для количества печенья в банке, и, спасибо Гвидо, что Python — удивительный язык — и имеет строковый оператор, который помогает нам повторять наши строки.
Переходим к следующей (и последней) задаче!
CS50 Shirtificate
Самая последняя проблема, которую нам предстоит решить, является в некотором роде настраиваемой. С помощью библиотеки fpdf2
мы сделаем свою собственную футболку I took CS50.
Здесь мантра, которую мы твердили неделями, обрела форму и смотрит на нас во плоти: «Когда сомневаешься, читай документацию». У задачи всего пять спецификаций, и за их пределами вы вольны использовать любые методы и создавать любые рубашки, какие захотите.
Честно говоря, такая свобода может немного пугать. Хотя минимальное решение этой задачи занимает 15-20 строк кода, поиск нужных методов и атрибутов может оказаться довольно сложной задачей. Давайте посмотрим, как нам удастся создать рубашку, используя только необходимые спецификации.
Как следует из раздела подсказок, мы можем добавить подкласс, который наследует сам класс FPDF
, чтобы написать заголовок. В данном случае наш заголовок будет представлять собой текст «Рубашка CS50», центрированный по горизонтали.
Код в учебнике для функции header буквально поможет вам в этом, только нам не нужно отрисовывать логотип. Внутри нашей функции для заголовка мы можем просто установить шрифт, переместить курсор вправо, вывести заголовок с выравниванием по центру и без рамки, а также выполнить перенос строки. Все это уже дано вам. После переноса строки мы можем вызвать метод image
для вставки нашего изображения (shirtificate.png). Чтобы сделать это правильно, мы можем установить его ширину на эффективную ширину страницы (это просто ширина страницы минус горизонтальные поля), чтобы облегчить нашу работу. Обратите внимание, что в документации сказано следующее о параметре width для изображений:
w
:float
необязательная ширина изображения. Если она не указана или равна нулю, она автоматически вычисляется из размера изображения. Передайте
pdf.epw
для горизонтального масштабирования на всю ширину страницы.
Нам также нужно поместить наш текст {name} took CS50
, где name
— возвращаемое значение input
, на нашу футболку. Здесь нам нужно окрасить наш текст в белый цвет. Для этого, возможно, вы уже ознакомились с документацией по стилизации текста, однако это может быть все еще немного непонятно.
Не хочу отступать от темы, но если вы действительно застряли (что я и сделал в свое время), ссылка на документацию выше также содержит ссылку на файл test_text_mode.py
в исходном коде библиотеки. Взгляните на это:
def test_text_modes(tmp_path):
pdf = FPDF(format=(350, 150))
pdf.add_page()
pdf.set_font("Helvetica", size=80)
with pdf.local_context(fill_color=(255, 128, 0)):
pdf.cell(txt="FILL default")
with pdf.local_context(text_color=(0, 128, 255)):
pdf.cell(txt=" text mode")
pdf.ln()
...
И вот он, простой в использовании цвет текста для местного контекста! Помните, что наш цвет должен быть белым, значение RGB которого представляет все цвета до краев. С помощью локального контекста мы создаем ячейку, на этот раз для текста на рубашке. Ее высота, как следует из подсказок в объяснении задачи, может быть отрицательной, чтобы настроить ее должным образом, скажем, что-то вроде 250ish. Для ширины нам поможет наш старый друг — эффективная ширина страницы. Мы также выровняем его по центру, конечно.
Теперь, когда наш класс кажется законченным, пришло время создать экземпляр, добавить страницу с соответствующей ориентацией и форматом, и вывести shirtificate.pdf
. На этом наша проблема и наборы задач закончены!
Это было действительно веселое и восхитительное путешествие, но теперь, когда наборы задач фактически закончились, это еще не значит, что мы закончили. На следующей неделе, в последней части этой серии, мы подумаем о том, чему мы научились до сих пор, что еще предстоит узнать и как продолжить путь к финальному проекту и дальше.
До тех пор, счастливого кодинга.