BETA

key bindされる関数を作り直さずにコールバック関数とする(tkinter)

投稿日:2020-03-16
最終更新:2020-03-20

この記事のまとめ

  • key bindするときは入力キーに対する引数が必要
  • しかし、key bindで受け取った値を関数内では使わない
  • また、key bindに指定したい関数はkey bind以外でも使う
  • 新しくkey bind用の関数を作りたくない
    これらはfreezeという関数を作れば解決できる。

実行環境

Python 3.8.2

freeze関数

def freeze(func, *args, **kwargs):  
    def newfunc(*fargs, **fkwargs):  
        return func(*args, **kwargs)  
    newfunc.func = func  
    newfunc.args = args  
    newfunc.kwargs = kwargs  
    return newfunc  

functools.partialが関数の部分的な凍結なら、freezeは完全な凍結のイメージ。

def add(x, y):  
    return x + y  


a = freeze(add, 5, 2)  
a()  # 7  
a(1)  # 7  
a(None, "")  # 7  

a = freeze(add, 5, 2)としたことでaにどんな引数を渡しても7を返してくれる。

b = freeze(add, 3, y=2)  
b.func  # <function __main__.add(x, y)>  
b.args  # (1,)  
b.kwargs  # {'y': 2}  
b()  # 3  

funcargskwargsを使うことでどんな引数が渡されていたかの確認ができる。
必要がなければ、なくても問題はない。

def freeze(func, *args, **kwargs):  
    def newfunc(*fargs, **fkwargs):  
        return func(*args, **kwargs)  
    return newfunc  

使いどころ

例えば、クラスメソッドをkey bindに設定したい時に使う。

class Foo:  
    def __init__(self):  
        self.index = 0  

    def next(self):  
        self.index += 1  
        print(self.index)  

動作がわかりやすいようにprint(self.index)next内に入れた。

例えば、<Right>キー(→)を押したときに加算したいとする。

import tkinter as tk  


root = tk.Tk()  
foo = Foo()  
root.bind("<Right>", foo.next)  
root.mainloop()  

試しに上のコードを実行して、<Right>キー(→)を押すと以下のようなエラーが表示される。

Exception in Tkinter callback  
Traceback (most recent call last):  
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/tkinter/__init__.py", line 1883, in __call__  
    return self.func(*args)  
TypeError: next() takes 1 positional argument but 2 were given  

key bindするときは入力keyを受け取るための引数を用意する必要がある。
そのため、クラスメソッドnextにkey bind用の引数eventを持たせると以下のようになる。

import tkinter as tk  


class Foo:  
    def __init__(self):  
        self.index = 0  

    def next(self, event):  
        self.index += 1  
        print(self.index)  


root = tk.Tk()  
foo = Foo()  
root.bind("<Right>", foo.next)  
root.mainloop()  

こちらは<Right>キー(→)を押すと、self.indexの値が出力される。

→→→

1  
2  
3  

しかしながら、key bind用に加えた引数eventはkey bindでしか使わない。
したがって、key bind用と普段使い用を別のメソッドにしてみる。

class Foo:  
    def __init__(self):  
        self.index = 0  

    def next(self):  
        self.index += 1  
        print(self.index)  

    def key_next(self, event):  
        self.next()  

これだとkey_nextのようにkey bindをするためだけのメソッドができてしまう。
また、eventに対して何らかの操作を加えるならいいが、今回の例だとeventkey_next内で全く使われていない。

まとめると

  • key bindするときは入力キーに対する引数が必要
  • しかし、key bindで受け取った値を関数内では使わない
  • また、key bindに指定したい関数はkey bind以外でも使う
  • 新しくkey bind用の関数は作りたくない
    これらはfreezeという関数を作れば解決できる。

freezeを試す

import tkinter as tk  


class Foo:  
    def __init__(self):  
        self.index = 0  

    def next(self):  
        self.index += 1  
        print(self.index)  


def freeze(func, *args, **kwargs):  
    def newfunc(*fargs, **fkwargs):  
        return func(*args, **kwargs)  
    newfunc.func = func  
    newfunc.args = args  
    newfunc.kwargs = kwargs  
    return newfunc  


root = tk.Tk()  
foo = Foo()  
root.bind("<Right>", freeze(foo.next))  
root.mainloop()  

あとは<Right>キー(→)を連打すればいい。
→→→→

1  
2  
3  
4  

最初からkey bindすることを想定しているなら

class Foo:  
    def __init__(self):  
        self.index = 0  

    def next(self, *event):  
        self.index += 1  
        print(self.index)  

freezeを使うのは既存関数をkey bindに使いたいときに限る。

最後に

freezeをわざわざ作ったけど、同等の機能を持った関数が標準ライブラリにありそう。functoolsは見たけど、見逃している可能性が高い。

参考文献

functools --- 高階関数と呼び出し可能オブジェクトの操作 — Python 3.8.2 ドキュメント

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

A03kiの技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう