Таймеры, события и GitHub
Таймер в эмуляции событийного подхода через mainloop() на самом деле не так просто сделать.
«Однопоточный» вариант. Tkinter умеет выполнить callback в указанное время (через указанный интервал). Для того, чтобы он запускался всё время, в сам этот callback вставляется планировщик его следующего вызова:
1 import tkinter as tk 2 import time 3 4 class App(): 5 def __init__(self): 6 self.root = tk.Tk() 7 self.label = tk.Label(text="") 8 self.label.grid() 9 self.update_clock() 10 self.root.mainloop() 11 12 def update_clock(self): 13 now = time.strftime("%H:%M:%S") 14 self.label.configure(text=now) 15 self.root.after(1000, self.update_clock) 16 17 app=App()
- Внешний вариант. Мы можем использовать любой фреймфорк, в котором есть часики, и дёргать callback-и оттуда.
Пример для threading:
1 from threading import Thread, Event 2 3 class MyThread(Thread): 4 def __init__(self, event): 5 Thread.__init__(self) 6 self.stopped = event 7 8 def run(self): 9 while not self.stopped.wait(0.5): 10 print("my thread") 11 12 stopFlag = Event() 13 thread = MyThread(stopFlag) 14 thread.start() 15 input("А вы пока нажмите Enter\n") 16 stopFlag.set() 17 print("Тогда всё")
Совместим threading и tkinter:
1 import tkinter as tk 2 import time 3 from threading import Thread, Event 4 5 class MyThread(Thread): 6 def __init__(self, event, root): 7 Thread.__init__(self) 8 self.stopped = event 9 self.root = root 10 11 def run(self): 12 while not self.stopped.wait(1): 13 self.root.update_clock() 14 15 class App(): 16 def __init__(self): 17 self.root = tk.Tk() 18 self.label = tk.Label(text="") 19 self.label.grid() 20 self.button = tk.Button(text="Quit", command = self.delayed_quit) 21 self.button.grid() 22 self.update_clock() 23 self.stopped = Event() 24 self.thread = MyThread(self.stopped, self) 25 self.thread.start() 26 self.root.mainloop() 27 28 def delayed_quit(self): 29 self.stopped.set() 30 self.root.after(3000, self.root.quit) 31 self.label.configure(text="Wait 3 secs") 32 33 def update_clock(self): 34 now = time.strftime("%H:%M:%S") 35 self.label.configure(text=now) 36 37 app=App()
Недостаток этого способа — в том, что мы вынуждены работать в двух моделях событий — tkiner-овской и thread-овой.
- «Правильный» подход: все события должны быть tkinter-овскими, таймер их только генерирует и подбрасывает в очередь событий:
1 import time 2 from tkinter import * 3 from threading import Thread, Event; 4 5 class Clock(Thread): 6 def __init__(self, root, grain=1, event="<<Tick>>"): 7 super().__init__() 8 self.root = root # Окно, которому посылать событие 9 self.grain = grain # Размер одного тика в секундах (м. б дробный) 10 self.event = event # TKinter-событие, которое надо посылать 11 self.done = Event() # threading-событие, которое останавливет тред 12 13 def run(self): 14 while not self.done.wait(self.grain): 15 self.root.event_generate(self.event) 16 17 class App(Frame): 18 def __init__(self, master=None, **kwargs): 19 Frame.__init__(self, master, **kwargs) 20 self.grid() 21 self.Clock = Clock(self) 22 self.Time = StringVar() 23 self.update_clock() 24 self.Screen = Label(textvariable=self.Time) 25 self.Screen.grid(row=0, column=0) 26 self.Start = Button(text="Start", command=self.start) 27 self.Start.grid(row=0, column=1) 28 self.Quit = Button(text="Quit", command=self.quit) 29 self.Quit.grid(row=0, column=2) 30 self.bind(self.Clock.event, self.tick) 31 # тред надо остановить, даже если окно просто закрыли 32 self.bind("<Destroy>", self.quit) 33 34 def tick(self, event): 35 self.update_clock() 36 37 def start(self): 38 self.Clock.start() 39 40 def quit(self, *events): 41 self.Clock.done.set() 42 self.master.quit() 43 44 def update_clock(self): 45 self.Time.set(time.strftime("%H:%M:%S")) 46 47 Tick = App() 48 Tick.mainloop()
Здесь используется т. н. синтетическое событие (в терминах tkinter — virtual event), которое мы называем сами (это как class MyEvent(Event): в Python)
GitHub
При разработке проекта необходимо выстраивать информационное пространство
На примере GitHub
Доступ к wiki не обязательно совпадает с доступом к репозиторию. Например, команда можно работать по merge-схеме, а в вики при этом пишут все
Ведение совместного репозитория:
«merge»-схема (классическая)
- Каждый участник проекта ведёт свой публичный репозиторий, которой он синхронизует с проектом
Один из этих репозиториев считается финальным. Доступ на push в этот репозиторий (как и в любой другой при этой схеме) имеет либо вообще один человек (т. н. выпускающий), либо очень небольшая команда (в основном, на замену выпускающему)
Выпускающий часто сопровождает и второй — линейный — репозиторий, в котором он ведёт свою часть разработки
GutHub рассылает уведомления об обновлениях в основном репозитории
Когда приходит пора, разработчик ставит в известность выпускающего, что его опубликованный код (обычно по определённому тегу) пора помержить (GitHub: pull request, часто — почтой, иногда какая-то автоматика конкретного портала, например, специально оформленный тег)
- Выпускающий изучает соответствующий код, попутно исправляя и/или обсуждая с автором недочёты, и по по окончании процесса пуллит его.
«push»-схема (историческая)
Работа ведётся в общем репозитории, доступ на push в который имеют все разработчики
- Для параллельной разработке используются ветки, одна из которых объявляется основной
- Когда приходит пора синхронизации, разработчик мержится с основной веткой, после чего пушит результат обратно в неё
GutHub рассылает уведомления об обновлениях
Merge-схема
- Хорошо масштабируется на большое сообщество
- Хорошо работает, когда опыт разработчиков неодинаков
- Заложена в архитектуру и систему команд git
- Позволяет взаимодействовать в offline (например, пересылать изменения по почте)
Push-схема
- Хорошо подходит для маленьких сообществ с одинаково высоким опытом
- Требует дополнительной дисциплины / online-взаимодействия
Не до конца поддерживается в git (например, в git вообще нет понятия «права доступа» — к веткам, коммитам и каким-то «частям кода»)
В GitHub есть
ДЗ
Семестровый проект
Семестровый проект — это git-репозиторий с кодом на Python3,
который я могу установить (любым предложенным вами кроссплатформенным способом) и запустить
предпочтительно wheel
- в котором есть более одного участника, и я могу посмотреть статистику участия (оба предложенных ниже измерения неидеальны, я это знаю)
- по количество коммитов
- по объёму кода
в котором flake8 (или pylint) находит не больше 5 ошибок (некоторые требования можно запрещать в config-файле)
- в котором есть немножко тестов (с использованием любого тест-фреймворка, годится встроенный питоний)
- должны быть покрыты модульными тестами все функции и классы, не использующие интерактивные возможности (т. е не GUI, не сетевое взаимодействие и т. п.)
- таких функций/классов должно быть не менее 5
- в котором есть немножко документации
описание проекта в README (в случае GitHub — README.md) и постановка задачи на GH
- В случае GUI — проект интерфейса
- не обязательно полностью совпадающий с тем, что получилось
- достаточно детальный, чтобы было понятно, какой блок виджетов из чего состоит и за что отвечает
- можно в виде картинки с исходниками из какого-то диаграммера, а можно и просто фоточки нарисованного от руки
- В случае GUI — проект интерфейса
(по возможности) программной (с использованием любого фреймворка, годится встроенный питоний, но можно и sphinx)
(по возможности) пользовательской (либо sphinx, либо прямо на GH)
- в котором есть немножко локализации (с теми же оговорками по части фреймворков)
- с использованием gettext или babel
обратите внимание на то, что, например, -.mo файлы надо сгенерировать и положить в дистрибутив вашего проекта (например, в wheel, как в лекциях)
Немножко — это реально немножко, чтобы я видел, что работа проделана. Например, если вы задумали какое-то приложение из реал лайфа, и в нём довольно много логики, обмазать всю её тестами будет долго. Но пяток должен быть.
Как начать
Для регистрации необходимо оформить issue вот тут, в котором указать ссылку на ваш репозиторий и взаимно-однозначное соответствие ников людям, который будут получать оценки
В репозитории уже должен быть файл README.md с постановкой задачи и (в случае GUI) проект интерфейса с коротенькими подписями, какой блок виджетов за то отвечает.
Если вы не знаете, какую задачу вам решать, или с кем скооперироваться, оформляете issue с просьбой выдать вам учебно-тренировочную задачу и/или подобрать товарища по команде.
Внимание!
Выдача задания и особенно подбор партнёра — случайный процесс, который может не привести к появлению команды/решаемой задачи!