31、Python GUI 开发:从基础到应用

Python GUI 开发:从基础到应用

1. Python 进程处理与守护进程

在 Python 中处理进程时,我们能感受到它的成熟与强大。Python 有着优雅且复杂的线程 API,但全局解释器锁(GIL)是需要时刻牢记的一点。如果程序是 I/O 密集型的,GIL 通常不会造成问题;但如果需要使用多个处理器,那么使用进程是个不错的选择。甚至有人认为,即便没有 GIL,使用进程也比使用线程更好,主要原因在于调试线程代码可能会是一场噩梦。

下面是一个简单的守护进程示例,它能帮助我们理解守护进程的基本概念:

No mod 5 at 1207272456.18
No mod 5 at 1207272457.19
No mod 5 at 1207272458.19
No mod 5 at 1207272459.19
No mod 5 at 1207272461.2
No mod 5 at 1207272462.2
No mod 5 at 1207272463.2
No mod 5 at 1207272464.2
No mod 5 at 1207272466.2
No mod 5 at 1207272467.2
No mod 5 at 1207272468.2
No mod 5 at 1207272469.2
No mod 5 at 1207272471.2
No mod 5 at 1207272472.2
jmjones@dinkgutsy:code$ cat /tmp/stderr.log
Mod 5 at 1207272455.18
Mod 5 at 1207272460.2
Mod 5 at 1207272465.2
Mod 5 at 1207272470.2

这个简单的示例展示了守护进程的基本概念,我们可以利用守护进程编写目录监控器、网络监视器、网络服务器等长时间运行的程序。

此外,熟悉 Subprocess 模块也是个不错的选择,它是处理子进程的一站式解决方案。

2. GUI 开发基础理论

在系统管理工作中,构建 GUI 应用程序可能不是首先想到的任务。但在某些情况下,构建 GUI 应用程序能让工作变得更轻松。这里的 GUI 涵盖了传统的使用 GTK 和 QT 等工具包的应用程序,以及基于 Web 的应用程序。

当编写控制台实用程序时,通常期望它能在无需用户干预的情况下运行并完成任务。但编写 GUI 实用程序时,需要用户提供一些输入才能使程序正常工作。

GUI 应用程序的行为是事件驱动的。应用程序等待事件发生,例如按钮被按下或复选框被选中。程序员会将特定事件与特定代码块关联起来,这些代码块被称为事件处理程序。GUI 工具包的一项重要任务就是在关联事件发生时调用正确的事件处理程序,它通过提供一个“事件循环”来实现这一点,该循环会不断等待事件发生,并在事件发生时进行适当处理。

在创建 GUI 应用程序的界面时,有两种方式可供选择:
- 使用 GUI 构建器 :不同的 GUI 工具包可能有对应的 GUI 构建器,例如在 Mac 上编写 Cocoa 应用程序可以使用 Interface Builder,在 Linux 上使用 PyGTK 可以使用 Glade,使用 PyQT 可以使用 QT Designer。
- 手动编写代码 :如果需要对 GUI 有更多的控制,也可以通过编写代码来手动布局 GUI。在 PyGTK 中,每种 GUI 组件都对应一个 Python 类,例如窗口是 gtk.Window 类的对象,按钮是 gtk.Button 类的对象。

3. 构建简单的 PyGTK 应用程序

在运行示例或编写自己的 PyGTK 应用程序之前,需要先安装 PyGTK。在较新的 Linux 发行版上安装相对简单,在 Windows 上看起来也比较容易,如果使用的是 Ubuntu,它可能已经安装好了。如果没有适用于你平台的二进制发行版,安装过程可能会比较麻烦。

以下是一个简单的 PyGTK 应用程序示例,它包含一个窗口和一个按钮,点击按钮时会更新按钮的标签显示当前时间:

#!/usr/bin/env python
import pygtk
import gtk
import time

class SimpleButtonApp(object):
    """This is a simple PyGTK app that has one window and one button.
    When the button is clicked, it updates the button's label with the current time.
    """
    def __init__(self):
        #the main window of the application
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        #this is how you "register" an event handler. Basically, this 
        #tells the gtk main loop to call self.quit() when the window "emits"
        #the "destroy" signal.
        self.window.connect("destroy", self.quit)
        #a button labeled "Click Me"
        self.button = gtk.Button("Click Me")
        #another registration of an event handler. This time, when the
        #button "emits" the "clicked" signal, the 'update_button_label'
        #method will get called.
        self.button.connect("clicked", self.update_button_label, None)
        #The window is a container. The "add" method puts the button
        #inside the window.
        self.window.add(self.button)
        #This call makes the button visible, but it won't become visible
        #until its container becomes visible as well.
        self.button.show()
        #Makes the container visible
        self.window.show()

    def update_button_label(self, widget, data=None):
        """set the button label to the current time
        This is the handler method for the 'clicked' event of the button
        """
        self.button.set_label(time.asctime())

    def quit(self, widget, data=None):
        """stop the main gtk event loop
        When you close the main window, it will go away, but if you don't
        tell the gtk main event loop to stop running, the application will
        continue to run even though it will look like nothing is really 
        happening.
        """
        gtk.main_quit()

    def main(self):
        """start the gtk main event loop"""
        gtk.main()

