【问题标题】:Factory for Callback methods - Python TKinter回调方法的工厂 - Python TKinter
【发布时间】:2009-05-26 08:18:17
【问题描述】:

编写一个测试应用程序来模拟 PIO 行,我有一个非常简单的 Python/Tk GUI 应用程序。使用数字键 1 到 8 模拟 PIO 引脚 1 到 8。按下键 = PIO 高电平,松开键 = PIO 变为低电平。我需要它不是问题。我有点陷入了一个兔子洞,试图使用工厂来创建按键回调函数。

这是一些精简的代码:

#!usr/bin/env python
"""
Python + Tk GUI interface to simulate a 8 Pio lines.
"""

from Tkinter import *

def cb_factory(numberic_key):
    """
    Return a call back function for a specific keyboard numeric key (0-9)
    """
    def cb( self, event, key=numberic_key ):
        bit_val = 1<<numberic_key-1
        if int(event.type) == 2 and not (bit_val & self.bitfield):
            self.bitfield |= bit_val
            self.message("Key %d Down" % key)
        elif int(event.type) == 3 and (bit_val & self.bitfield):
            self.bitfield &= (~bit_val & 0xFF)
            self.message("Key %d Up" % key)
        else:
            # Key repeat
            return
        print hex(self.bitfield)
        self.display_bitfield()
    return cb

class App( Frame ):
    """
    Main TK App class 
    """

    cb1 = cb_factory(1)
    cb2 = cb_factory(2)
    cb3 = cb_factory(3)
    cb4 = cb_factory(4)
    cb5 = cb_factory(5)
    cb6 = cb_factory(6)
    cb7 = cb_factory(7)
    cb8 = cb_factory(8)

    def __init__(self, parent):
        "Init"
        self.parent = parent
        self.bitfield = 0x00
        Frame.__init__(self, parent)

        self.messages = StringVar()
        self.messages.set("Initialised")

        Label( parent, bd=1, 
               relief=SUNKEN, 
               anchor=W, 
               textvariable=self.messages,
               text="Testing" ).pack(fill=X)

        self.bf_label = StringVar()
        self.bf_label.set("0 0 0 0 0 0 0 0")

        Label( parent, bd=1, 
               relief=SUNKEN, 
               anchor=W, 
               textvariable=self.bf_label,
               text="Testing" ).pack(fill=X)

 # This Doesn't work! Get a traceback saying 'cb' expected 2 arguements
 # but only got 1?
 #
 #       for x in xrange(1,9):
 #           cb = self.cb_factory(x)
 #           self.parent.bind("<KeyPress-%d>" % x, cb) 
 #           self.parent.bind("<KeyRelease-%d>" % x, cb) 

        self.parent.bind("<KeyPress-1>", self.cb1)
        self.parent.bind("<KeyRelease-1>", self.cb1)

        self.parent.bind("<KeyPress-2>", self.cb2)
        self.parent.bind("<KeyRelease-2>", self.cb2)

        self.parent.bind("<KeyPress-3>", self.cb3)
        self.parent.bind("<KeyRelease-3>", self.cb3)

        self.parent.bind("<KeyPress-4>", self.cb4)
        self.parent.bind("<KeyRelease-4>", self.cb4)

        self.parent.bind("<KeyPress-5>", self.cb5)
        self.parent.bind("<KeyRelease-5>", self.cb5)

        self.parent.bind("<KeyPress-6>", self.cb6)
        self.parent.bind("<KeyRelease-6>", self.cb6)

        self.parent.bind("<KeyPress-7>", self.cb7)
        self.parent.bind("<KeyRelease-7>", self.cb7)

        self.parent.bind("<KeyPress-8>", self.cb8)
        self.parent.bind("<KeyRelease-8>", self.cb8)


    def display_bitfield(self):
        """
        Display the PIO lines (1 for on, 0 for off)
        """
        bin_lst = []
        for x in xrange(8):
            bit = 1 << x
            if bit & self.bitfield:
                bin_lst.append("1")
            else:
                bin_lst.append("0")
        bin_lst.reverse()
        bin_str = " ".join( bin_lst )
        self.bf_label.set( bin_str )

    def message( self, msg_txt ):
        "set"
        self.messages.set( msg_txt )

    def cb_factory(self,  numberic_key ):
        """
        Return a call back function for a specific keyboard numeric key (0-9)
        """
        def cb( self, event, key=numberic_key ):
            bit_val = 1<<numberic_key-1
            if int(event.type) == 2:
                self.bitfield |= bit_val
                self.message("Key %d Down" % key)
            else:
                self.bitfield &= (~bit_val & 0xFF)
                self.message("Key %d Up" % key)
            print hex(self.bitfield)
            self.display_bitfield()
        return cb

##########################################################################

if __name__ == "__main__":

    root = Tk()
    root.title("PIO Test")
    theApp = App( root )

    root.mainloop()

我终于得到了某种方法工厂用于回调,但我觉得它不是很令人满意。

