読者です 読者をやめる 読者になる 読者になる

はんぎょねこの憂鬱

耳から変な汁が出てきた

Python lambda式の落とし穴

Pythonクロージャ内部で使われている変数は通常、実行時に値が評価されます。for文などと組み合わせてlambda式やローカル関数を使うときは、ちょっと気を付けないといけません。

例えば、0~4まで出力する関数を5つ用意したいとき

# 関数作る
functions = []
for i in range(0, 5):
    functions.append(lambda: print(i))

# 関数呼び出す
for func in functions:
    func() # この時点では i は 4 ですよ?

このように書いてしまうと、下記のような結果が出力されてしまいます。

4
4
4
4
4

こいつを回避するためには、デフォルト引数を使ってちょいと工夫してやる必要があります。

# 関数作る
functions = []
for i in range(0, 5):
    functions.append(lambda x=i: print(x)) # デフォルト引数は関数定義時に評価される

# 関数呼び出す
for func in functions:
    func()

これで意図した結果が得られます。

0
1
2
3
4

functools.partialを使う方法もあるようですね。

参照:Guide to Python - Common Gotchas

QDockWidgetに追加したWidgetが表示されないとき

PySide

setWidget()を使え

QListWidget コンテキストメニュー

PySide

f:id:dungeonneko:20160210011910g:plain

# conding: utf-8
import sys
from PySide.QtCore import *
from PySide.QtGui import *

# 右クリックでカレントの行を削除
def customMenu(widget, pos):
    if len(widget.selectedItems()) == 0:
        return
    action = QAction('Remove', widget)
    action.triggered.connect(lambda : widget.takeItem(widget.currentRow()))
    menu = QMenu()
    menu.addAction(action)
    menu.exec_(widget.mapToGlobal(pos))

# entry point
if __name__ == '__main__':
    myapp = QApplication(sys.argv)
    widget = QListWidget()
    widget.setContextMenuPolicy(Qt.CustomContextMenu) # Policyも設定しないといけない
    widget.customContextMenuRequested.connect(lambda pos : customMenu(widget, pos))
    widget.addItem('item1')
    widget.addItem('item2')
    widget.addItem('item3')
    widget.show()
    sys.exit(myapp.exec_())

QScrollAreaの罠にハマる

PySide

いつものようにLayoutにButtonを追加するが

一定サイズ以下に縮まないしスクロールバーも出ない

f:id:dungeonneko:20160209014920g:plain

# conding: utf-8
import sys
from PySide.QtCore import *
from PySide.QtGui import *

# entry point
if __name__ == '__main__':
    myapp = QApplication(sys.argv)
    widget = QScrollArea()
    widget.setWidgetResizable(True)

    # いつもの
    layout = QVBoxLayout(widget)
    widget.setLayout(layout)

    layout.addWidget(QPushButton('OK!'))
    layout.addWidget(QPushButton('OK!'))
    layout.addWidget(QPushButton('OK!'))
    layout.addWidget(QPushButton('OK!'))
    layout.addWidget(QPushButton('OK!'))
    layout.addWidget(QPushButton('OK!'))
    layout.addWidget(QPushButton('OK!'))
    layout.addWidget(QPushButton('OK!'))

    widget.show()
    sys.exit(myapp.exec_())

Widgetを一つはさむと何故か想定どおりに動く

f:id:dungeonneko:20160209014222g:plain

    # 内側にWidgetを1つ作ってあげる
    inner = QWidget()
    layout = QVBoxLayout(inner)
    inner.setLayout(layout)
    widget.setWidget(inner)

QListWidget 文字列フィルタリングしてみる

PySide

文字列でフィルタリングできるリストビューがあれば

人生の複雑な状況も整理できるかもしれない

f:id:dungeonneko:20160206230506g:plain

# conding: utf-8
import sys
from PySide.QtCore import *
from PySide.QtGui import *

class MyListWidget(QWidget):
    def __init__(self, data, parent=None):
        super().__init__(parent)
        layout = QVBoxLayout()
        self.__filter = QLineEdit()
        self.__lsview = QListWidget()

        self.setLayout(layout)
        layout.addWidget(self.__filter)
        layout.addWidget(self.__lsview)

        self.__filter.textChanged.connect(self.__refresh)
        self.__data = data
        self.__refresh()

    def __refresh(self):
        self.__lsview.clear()

        # 文字列でフィルタリング
        s = self.__filter.text()
        for d in self.__data:
            if len(s) > 0 and s not in d:
                continue
            self.__lsview.addItem(QListWidgetItem(d))

