Различия между версиями 1 и 3 (по 2 версиям)
Версия 1 от 2019-04-04 17:57:07
Размер: 1422
Редактор: FrBrGeorge
Комментарий:
Версия 3 от 2019-04-08 09:19:24
Размер: 10866
Редактор: FrBrGeorge
Комментарий:
Удаления помечены так. Добавления помечены так.
Строка 3: Строка 3:
Таймер: два подхода

Однопоточный:

{{{#!python
Таймер в эмуляции событийного подхода через `mainloop()` на самом деле не так просто сделать.
 * «Однопоточный» вариант. Tkinter умеет выполнить callback в указанное время (через указанный интервал). Для того, чтобы он запускался ''всё время'', в сам этот callback вставляется планировщик его следующего вызова:
 {{{#!python
Строка 15: Строка 13:
        self.label.pack()         self.label.grid()
Строка 26: Строка 24:
 * Внешний вариант. Мы можем использовать любой фреймфорк, в котором есть часики, и дёргать callback-и оттуда.
  * Пример для `threading`:
  {{{#!python
from threading import Thread, Event
Строка 27: Строка 29:
Многопоточный:
{{{#!python
Строка 37: Строка 37:
            # call a function
Строка 42: Строка 41:
# this will stop the timer input("А вы пока нажмите Enter\n")
Строка 44: Строка 43:
print("Тогда всё")
Строка 45: Строка 45:
  * Совместим `threading` и `tkinter`:
  {{{#!python
import tkinter as tk
import time
from threading import Thread, Event
Строка 46: Строка 51:
С порождение синтетического события:
https://stackoverflow.com/questions/270648/tkinter-invoke-event-in-main-loop
{{{#!python
class MyThread(Thread):
    def __init__(self, event, root):
        Thread.__init__(self)
        self.stopped = event
        self.root = root

    def run(self):
        while not self.stopped.wait(1):
            self.root.update_clock()

class App():
    def __init__(self):
        self.root = tk.Tk()
        self.label = tk.Label(text="")
        self.label.grid()
        self.button = tk.Button(text="Quit", command = self.delayed_quit)
        self.button.grid()
        self.update_clock()
        self.stopped = Event()
        self.thread = MyThread(self.stopped, self)
        self.thread.start()
        self.root.mainloop()

    def delayed_quit(self):
        self.stopped.set()
        self.root.after(3000, self.root.quit)
        self.label.configure(text="Wait 3 secs")

    def update_clock(self):
        now = time.strftime("%H:%M:%S")
        self.label.configure(text=now)

app=App()
  }}}
  Недостаток этого способа — в том, что мы вынуждены работать в ''двух'' моделях событий — tkiner-овской и thread-овой.
 * «Правильный» подход: все события должны быть tkinter-овскими, таймер их только генерирует и подбрасывает в очередь событий:
 {{{#!python
import time
Строка 50: Строка 90:
from threading import Thread, Event;
Строка 51: Строка 92:
def doFoo(*args):
    print("Hello, world")
class Clock(Thread):
    def __init__(self, root, grain=1, event="<<Tick>>"):
        super().__init__()
        self.root = root # Окно, которому посылать событие
        self.grain = grain # Размер одного тика в секундах (м. б дробный)
        self.event = event # TKinter-событие, которое надо посылать
        self.done = Event() # threading-событие, которое останавливет тред
Строка 54: Строка 100:
root = Tk()
root.bind("<<Foo>>", doFoo)
    def run(self):
        while not self.done.wait(self.grain):
            self.root.event_generate(self.event)
Строка 57: Строка 104:
# some time later, inject the "<<Foo>>" virtual event at the
# tail of the event queue
root.event_generate("<<Foo>>", when="tail")
}}}
class App(Frame):
    def __init__(self, master=None, **kwargs):
        Frame.__init__(self, master, **kwargs)
        self.grid()
        self.Clock = Clock(self)
        self.Time = StringVar()
        self.update_clock()
        self.Screen = Label(textvariable=self.Time)
        self.Screen.grid(row=0, column=0)
        self.Start = Button(text="Start", command=self.start)
        self.Start.grid(row=0, column=1)
        self.Quit = Button(text="Quit", command=self.quit)
        self.Quit.grid(row=0, column=2)
        self.bind(self.Clock.event, self.tick)
        # тред надо остановить, даже если окно просто закрыли
        self.bind("<Destroy>", self.quit)

    def tick(self, event):
        self.update_clock()

    def start(self):
        self.Clock.start()

    def quit(self, *events):
        self.Clock.done.set()
        self.master.quit()

    def update_clock(self):
        self.Time.set(time.strftime("%H:%M:%S"))

Tick = App()
Tick.mainloop()
 }}}
 Здесь используется т. н. ''синтетическое'' событие (в терминах `tkinter` — ''virtual'' event), которое мы называем сами (это как `class MyEvent(Event):` в Python)
Строка 63: Строка 140:
- issue
- wiki
- pages
При разработке проекта необходимо выстраивать информационное пространство
Строка 67: Строка 142:
Про ДЗ На примере [[https://help.github.com/en|GitHub]]
 * [[https://help.github.com/en/articles/managing-your-work-with-issues|issue tracker]]
 * [[https://help.github.com/en/articles/documenting-your-project-with-wikis|wiki]]
  * Доступ к wiki не обязательно совпадает с доступом к репозиторию. Например, команда можно работать по `merge`-схеме, а в вики при этом пишут все
  * [[https://help.github.com/en#github-pages-basics|github.io]]
 * [[https://help.github.com/en/articles/managing-project-boards|Управление проектами]]

Ведение совместного репозитория:
 1. «`merge`»-схема (классическая)
  * Каждый участник проекта ведёт свой публичный репозиторий, которой он синхронизует с проектом
  * Один из этих репозиториев считается финальным. Доступ на `push` в этот репозиторий (как и в любой другой при этой схеме) имеет либо вообще один человек (т. н. '''выпускающий'''), либо очень небольшая команда (в основном, на замену выпускающему)
   * Выпускающий часто сопровождает и второй — линейный — репозиторий, в котором он ведёт свою часть ''разработки''
   * !GutHub рассылает уведомления об обновлениях в основном репозитории
  * Когда приходит пора, разработчик ставит в известность выпускающего, что его опубликованный код (обычно по определённому тегу) пора помержить (!GitHub: pull request, часто — почтой, иногда какая-то автоматика конкретного портала, например, специально оформленный тег)
  * Выпускающий изучает соответствующий код, попутно исправляя и/или обсуждая с автором недочёты, и по по окончании процесса пуллит его.
 1. «`push`»-схема (историческая)
  * Работа ведётся в общем репозитории, доступ на `push` в который имеют все разработчики
  * Для параллельной разработке используются ветки, одна из которых объявляется основной
  * Когда приходит пора синхронизации, разработчик мержится с основной веткой, после чего пушит результат обратно в неё
  * !GutHub рассылает уведомления об обновлениях

Merge-схема
 * Хорошо масштабируется на большое сообщество
 * Хорошо работает, когда опыт разработчиков неодинаков
 * Заложена в архитектуру и систему команд git
 * Позволяет взаимодействовать в offline (например, пересылать изменения по почте)
Push-схема
 * Хорошо подходит для маленьких сообществ с одинаково высоким опытом
 * Требует дополнительной дисциплины / online-взаимодействия
 * Не до конца поддерживается в git (например, в git ''вообще'' нет понятия «права доступа» — к веткам, коммитам и каким-то «частям кода»)
  * В !GitHub есть
== ДЗ ==
=== Семестровый проект ===
<<Include(../GraduateProject)>>

=== Как начать ===
Для регистрации необходимо оформить issue [[https://github.com/FrBrGeorge/PythonDevelopment2019/issues|вот тут]], в котором указать ссылку на ваш репозиторий и взаимно-однозначное соответствие ников людям, который будут получать оценки :)

В репозитории ''уже'' должен быть файл README.md с постановкой задачи и (в случае GUI) проект интерфейса с коротенькими подписями, какой блок виджетов за то отвечает.

Если вы не знаете, какую задачу вам решать, или с кем скооперироваться, оформляете issue с просьбой ''выдать'' вам учебно-тренировочную задачу и/или подобрать товарища по команде.

{{{#!wiki caution
'''Внимание!'''

Выдача задания и особенно подбор партнёра — случайный процесс, который может не привести к появлению команды/решаемой задачи!
}}}
 

Таймеры, события и 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()
    

    Здесь используется т. н. синтетическое событие (в терминах tkintervirtual event), которое мы называем сами (это как class MyEvent(Event): в Python)

GitHub

При разработке проекта необходимо выстраивать информационное пространство

На примере GitHub

Ведение совместного репозитория:

  1. «merge»-схема (классическая)

    • Каждый участник проекта ведёт свой публичный репозиторий, которой он синхронизует с проектом
    • Один из этих репозиториев считается финальным. Доступ на push в этот репозиторий (как и в любой другой при этой схеме) имеет либо вообще один человек (т. н. выпускающий), либо очень небольшая команда (в основном, на замену выпускающему)

      • Выпускающий часто сопровождает и второй — линейный — репозиторий, в котором он ведёт свою часть разработки

      • GutHub рассылает уведомления об обновлениях в основном репозитории

    • Когда приходит пора, разработчик ставит в известность выпускающего, что его опубликованный код (обычно по определённому тегу) пора помержить (GitHub: pull request, часто — почтой, иногда какая-то автоматика конкретного портала, например, специально оформленный тег)

    • Выпускающий изучает соответствующий код, попутно исправляя и/или обсуждая с автором недочёты, и по по окончании процесса пуллит его.
  2. «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 — проект интерфейса
        • не обязательно полностью совпадающий с тем, что получилось
        • достаточно детальный, чтобы было понятно, какой блок виджетов из чего состоит и за что отвечает
        • можно в виде картинки с исходниками из какого-то диаграммера, а можно и просто фоточки нарисованного от руки
    • /!\ (по возможности) программной (с использованием любого фреймворка, годится встроенный питоний, но можно и sphinx)

    • /!\ (по возможности) пользовательской (либо sphinx, либо прямо на GH)

  • в котором есть немножко локализации (с теми же оговорками по части фреймворков)
    • с использованием gettext или babel
    • обратите внимание на то, что, например, -.mo файлы надо сгенерировать и положить в дистрибутив вашего проекта (например, в wheel, как в лекциях)

Немножко — это реально немножко, чтобы я видел, что работа проделана. Например, если вы задумали какое-то приложение из реал лайфа, и в нём довольно много логики, обмазать всю её тестами будет долго. Но пяток должен быть.

Как начать

Для регистрации необходимо оформить issue вот тут, в котором указать ссылку на ваш репозиторий и взаимно-однозначное соответствие ников людям, который будут получать оценки :)

В репозитории уже должен быть файл README.md с постановкой задачи и (в случае GUI) проект интерфейса с коротенькими подписями, какой блок виджетов за то отвечает.

Если вы не знаете, какую задачу вам решать, или с кем скооперироваться, оформляете issue с просьбой выдать вам учебно-тренировочную задачу и/или подобрать товарища по команде.

Внимание!

Выдача задания и особенно подбор партнёра — случайный процесс, который может не привести к появлению команды/решаемой задачи!

LecturesCMC/PythonDevelopment2019/07_TimersAndGhtHub (последним исправлял пользователь FrBrGeorge 2019-04-08 09:19:24)