无法初始化 window 并等待进程在 Python 3 + GTK+ 3 中结束
Unable to initialize a window and wait for a process to end in Python 3 + GTK+ 3
我是面向对象编程、Python 和 GTK+3 的新手,尽管我对过程编程(主要是 C)有一定的了解。
我正在尝试构建一个简单的 Python + GTK+ 3 脚本到 运行 pkexec apt-get update
under Linux.
我有一个 mainWindow
class(基于 Gtk.Window
class),其中包含一个名为 button
的按钮对象(基于 Gtk.Button
class) 在 clicked
事件时触发 mainWindow
中定义的 new_update_window()
方法;
new_update_window()
方法从 updateWindow
class(基于 Gtk.Window
class)初始化一个 updateWindow
对象,其中包含名为 label
的标签对象(基于 Gtk.Label
class)并调用 updateWindow
;[=69= 中定义的方法 show_all()
和 update()
]
update()
方法应该改label
,运行pkexec apt-get update
再改label
问题是无论我做什么都会发生以下情况之一:
- 如果我直接 运行
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
,显示 update.Window
但 label
立即设置为应该仅在 pkexec apt-get update
之后设置的值完成执行;
- 如果我直接运行
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
,update.Window
直到pkexec apt-get update
执行完毕才显示;
- 我尝试
import
ing threading
,在 updateWindow
中定义一个单独的 run_update()
方法,并使用 thread = threading.Thread(target=self.run_update)
在单独的线程中启动函数,thread.start()
、thread.join()
,但仍取决于我在 run_update()
(subprocess.call()
或 subprocess.Popen
)中调用的方法,上述相关行为会表现出来。
Tl;博士
我不知道如何完成我所追求的,即:
- 显示
updateWindow
(Gtk.Window
)
- 正在
updateWindow
中更新 label
(Gtk.Label
)
- 运行
pkexec apt-get update
- 正在
updateWindow
中更新 label
subprocess.Popen()
:显示 update.Window
,但 label
立即设置为只有在 pkexec apt-get update
完成执行后才应设置的值;
subprocess.call()
: update.Window
直到 pkexec apt-get update
完成执行才显示;
- 将两者中的任何一个包装在函数中,运行将函数包装在单独的线程中不会改变任何东西。
这是代码;
不使用线程(情况 1,在本例中使用 subprocess.Popen()
):
#!/usr/bin/python3
from gi.repository import Gtk
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
self.label.set_text("Updated.")
def run_update(self):
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
使用线程(案例 3,在本例中使用 subprocess.Popen()
):
#!/usr/bin/python3
from gi.repository import Gtk
import threading
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
thread = threading.Thread(target=self.run_update)
thread.start()
thread.join()
self.label.set_text("Updated.")
def run_update(self):
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
在
def run_update(self):
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
您没有等待进程终止,请尝试
def run_update(self):
proc = subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
proc.wait()
相反。这应该正确地等待完成,但它不会有太大帮助,因为 updateWindow.update
将从 mainWindow.new_update_window
调用并且 GUI 线程将等待进程完成。
自定义信号可用于在 subprocess.Popen
或 subprocess.call
启动的进程完成时进行通信:
#!/usr/bin/python3
from gi.repository import Gtk, GObject
import threading
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow(self)
update.show_all()
update.start_update()
class updateWindow(Gtk.Window):
def __init__(self, parent):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
self.parent = parent
GObject.signal_new('update_complete', self, GObject.SIGNAL_RUN_LAST,
None, (int,))
self.connect('update_complete', self.on_update_complete)
def on_update_complete(self, widget, rc):
self.label.set_text("Updated {:d}".format(rc))
# emit a signal to mainwindow if needed, self.parent.emit(...)
def start_update(self):
self.label.set_text("Updating... Please wait.")
thread = threading.Thread(target=self.run_update)
thread.start()
def run_update(self):
rc = subprocess.call(["/usr/bin/pkexec", "apt-get", "update"],
shell=False)
self.emit('update_complete', rc)
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
你快到了...
解决方案非常简单:)
您遇到的问题是 subprocess.call()
会冻结 GUI(循环),从而阻止 window 出现,而 subprocess.Popen()
会抛出命令并跳转至 self.label.set_text("Updated.")
如何解决
您可以通过 运行创建一个单独的线程来简单地解决它,调用您的命令:
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
并移动标签更改命令
self.label.set_text("Updated.")
进入线程,在第一个命令之后定位。然后线程不会冻结界面,而 label
不会过早更改,因为 subprocess.call()
会阻止这种情况。
代码则变为:
#!/usr/bin/python3
from gi.repository import Gtk
from threading import Thread
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
Thread(target = self.run_update).start()
def run_update(self):
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
self.label.set_text("Updated.")
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
或者
如果您想避免使用 Thread
,您可以使用 Gtk.main_iteration()
来防止在进程 运行s:
时界面冻结
#!/usr/bin/python3
from gi.repository import Gtk
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
subprocess.Popen(["gedit"])
self.hold()
self.label.set_text("Updated.")
def run_update(self):
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
def hold(self):
while True:
Gtk.main_iteration()
try:
subprocess.check_output(["pgrep", "apt-get"]).decode("utf-8")
except subprocess.CalledProcessError:
break
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
编辑
不断深入了解有更好的线程使用方法,然后在我的回答中发布。
您可以在 Gtk
GUI 中使用线程,使用
GObject.threads_init()
然后,要从线程更新接口,使用
GObject.idle_add()
来自 this (slgihtly outdated) link:
...在应用程序初始化时调用 gobject.threads_init()
。然后您正常启动线程,但要确保线程从不直接执行任何 GUI 任务。相反,您使用 gobject.idle_add
安排 GUI 任务在主线程中执行
当我们将 gobject.threads_init()
替换为 GObject.threads_init()
并将 gobject.idle_add
替换为 GObject.idle_add()
时,我们几乎已经有了如何在一个线程中 运行 线程的更新版本Gtk 应用程序。
在您的代码中应用(使用第二个示例,使用线程):
#!/usr/bin/python3
from gi.repository import Gtk, GObject
from threading import Thread
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
# self.thread = threading.Thread(target=self.run_update)
thread = Thread(target=self.run_update)
thread.start()
def run_update(self):
GObject.idle_add(
self.label.set_text, "Updating... Please wait.",
priority=GObject.PRIORITY_DEFAULT
)
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
GObject.idle_add(
self.label.set_text, "Updated.",
priority=GObject.PRIORITY_DEFAULT
)
GObject.threads_init()
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
不使用 Python 的 subprocess
模块,您可以使用 Gio.Subprocess
,它集成了 GTK 的主循环:
#!/usr/bin/python3
from gi.repository import Gtk, Gio
# ...
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
subprocess = Gio.Subprocess.new(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"], 0)
subprocess.wait_check_async(None, self._on_update_finished)
def _on_update_finished(self, subprocess, result):
subprocess.wait_check_finish(result)
self.label.set_text("Updated.")
我是面向对象编程、Python 和 GTK+3 的新手,尽管我对过程编程(主要是 C)有一定的了解。
我正在尝试构建一个简单的 Python + GTK+ 3 脚本到 运行 pkexec apt-get update
under Linux.
我有一个 mainWindow
class(基于 Gtk.Window
class),其中包含一个名为 button
的按钮对象(基于 Gtk.Button
class) 在 clicked
事件时触发 mainWindow
中定义的 new_update_window()
方法;
new_update_window()
方法从 updateWindow
class(基于 Gtk.Window
class)初始化一个 updateWindow
对象,其中包含名为 label
的标签对象(基于 Gtk.Label
class)并调用 updateWindow
;[=69= 中定义的方法 show_all()
和 update()
]
update()
方法应该改label
,运行pkexec apt-get update
再改label
问题是无论我做什么都会发生以下情况之一:
- 如果我直接 运行
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
,显示update.Window
但label
立即设置为应该仅在pkexec apt-get update
之后设置的值完成执行; - 如果我直接运行
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
,update.Window
直到pkexec apt-get update
执行完毕才显示; - 我尝试
import
ingthreading
,在updateWindow
中定义一个单独的run_update()
方法,并使用thread = threading.Thread(target=self.run_update)
在单独的线程中启动函数,thread.start()
、thread.join()
,但仍取决于我在run_update()
(subprocess.call()
或subprocess.Popen
)中调用的方法,上述相关行为会表现出来。
Tl;博士
我不知道如何完成我所追求的,即:
- 显示
updateWindow
(Gtk.Window
) - 正在
updateWindow
中更新 - 运行
pkexec apt-get update
- 正在
updateWindow
中更新
label
(Gtk.Label
)
label
subprocess.Popen()
:显示update.Window
,但label
立即设置为只有在pkexec apt-get update
完成执行后才应设置的值;subprocess.call()
:update.Window
直到pkexec apt-get update
完成执行才显示;- 将两者中的任何一个包装在函数中,运行将函数包装在单独的线程中不会改变任何东西。
这是代码;
不使用线程(情况 1,在本例中使用 subprocess.Popen()
):
#!/usr/bin/python3
from gi.repository import Gtk
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
self.label.set_text("Updated.")
def run_update(self):
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
使用线程(案例 3,在本例中使用 subprocess.Popen()
):
#!/usr/bin/python3
from gi.repository import Gtk
import threading
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
thread = threading.Thread(target=self.run_update)
thread.start()
thread.join()
self.label.set_text("Updated.")
def run_update(self):
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
在
def run_update(self):
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
您没有等待进程终止,请尝试
def run_update(self):
proc = subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
proc.wait()
相反。这应该正确地等待完成,但它不会有太大帮助,因为 updateWindow.update
将从 mainWindow.new_update_window
调用并且 GUI 线程将等待进程完成。
自定义信号可用于在 subprocess.Popen
或 subprocess.call
启动的进程完成时进行通信:
#!/usr/bin/python3
from gi.repository import Gtk, GObject
import threading
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow(self)
update.show_all()
update.start_update()
class updateWindow(Gtk.Window):
def __init__(self, parent):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
self.parent = parent
GObject.signal_new('update_complete', self, GObject.SIGNAL_RUN_LAST,
None, (int,))
self.connect('update_complete', self.on_update_complete)
def on_update_complete(self, widget, rc):
self.label.set_text("Updated {:d}".format(rc))
# emit a signal to mainwindow if needed, self.parent.emit(...)
def start_update(self):
self.label.set_text("Updating... Please wait.")
thread = threading.Thread(target=self.run_update)
thread.start()
def run_update(self):
rc = subprocess.call(["/usr/bin/pkexec", "apt-get", "update"],
shell=False)
self.emit('update_complete', rc)
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
你快到了...
解决方案非常简单:)
您遇到的问题是 subprocess.call()
会冻结 GUI(循环),从而阻止 window 出现,而 subprocess.Popen()
会抛出命令并跳转至 self.label.set_text("Updated.")
如何解决
您可以通过 运行创建一个单独的线程来简单地解决它,调用您的命令:
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
并移动标签更改命令
self.label.set_text("Updated.")
进入线程,在第一个命令之后定位。然后线程不会冻结界面,而 label
不会过早更改,因为 subprocess.call()
会阻止这种情况。
代码则变为:
#!/usr/bin/python3
from gi.repository import Gtk
from threading import Thread
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
Thread(target = self.run_update).start()
def run_update(self):
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
self.label.set_text("Updated.")
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
或者
如果您想避免使用 Thread
,您可以使用 Gtk.main_iteration()
来防止在进程 运行s:
#!/usr/bin/python3
from gi.repository import Gtk
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
subprocess.Popen(["gedit"])
self.hold()
self.label.set_text("Updated.")
def run_update(self):
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
def hold(self):
while True:
Gtk.main_iteration()
try:
subprocess.check_output(["pgrep", "apt-get"]).decode("utf-8")
except subprocess.CalledProcessError:
break
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
编辑
不断深入了解有更好的线程使用方法,然后在我的回答中发布。
您可以在 Gtk
GUI 中使用线程,使用
GObject.threads_init()
然后,要从线程更新接口,使用
GObject.idle_add()
来自 this (slgihtly outdated) link:
...在应用程序初始化时调用 gobject.threads_init()
。然后您正常启动线程,但要确保线程从不直接执行任何 GUI 任务。相反,您使用 gobject.idle_add
安排 GUI 任务在主线程中执行
当我们将 gobject.threads_init()
替换为 GObject.threads_init()
并将 gobject.idle_add
替换为 GObject.idle_add()
时,我们几乎已经有了如何在一个线程中 运行 线程的更新版本Gtk 应用程序。
在您的代码中应用(使用第二个示例,使用线程):
#!/usr/bin/python3
from gi.repository import Gtk, GObject
from threading import Thread
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
# self.thread = threading.Thread(target=self.run_update)
thread = Thread(target=self.run_update)
thread.start()
def run_update(self):
GObject.idle_add(
self.label.set_text, "Updating... Please wait.",
priority=GObject.PRIORITY_DEFAULT
)
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
GObject.idle_add(
self.label.set_text, "Updated.",
priority=GObject.PRIORITY_DEFAULT
)
GObject.threads_init()
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
不使用 Python 的 subprocess
模块,您可以使用 Gio.Subprocess
,它集成了 GTK 的主循环:
#!/usr/bin/python3
from gi.repository import Gtk, Gio
# ...
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
subprocess = Gio.Subprocess.new(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"], 0)
subprocess.wait_check_async(None, self._on_update_finished)
def _on_update_finished(self, subprocess, result):
subprocess.wait_check_finish(result)
self.label.set_text("Updated.")