if __name__ == "__main__":
    s = SimpleButtonApp()
    s.main()

在这个示例中,主类继承自 object 而不是某个 GTK 类。创建 PyGTK GUI 应用程序不一定需要采用面向对象的方式,但对于更复杂的应用,创建自定义类是个很好的选择,这样可以方便地管理和访问所有 GUI 组件。

4. 使用 PyGTK 构建 Apache 日志查看器

接下来,我们将使用 PyGTK 构建一个更实用的应用程序——Apache 日志查看器,它具备以下功能:
- 选择并打开指定的日志文件
- 一眼查看行号、远程主机、状态和发送的字节数
- 按行号、远程主机、状态或发送的字节数对日志行进行排序

以下是该日志查看器的源代码:

#!/usr/bin/env python
import gtk
from apache_log_parser_regex import dictify_logline

class ApacheLogViewer(object):
    """Apache log file viewer which sorts on various pieces of data"""
    def __init__(self):
        #the main window of the application
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_size_request(640, 480)
        self.window.maximize()
        #stop event loop on window destroy
        self.window.connect("destroy", self.quit)
        #a VBox is a container that holds other GUI objects primarily for layout
        self.outer_vbox = gtk.VBox()

        #toolbar which contains the open and quit buttons
        self.toolbar = gtk.Toolbar()
        #create open and quit buttons and icons
        #add buttons to toolbar
        #associate buttons with correct handlers
        open_icon = gtk.Image()
        quit_icon = gtk.Image()
        open_icon.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_LARGE_TOOLBAR)
        quit_icon.set_from_stock(gtk.STOCK_QUIT, gtk.ICON_SIZE_LARGE_TOOLBAR)
        self.open_button = gtk.ToolButton(icon_widget=open_icon)
        self.quit_button = gtk.ToolButton(icon_widget=quit_icon)
        self.open_button.connect("clicked", self.show_file_chooser)
        self.quit_button.connect("clicked", self.quit)
        self.toolbar.insert(self.open_button, 0)
        self.toolbar.insert(self.quit_button, 1)
        #a control to select which file to open
        self.file_chooser = gtk.FileChooserWidget()
        self.file_chooser.connect("file_activated", self.load_logfile)
        #a ListStore holds data that is tied to a list view
        #this ListStore will store tabular data of the form:
        #line_numer, remote_host, status, bytes_sent, logline
        self.loglines_store = gtk.ListStore(int, str, str, int, str)
        #associate the tree with the data...
        self.loglines_tree = gtk.TreeView(model=self.loglines_store)
        #...and set up the proper columns for it
        self.add_column(self.loglines_tree, 'Line Number', 0)
        self.add_column(self.loglines_tree, 'Remote Host', 1)
        self.add_column(self.loglines_tree, 'Status', 2)
        self.add_column(self.loglines_tree, 'Bytes Sent', 3)
        self.add_column(self.loglines_tree, 'Logline', 4)
        #make the area that holds the apache log scrollable
        self.loglines_window = gtk.ScrolledWindow()
        #pack things together
        self.window.add(self.outer_vbox)
        self.outer_vbox.pack_start(self.toolbar, False, False)
        self.outer_vbox.pack_start(self.file_chooser)
        self.outer_vbox.pack_start(self.loglines_window)
        self.loglines_window.add(self.loglines_tree)
        #make everything visible
        self.window.show_all()
        #but specifically hide the file chooser
        self.file_chooser.hide()

    def add_column(self, tree_view, title, columnId, sortable=True):
        column = gtk.TreeViewColumn(title, gtk.CellRendererText() , text=columnId)
        column.set_resizable(True)      
        column.set_sort_column_id(columnId)
        tree_view.append_column(column)

    def show_file_chooser(self, widget, data=None):
        """make the file chooser dialog visible"""
        self.file_chooser.show()

    def load_logfile(self, widget, data=None):
        """load logfile data into tree view"""
        filename = widget.get_filename()
        print "FILE-->", filename
        self.file_chooser.hide()
        self.loglines_store.clear()
        logfile = open(filename, 'r')
        for i, line in enumerate(logfile):
            line_dict = dictify_logline(line)
            self.loglines_store.append([i + 1, line_dict['remote_host'], 
                    line_dict['status'], int(line_dict['bytes_sent']), line])
        logfile.close()

    def quit(self, widget, data=None):
        """stop the main gtk event loop"""
        gtk.main_quit()

    def main(self):
        """start the gtk main event loop"""
        gtk.main()

