⇤ ← Версия 1 от 2019-04-04 17:57:07
1422
Комментарий:
|
6894
|
Удаления помечены так. | Добавления помечены так. |
Строка 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: |
'''TODO''' | |
Строка 67: | Строка 145: |
Про ДЗ | == ДЗ == '''TODO''' Я в ближайшие пару дней буду делать proposal относительно семестровой работы по курсу. Если коротко: Работа — это git-репозиторий с кодом на Python3, * который я могу склонировать и запустить * в котором есть более одного участника, и я могу посмотреть статистику участия * в котором есть немножко тестов (с использованием любого тест-фреймворка, годится встроенный питоний) *в котором есть немножко документации * программной (с использованием любого фреймворка, годится встроенный питоний, но можно и sphinx) * пользовательской (либо sphinx, либо прямо на GH) * описание проекта и постановка задачи на GH * в котором есть немножко локализации (с теми же оговорками) Немножко — это реально немножко, чтобы я видел, что работа проделана. Например, если вы задумали какое-то приложение из реал пайфа, и в нём довольно много логики, обмазать ''всю'' её тестами будет долго. Но пяток должен быть. |
Таймеры, события и 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
TODO - issue - wiki - pages
ДЗ
TODO
Я в ближайшие пару дней буду делать proposal относительно семестровой работы по курсу. Если коротко: Работа — это git-репозиторий с кодом на Python3,
- который я могу склонировать и запустить
- в котором есть более одного участника, и я могу посмотреть статистику участия
- в котором есть немножко тестов (с использованием любого тест-фреймворка, годится встроенный питоний)
- в котором есть немножко документации
- программной (с использованием любого фреймворка, годится встроенный питоний, но можно и sphinx)
- пользовательской (либо sphinx, либо прямо на GH)
- описание проекта и постановка задачи на GH
- в котором есть немножко локализации (с теми же оговорками)
Немножко — это реально немножко, чтобы я видел, что работа проделана. Например, если вы задумали какое-то приложение из реал пайфа, и в нём довольно много логики, обмазать всю её тестами будет долго. Но пяток должен быть.