# entry point
if __name__ == '__main__':
    myapp = QApplication(sys.argv)
    widget = QWidget()
    layout = QHBoxLayout(widget)

    # ややこしいデータを挿入する
    mylist = MyListWidget((
        '俺がお前でお前が俺で嫁は関係ない',
        '俺は俺でお前はお前で嫁は嫁だからあんまり関係ない',
        'お前のものは俺のもので俺のものは嫁のもの',
        '俺もお前でお前も俺で嫁は関係ない',
        '俺もお前もあいつもこいつも嫁も関係ない',
        '俺とお前が嫁で嫁は実は婿だった訳だ',
        'お前の嫁が俺の嫁なわけがない',
        'えっ?俺がお前の嫁なの?'))

    widget.setLayout(layout)
    layout.addWidget(mylist)
    widget.show()
    sys.exit(myapp.exec_())

駄目でした

QComboboxにはOrderedDictが良さげ

PySide

Comboboxのインデックスと処理が1:1だと便利

dictは追加した要素を不定の順序でイテレートするので

挿入順を保存したい場合は OrderedDict を使う

f:id:dungeonneko:20160206021014g:plain

# conding: utf-8
import sys
from PySide.QtCore import *
from PySide.QtGui import *
import collections

# entry point
if __name__ == '__main__':
    myapp = QApplication(sys.argv)
    widget = QWidget()
    layout = QHBoxLayout(widget)
    lhs = QSpinBox()
    ope = QComboBox()
    rhs = QSpinBox()
    btn = QPushButton('Calc')
    ans = QLabel('???')

    widget.setLayout(layout)
    layout.addWidget(lhs)
    layout.addWidget(ope)
    layout.addWidget(rhs)
    layout.addWidget(btn)
    layout.addWidget(ans)

    # 辞書に追加した順序が保持される
    operators = collections.OrderedDict((
        ('Add', lambda x, y : x + y),
        ('Sub', lambda x, y : x - y),
        ('Mul', lambda x, y : x * y),
        ('Div', lambda x, y : x / y),
    ))

    # コンボボックスに追加
    ope.addItems(list(operators.keys()))

    # 計算ボタンがおされたときの処理
    def calc():
        x = operators[ ope.currentText() ](lhs.value(), rhs.value())
        ans.setText(str(x))

    # ボタン押下イベントに接続
    btn.clicked.connect(calc)

    widget.show()
    sys.exit(myapp.exec_())

Signalの使いかたをよく忘れるのでメモ

PySide

これでGUIピタゴラ装置作ったら仕事してる振りできる

f:id:dungeonneko:20160204225915g:plain

# conding: utf-8
import sys
from PySide.QtCore import *
from PySide.QtGui import *

# ほげ~
class Hoge(QLabel):
    # 文字列を引数に渡すシグナルを定義
    textChanged = Signal(str)

    # 初期化
    def __init__(self, parent=None):
        super().__init__(parent)

    # setTextが呼ばれたらラベルを書き換えて一秒後にシグナルを発信
    def setText(self, text):
        super().setText(text)
        QTimer.singleShot(1000, lambda : self.textChanged.emit(text))

# entry point
if __name__ == '__main__':
    myapp = QApplication(sys.argv)

    widget = QWidget()
    layout = QVBoxLayout(widget)
    lineed = QLineEdit()
    label0 = Hoge()
    label1 = Hoge()
    label2 = Hoge()
    label3 = Hoge()
    label4 = Hoge()

    widget.setLayout(layout)
    layout.addWidget(lineed)
    layout.addWidget(label0)
    layout.addWidget(label1)
    layout.addWidget(label2)
    layout.addWidget(label3)
    layout.addWidget(label4)

    lineed.textChanged.connect(lambda s : label0.setText(s))
    label0.textChanged.connect(lambda s : label1.setText(s))
    label1.textChanged.connect(lambda s : label2.setText(s))
    label2.textChanged.connect(lambda s : label3.setText(s))
    label3.textChanged.connect(lambda s : label4.setText(s))

    widget.show()
    sys.exit(myapp.exec_())