Из особенностей: PyGTK (hildonize for Nokia N900), само-модифицируемый код, что бы не возиться с конфиг-файлами.
По традиции, лирическое вступление.
Походы в спортзал на протяжении последних двух лет для меня были скорее социальным событием, нежели целенаправленной тренировкой и продуманной прокачкой нужных групп мышц. Так сложилось, что когда в одном месте собирается два-три PhD студента из разных, но близлежащих областей науки, да к этому ещё добавляется природное тяготение всех троих потрепать языком — остановить этот поток сознания не способно уже ничто.
Но волею судеб, и, можно сказать, плохим роком, компания наша развалилось на части. Катастрофическая нехватка времени и возможности согласовать расписания с одной стороны, и старые травмы с другой, привели к тому, что в спортзал последние три месяца я хожу один. И, если честно, то об этом уже даже и не жалею.
Когда встал вопрос ребром и нужно было принимать решение — бросать это «дурное» занятие или же наоборот отнестись к нему серьёзно, продумать программу тренировок и обрасти теоретическими знаниями по предмету, помогло стечение обстоятельств. Очень вовремя мой брат (akusto) показал тренировку 5х5 , которую сам тогда использовал пару недель. Она получила первое место среди множества программ на bodybuilding.com. Кратко её можно описать как 4х дневную с 5 подходами по 5 повторений для всех основных групп мышц с фиксированным временем отдыха между упражнениями и подходами. Последнее для меня являлось очень важным. Так как именно точно заданное время тренировки даёт некую подчёркнутость и завершённость сему действию. Плюс ко всему, упражнения были выбраны из лучших по рейтингу того же bodybuilding.com.
Жёсткие требования на время и обязательное увеличение веса каждую неделю привели к очевидному решению — написать свою программу, которая бы сообщала о необходимости приступить к очередному упражнению и сохраняла бы вес штанги/гантели, который можно было бы увеличивать по мере необходимости (по условию тренировки, вес нужно увеличивать, если вы смогли выполнить все 5х5 повторений).
Время открыть любимый IDE и встучать туда пару строк питона. Так как код получился чуть-чуть длинноватым для хабра (~251 строка), предлагается перейти по данной ссылке для ознакомления с полной простынёй.
Сейчас разберём по частям.
Подключив все используемые модули (строки 7-14), создаём класс программы. Это делается в основном только для того, что бы функции (которых пока нету в коде) имели доступ и использовали единый namespace переменных. Начиная с 19-ой строки идёт инициализация этих самых общих переменных (назвать их глобальными будет немного не правильно с точки зрения Computer Science). Переменная self.exercise_list содержит список упражнений, который начинается со дня недели. В каждой строке хранится название упражнения и рабочий вес в lbs (паунды; килограммы в штатах до сих пор не очень то прижились), за которыми стоят символы «х5». Они поставлены там не случайно. Именно по этим двум символам я буду определять, что предшествующее число — вес. Ну и символически это указывает на то, что используется «5х5» программа тренировок.
В свободные дни недели, например в среду (строка 36), переменная содержит только строку с названием дня недели. Только название этого дня недели и будет отображёно, если в среду мы включим скрипт на выполнение.
Строки 57-68 задают переменные, определяющие параметры выполнения скрипта.
Далее следует основной функционал скрипта, который мы рассмотрим чуть позже.
Со строки 189 начинается основной цикл выполнения программы и создаётся объект класса. Особое внимание стоит уделить строкам 198-201. В них мы находим путь к выполняемому скрипту pygym.srcfile и считываем исходный питоновский код в pygym.src. Это сделано для того, чтобы потом можно было поменять исходный код и сохранить его для последующего исполнения, не заморачиваясь с хранением конфигурационного файла отдельно. То есть, код будет менять сам себя.
Все последующие строки — это GTK и hildonize описание виджетов в основном окне, которое мы создаём в строчке 199 и показываем в 247ой. После чего выполняется основной цикл GTK.
Вот такой вот получается скелетик. Но мы ещё не разобрались что же он умеет делать. Давайте рассмотрим функционал в основном класс программы.
- def create_labels(self):
- self.labels = []
- self.exlist = self.exercise_list[self.weekday]
- for ex in self.exlist.split('\n'):
- self.labels.append(gtk.Label(ex))
- self.ex_vbox.pack_start(self.labels[-1], True, True, 0)
- self.bold_label()
- self.ex_vbox.show_all()
Функция будет выводить список упражнений как массив gtk.label. В строке 3 из списка всех упражнений недели мы выбираем текущий день self.weekday. Разбиваем многострочную переменную с помощью split на отдельные строки и добавляем их к вертикальному контейнеру self.ex_vbox. Запускаем функцию
- def bold_label(self):
- if self.active_ex != -1:
- self.labels[self.active_ex].set_markup('<b>'+self.labels[self.active_ex].get_text()+'</b>')
- def debold_label(self):
- self.labels[self.active_ex].set_text(self.labels[self.active_ex].get_text())
Тут всё как по написанному — первая функция с помощью markup добавляет теги bold к активному упражнению; вторая убирает эти теги.
- def pause(self, pause_time, set_number=None):
- start_t = time.time()
- while (time.time()-start_t < pause_time):
- temp_t = time.time()
- while time.time()-temp_t < 1:
- while gtk.events_pending(): gtk.main_iteration()
- time.sleep(self.sleep_time)
- if set_number:
- self.status.set_text(self.excercise.get_text()+'\n %03i seconds left before set #%i'%(pause_time -
- time.time()+start_t, set_number))
- else:
- self.status.set_text(self.excercise.get_text()+'\n %03i seconds left'%(pause_time - time.time()+start_t))
- if pause_time>self.pause_rep and int(pause_time- time.time()+start_t) == self.pre_ex:
- os.system('dbus-send --print-reply --system --dest=com.nokia.mce /com/nokia/mce/request com.nokia.mce.request.req_vibrator_pattern_activate string:"PatternChatAndEmail" >/dev/null')
- if pause_time>self.pause_rep:
- os.system('dbus-send --print-reply --system --dest=com.nokia.mce /com/nokia/mce/request com.nokia.mce.request.req_vibrator_pattern_activate string:"PatternChatAndEmail" >/dev/null')
Одна из основных функций программы — ожидание паузы и вибрация в самый не подходящий момент… Важной является строчка номер 6. Если её не включить, то приложение просто не будет отвечать на клики и не будет обновлять экран. Это просто инструкция для GTK проверить и обработать все сообщения и события приложения. В 14ой и 16ой строках вызывается внешняя программа, которая шлёт DBus сообщение для длинного вибрирования (около 1 секунды). Это немного грязный подход, так как можно было бы подсоединить DBus модуль и сделать это всё внутри питона, не вызывая никаких внешних приложений. Но просто было лень!
Ну и напоследок самое вкусное — самоизменяемый код.
- def weight_change(self, button):
- if self.active_ex > 0:
- ex = self.labels[self.active_ex].get_text()
- lmatch = re.search("([0-9.]*)x5",ex)
- if lmatch:
- if button.get_label() == '+':
- new_weight = self.weight_increment + float(lmatch.group(1))
- elif button.get_label() == '-':
- new_weight = - self.weight_increment + float(lmatch.group(1))
- if new_weight == int(new_weight):
- new_weight = '%i'%new_weight
- else:
- new_weight = '%.1f'%new_weight
- new_text = ex[:lmatch.start(1)] + new_weight + ex[lmatch.end(1):]
- self.labels[self.active_ex].set_text(new_text)
- self.bold_label()
- self.src = re.sub(ex, new_text, self.src)
- self.write_source()
- elif self.active_ex == 0:
- #if self.start.get_property("visible"):
- for ex in self.labels:
- ex.destroy()
- if button.get_label() == '+':
- self.weekday = (self.weekday + 1)%7
- elif button.get_label() == '-':
- self.weekday = (self.weekday - 1)%7
- self.create_labels()
- else:
- hildon.hildon_banner_show_information(button,
- '0', "Use 'v' or '^' buttons to choose an exercise")
- def write_source(self):
- # write the source code back
- f = open(self.srcfile, 'w')
- f.write(self.src)
- f.close()
Здесь описаны две функции. Одна вызывается при нажатии кнопок изменения веса, вторая сохраняет изменённый исходный код. В 4-ой строке идёт поиск по регулярному выражению числа с символами «х5» на конце. Пожалуй, это перебор использовать RE для такого простого случая. Но хотелось показать как это может работать в общем случае для кода любой сложности. Найдя нужные числа мы модифицируем их согласно нажатой кнопке — добавляем/отнимаем от текущего веса self.weight_increment. С 10-ой по 13-ую строки мы преобразуем число в целое без точки или в десятичную дробь с одним знаком после запятой (или же после точки… я запутался). И сохраняем уже новое значение, заменяя его в старых строках (14). Подставляем эту замену в исходный код программы (17) и сохраняем его в файл (18 => 32). Строки 19-30 обрабатывают событие изменения дня недели. То есть, в программе можно посмотреть и даже запустить на выполнение не только текущий день, но и программы других дней недели.
Вот, пожалуй, и всё на сегодня. Спасибо за внимание, с вами был PhDишнутый тяжелоатлет Vadikus.