GymPy — лог-программа для любителей потягать железо


Из особенностей: PyGTK (hildonize for Nokia N900), само-модифицируемый код, что бы не возиться с конфиг-файлами. По традиции, лирическое вступление.



 

Походы в спортзал на протяжении последних двух лет для меня были скорее социальным событием, нежели целенаправленной тренировкой и продуманной прокачкой нужных групп мышц. Так сложилось, что когда в одном месте собирается два-три PhD студента из разных, но близлежащих областей науки, да к этому ещё добавляется природное тяготение всех троих потрепать языком — остановить этот поток сознания не способно уже ничто. Но волею судеб, и, можно сказать, плохим роком, компания наша развалилось на части. Катастрофическая нехватка времени и возможности согласовать расписания с одной стороны, и старые травмы с другой, привели к тому, что в спортзал последние три месяца я хожу один. И, если честно, то об этом уже даже и не жалею. Когда встал вопрос ребром и нужно было принимать решение — бросать это "дурное" занятие или же наоборот отнестись к нему серьёзно, продумать программу тренировок и обрасти теоретическими знаниями по предмету, помогло стечение обстоятельств. Очень вовремя мой брат показал тренировку 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. Вот такой вот получается скелетик. Но мы ещё не разобрались что же он умеет делать. Давайте рассмотрим функционал в основном класс программы.


  1.     def create_labels(self):
  2.         self.labels = []
  3.         self.exlist = self.exercise_list[self.weekday]
  4.         for ex in self.exlist.split('\n'):
  5.             self.labels.append(gtk.Label(ex))
  6.             self.ex_vbox.pack_start(self.labels[-1], True, True, 0)
  7.         self.bold_label()
  8.         self.ex_vbox.show_all()

Функция будет выводить список упражнений как массив gtk.label. В строке 3 из списка всех упражнений недели мы выбираем текущий день self.weekday. Разбиваем многострочную переменную с помощью split на отдельные строки и добавляем их к вертикальному контейнеру self.ex_vbox. Запускаем функцию облысения выделения (утолщения) активного упражнения (точнее это то упражнение, которое можно редактировать в текущий момент), и показываем вертикальный контейнер и всё его новое содержимое.


  1.     def bold_label(self):
  2.         if self.active_ex != -1:
  3.             self.labels[self.active_ex].set_markup('<b>'+self.labels[self.active_ex].get_text()+'</b>')
  4.  
  5.     def debold_label(self):
  6.         self.labels[self.active_ex].set_text(self.labels[self.active_ex].get_text())

Тут всё как по написанному — первая функция с помощью markup добавляет теги bold к активному упражнению; вторая убирает эти теги.


  1.     def pause(self, pause_time, set_number=None):
  2.         start_t = time.time()
  3.         while (time.time()-start_t < pause_time):
  4.             temp_t = time.time()
  5.             while time.time()-temp_t < 1:
  6.                 while gtk.events_pending(): gtk.main_iteration()
  7.                 time.sleep(self.sleep_time)
  8.             if set_number:
  9.                 self.status.set_text(self.excercise.get_text()+'\n %03i seconds left before set #%i'%(pause_time -
  10.                 time.time()+start_t, set_number))
  11.             else:
  12.                 self.status.set_text(self.excercise.get_text()+'\n %03i seconds left'%(pause_time - time.time()+start_t))
  13.             if pause_time>self.pause_rep and int(pause_time- time.time()+start_t) == self.pre_ex:
  14.                 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')
  15.         if pause_time>self.pause_rep:
  16.             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 модуль и сделать это всё внутри питона, не вызывая никаких внешних приложений. Но просто было лень! Ну и напоследок самое вкусное — самоизменяемый код.


  1.     def weight_change(self, button):
  2.         if self.active_ex > 0:
  3.             ex = self.labels[self.active_ex].get_text()
  4.             lmatch = re.search("([0-9.]*)x5",ex)
  5.             if lmatch:
  6.                 if button.get_label() == '+':
  7.                     new_weight = self.weight_increment + float(lmatch.group(1))
  8.                 elif button.get_label() == '-':
  9.                     new_weight = - self.weight_increment + float(lmatch.group(1))
  10.                 if new_weight == int(new_weight):
  11.                     new_weight = '%i'%new_weight
  12.                 else:
  13.                     new_weight = '%.1f'%new_weight
  14.                 new_text = ex[:lmatch.start(1)] + new_weight + ex[lmatch.end(1):]
  15.                 self.labels[self.active_ex].set_text(new_text)
  16.                 self.bold_label()
  17.                 self.src = re.sub(ex, new_text, self.src)
  18.                 self.write_source()
  19.         elif self.active_ex == 0:
  20.             #if self.start.get_property("visible"):
  21.             for ex in self.labels:
  22.                 ex.destroy()
  23.             if button.get_label() == '+':
  24.                 self.weekday = (self.weekday + 1)%7
  25.             elif button.get_label() == '-':
  26.                 self.weekday = (self.weekday - 1)%7
  27.             self.create_labels()
  28.         else:
  29.             hildon.hildon_banner_show_information(button,
  30.                 '0', "Use 'v' or '^' buttons to choose an exercise")
  31.  
  32.     def write_source(self):
  33.         # write the source code back
  34.         f = open(self.srcfile, 'w')
  35.         f.write(self.src)
  36.         f.close()

Здесь описаны две функции. Одна вызывается при нажатии кнопок изменения веса, вторая сохраняет изменённый исходный код. В 4-ой строке идёт поиск по регулярному выражению числа с символами "х5" на конце. Пожалуй, это перебор использовать RE для такого простого случая. Но хотелось показать как это может работать в общем случае для кода любой сложности. Найдя нужные числа мы модифицируем их согласно нажатой кнопке — добавляем/отнимаем от текущего веса self.weight_increment. С 10-ой по 13-ую строки мы преобразуем число в целое без точки или в десятичную дробь с одним знаком после запятой (или же после точки… я запутался). И сохраняем уже новое значение, заменяя его в старых строках (14). Подставляем эту замену в исходный код программы (17) и сохраняем его в файл (18 => 32). Строки 19-30 обрабатывают событие изменения дня недели. То есть, в программе можно посмотреть и даже запустить на выполнение не только текущий день, но и программы других дней недели. Вот, пожалуй, и всё на сегодня. Спасибо за внимание, с вами был PhDишнутый тяжелоатлет Vadikus.