if __name__ == "__main__":
    l = ApacheLogViewer()
    l.main()

在这个示例中, ApacheLogViewer 类继承自 object ,主要的初始化工作在 __init__ 方法中完成。我们创建了一个窗口对象,并设置了其大小和最大化属性。使用 VBox 作为容器来布局其他 GUI 组件,添加了工具栏和工具按钮,创建了文件选择器和用于显示日志行的列表组件,最后将所有组件组合在一起并设置可见性。

以下是该应用程序的创建流程:

graph TD
    A[创建窗口对象] --> B[设置窗口大小和最大化]
    B --> C[创建 VBox 容器]
    C --> D[创建工具栏和工具按钮]
    D --> E[创建文件选择器]
    E --> F[创建列表组件(ListStore 和 TreeView)]
    F --> G[设置列表组件的列]
    G --> H[创建滚动窗口]
    H --> I[将组件添加到 VBox 并设置可见性]

当启动这个应用程序时,我们可以选择并打开指定的日志文件,在列表控件中查看日志的行号、远程主机、状态和发送的字节数,还可以通过点击列标题对日志行进行排序。

5. 使用 Curses 构建 Apache 日志查看器

curses 是一个用于创建交互式文本界面应用程序的库。与 GUI 工具包不同, curses 不遵循事件处理和回调的方式,需要我们自己负责获取用户输入并进行相应处理。同时,在 curses 中通常是直接在屏幕上绘制文本,而不是像 GUI 工具包那样将小部件添加到容器中。

以下是使用 Python 标准库中的 curses 模块实现的 Apache 日志查看器示例:

#!/usr/bin/env python
"""
curses based Apache log viewer
Usage:

    curses_log_viewer.py logfile
This will start an interactive, keyboard driven log viewing application. Here
are what the various key presses do: 
    u/d   - scroll up/down
    t     - go to the top of the log file
    q     - quit
    b/h/s - sort by bytes/hostname/status
    r     - restore to initial sort order
"""
import curses
from apache_log_parser_regex import dictify_logline
import sys
import operator

class CursesLogViewer(object):
    def __init__(self, logfile=None):
        self.screen = curses.initscr()
        self.curr_topline = 0
        self.logfile = logfile
        self.loglines = []

    def page_up(self):
        self.curr_topline = self.curr_topline - (2 * curses.LINES)
        if self.curr_topline < 0:
            self.curr_topline = 0
        self.draw_loglines()

    def page_down(self):
        self.draw_loglines()

    def top(self):
        self.curr_topline = 0
        self.draw_loglines()

    def sortby(self, field):
        #self.loglines = sorted(self.loglines, key=operator.itemgetter(field))
        self.loglines.sort(key=operator.itemgetter(field))
        self.top()

    def set_logfile(self, logfile):
        self.logfile = logfile
        self.load_loglines()

    def load_loglines(self):
        self.loglines = []
        logfile = open(self.logfile, 'r')
        for i, line in enumerate(logfile):
            line_dict = dictify_logline(line)
            self.loglines.append((i + 1, line_dict['remote_host'], 
                line_dict['status'], int(line_dict['bytes_sent']), line.rstrip()))
        logfile.close()
        self.draw_loglines()

    def draw_loglines(self):
        self.screen.clear()
        status_col = 4
        bytes_col = 6
        remote_host_col = 16
        status_start = 0
        bytes_start = 4
        remote_host_start = 10
        line_start = 26
        logline_cols = curses.COLS - status_col - bytes_col - remote_host_col - 1
        for i in range(curses.LINES):
            c = self.curr_topline
            try:
                curr_line = self.loglines[c]
            except IndexError:
                break
            self.screen.addstr(i, status_start, str(curr_line[2]))
            self.screen.addstr(i, bytes_start, str(curr_line[3]))
            self.screen.addstr(i, remote_host_start, str(curr_line[1]))
            #self.screen.addstr(i, line_start, str(curr_line[4])[logline_cols])
            self.screen.addstr(i, line_start, str(curr_line[4]), logline_cols)
            self.curr_topline += 1
        self.screen.refresh()

    def main_loop(self, stdscr):
        stdscr.clear()
        self.load_loglines()
        while True:
            c = self.screen.getch()
            try:
                c = chr(c)
            except ValueError:
                continue
            if c == 'd': 
                self.page_down()
            elif c == 'u': 
                self.page_up()
            elif c == 't': 
                self.top()
            elif c == 'b': 
                self.sortby(3)
            elif c == 'h': 
                self.sortby(1)
            elif c == 's': 
                self.sortby(2)
            elif c == 'r': 
                self.sortby(0)
            elif c == 'q': 
                break

