工厂方法创建回调函数

30 阅读2分钟

我正在编写一个测试应用程序来模拟 PIO 线,其中包含一个简单的 Python/Tk GUI 应用程序。该应用程序使用数字键 1 到 8 来模拟 PIO 引脚 1 到 8。按下按键 = PIO 高电平,释放按键 = PIO 低电平。我并不打算讨论应用程序的目的。我试图使用工厂方法创建一个按键回调函数,这个过程遇到了了一些问题。

以下是简化的代码示例:

from Tkinter import *

def cb_factory(numberic_key):

    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):

    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 arguments
        # 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):

        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):

        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 个键,但这对我来说已经足够了。

  1. 解决方案

方法一:传递 self 参数

你可以在工厂函数中传递 self 参数。修改后的代码如下:

def cb_factory(self, numberic_key):

    def cb(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

方法二:使用 functools.partial

你也可以使用 functools.partial 来创建类方法工厂。修改后的代码如下:

from functools import partial

def cb_factory(self, numberic_key):

    cb = partial(self.key_action, key=numberic_key)
    return cb

def key_action(self, event, key):
    bit_val = 1 << 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()