所以我的问题是,你能不能有一个类方法工厂,它会按照我尝试的方式生成类方法(参见注释掉的代码和 App 类方法 cb_factory())?

注意:是的,我知道这个应用程序一次只能按住 4 个键,但这对我的目的来说已经足够了。

【问题讨论】:

    标签: python methods factory


    【解决方案1】:

    cb 期望 'self' 和 'event'。也许它只从绑定中获取事件?

    【讨论】:

    • 是的,就是这样!在“cb_factory”返回的“cb”方法中,“self”是多余的。我将发布修改后的代码以显示工作示例。现在,如果有人愿意向我解释这一点,我的大脑正在受伤!
    【解决方案2】:

    回答您的后续问题。

    我不确定您不了解哪一部分,但我猜您不太了解事件回调的工作原理?如果是这样,这很容易。 Tk 在循环中运行以查找事件(按键、鼠标点击等)。当您将回调/函数绑定到事件时,您只是告诉它调用您的函数并将事件对象作为参数传递。然后,您可以查询事件对象以获取有关实际发生情况的更多详细信息。您当前正在构建单独的回调函数并将每个回调函数绑定到 18 个键事件(在键 1-9 上按下和释放)。我认为您可以将其重写为简单地将 cb 作为您的类的方法,因为事件对象几乎肯定也会包含键码。

    class:
      def __init__(self):
        for x in xrange(8):
          self.parent.bind("<KeyPress-%d>" % x, self.keyaction)
          self.parent.bind("<KeyRelease-%d>" % x, self.keyaction)
    
      def keyaction(self, event):
        key = event.keycode # attribute may have another name, I haven't checked tk docs
        ... do stuff ...
    

    由于我们现在使用 self.keyaction 作为回调,它应该将 self 作为其第一个参数。它从事件对象中获取其键码。现在,在创建函数时,两个值都不需要“内置”到函数中,因此无需为每个键实际创建不同的回调,代码更容易理解。

    【讨论】:

    • It's key = event.char 但我更感兴趣的是为什么工厂生产的'cb'方法不需要'self'作为它的第一个参数。
    • 因为它从工厂函数继承了self的含义,几乎就像一个全局变量。 self 没有在 cb 中定义,但在上面的范围内定义(对象方法)
    【解决方案3】:

    考虑到 SpliFF 的回答,这是修改后的代码。我发现这在美学上更令人愉悦,但我不明白它是如何工作的,这让我很烦恼。所以,为了额外的功劳,谁能解释一下这是如何工作的?

    #!usr/bin/env python
    """
    Python + Tk GUI interface to simulate a 8 Pio lines.
    """
    
    from Tkinter import *
    from pio_handler import *
    
    class App( Frame ):
        """
        Main TK App class 
        """
    
        def __init__(self, parent):
            "Init"
            self.parent = parent
            self.bitfield = 0x00
            Frame.__init__(self, parent)
    
            self.messages = StringVar()
            self.messages.set("Initialised")
    
            Label( parent, bd=1, 
                   relief=SUNKEN, 
                   anchor=W, 
                   textvariable=self.messages,
                   text="Testing" ).pack(fill=X)
    
            self.bf_label = StringVar()
            self.bf_label.set("0 0 0 0 0 0 0 0")
    
            Label( parent, bd=1, 
                   relief=SUNKEN, 
                   anchor=W, 
                   textvariable=self.bf_label,
                   text="Testing" ).pack(fill=X)
    
            # This is the clever bit!
            # Use a factory to assign a callback function for keys 1 to 8
            for x in xrange(1,9):
                cb = self.cb_factory(x)
                self.parent.bind("<KeyPress-%d>" % x, cb) 
                self.parent.bind("<KeyRelease-%d>" % x, cb) 
    
        def display_bitfield(self):
            """
            Display the PIO lines (1 for on, 0 for off)
            """
            bin_lst = []
            for x in xrange(8):
                bit = 1 << x
                if bit & self.bitfield:
                    bin_lst.append("1")
                else:
                    bin_lst.append("0")
            bin_lst.reverse()
            bin_str = " ".join( bin_lst )
            self.bf_label.set( bin_str )
    
        def message( self, msg_txt ):
            "set"
            self.messages.set( msg_txt )
    
        def cb_factory(self,  numeric_key ):
            """
            Return a call back function for a specific keyboard numeric key (0-9)
            """
            def cb( event, key=numeric_key ):
                bit_val = 1<<numeric_key-1
                if int(event.type) == 2:
                    self.bitfield |= bit_val
                    self.message("Key %d Down" % key)
                else:
                    self.bitfield &= (~bit_val & 0xFF)
                    self.message("Key %d Up" % key)
                print hex(self.bitfield)
                self.display_bitfield()
            return cb
    
    ##########################################################################
    
    if __name__ == "__main__":
    
        root = Tk()
        root.title("PIO Test")
        theApp = App( root )
    
        root.mainloop()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-05
      • 1970-01-01
      • 2020-05-10
      • 1970-01-01
      相关资源
      最近更新 更多