if __name__ == '__main__':
    infile = sys.argv[1]
    c = CursesLogViewer(infile)
    curses.wrapper(c.main_loop)

在这个示例中,我们创建了 CursesLogViewer 类来结构代码。在构造函数中,创建了 curses 屏幕并初始化了一些变量。在程序的主函数中,实例化 CursesLogViewer 并传入要查看的日志文件。然后将 main_loop 方法传递给 curses wrapper 函数,该函数会设置终端状态以适合 curses 应用程序,调用传入的函数,最后恢复终端状态。

main_loop 方法充当一个简单的事件循环,等待用户的键盘输入。根据用户输入调用相应的方法,例如按下 u d 键分别调用 page_up page_down 方法来实现日志的上下滚动;按下 b h s 键分别按字节数、主机名、状态对日志进行排序;按下 r 键恢复到初始排序;按下 q 键退出程序。

以下是该应用程序的操作流程:
| 按键 | 操作 |
| ---- | ---- |
| u | 向上滚动日志 |
| d | 向下滚动日志 |
| t | 跳转到日志文件顶部 |
| q | 退出程序 |
| b | 按字节数排序 |
| h | 按主机名排序 |
| s | 按状态排序 |
| r | 恢复初始排序 |

graph TD
    A[启动程序,传入日志文件] --> B[初始化 CursesLogViewer]
    B --> C[调用 main_loop 方法]
    C --> D[获取用户键盘输入]
    D --> E{判断输入}
    E -- u --> F[调用 page_up 方法]
    E -- d --> G[调用 page_down 方法]
    E -- t --> H[调用 top 方法]
    E -- b --> I[调用 sortby 方法按字节数排序]
    E -- h --> J[调用 sortby 方法按主机名排序]
    E -- s --> K[调用 sortby 方法按状态排序]
    E -- r --> L[调用 sortby 方法恢复初始排序]
    E -- q --> M[退出循环,结束程序]
    F --> D
    G --> D
    H --> D
    I --> D
    J --> D
    K --> D
    L --> D

虽然 PyGTK 应用程序和 curses 应用程序的代码行数相当,但 curses 应用程序可能感觉更复杂,可能是因为需要自己创建事件循环、自己创建“小部件”以及直接在终端屏幕上绘制文本。不过,在某些情况下,掌握 curses 应用程序的编写方法会很有帮助。例如,在一些资源受限或只能使用文本界面的环境中, curses 可以发挥重要作用。

我们还可以对这个 curses 应用程序进行一些改进,例如添加反转当前排序顺序的功能,这是一个相对简单的修改,有兴趣的读者可以自行尝试。

Matlab基于粒子群优化算法及鲁棒MPPT控制器提高光伏并网的效率内容概要:本文围绕Matlab在电力系统优化与控制领域的应用展开,重点介绍了基于粒子群优化算法(PSO)和鲁棒MPPT控制器提升光伏并网效率的技术方案。通过Matlab代码实现,结合智能优化算法与先进控制策略,对光伏发电系统的最大功率点跟踪进行优化,有效提高了系统在不同光照条件下的能量转换效率和并网稳定性。同时,文档还涵盖了多种电力系统应用场景,如微电网调度、储能配置、鲁棒控制等,展示了Matlab在科研复现与工程仿真中的强大能力。; 适合人群:具备一定电力系统基础知识和Matlab编程能力的高校研究生、科研人员及从事新能源系统开发的工程师;尤其适合关注光伏并网技术、智能优化算法应用与MPPT控制策略研究的专业人士。; 使用场景及目标:①利用粒子群算法优化光伏系统MPPT控制器参数,提升动态响应速度与稳态精度;②研究鲁棒控制策略在光伏并网系统中的抗干扰能力;③复现已发表的高水平论文(如EI、SCI)中的仿真案例,支撑科研项目与学术写作。; 阅读建议:建议结合文中提供的Matlab代码与Simulink模型进行实践操作,重点关注算法实现细节与系统参数设置,同时参考链接中的完整资源下载以获取更多复现实例,加深对优化算法与控制系统设计的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值