1

ループ変数 (PyQT で使用するため) に応じて、ループ内に関数を作成したいのですが、関数はループ変数を「逆参照」していません。(私は適切な用語を知らないので、私のずさんさを許してください。) これは簡単な例です:

a = [1, 2, 3, 4]
b = []

for item in a:
    func = lambda: print(item)
    b.append(func)

print(a)
for func in b:
  func()

関数のリストになりたいと思いbます。それぞれが対応する要素を出力しますa(定義時に、a後で変更されても変更されるべきではありません)。リンクで提案されているように、func = lambda item=item: print(item)この単純なケースを修正しますが、同じ修正で PyQT ケースを機能させることはできません:

import sys
import xml.etree.ElementTree as ET
from PyQt4 import QtCore, QtGui

class Main(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        self.tree = ET.fromstring('<root><a>1</a><a>2</a><a>3</a><a>4</a></root>')
        self.create_widgets()
        self.w = None

    def create_widgets(self):
        self.buttons = []

        # THIS DOESN'T WORK
        for value in self.tree.findall('a'):
            button = QtGui.QPushButton(value.text)
            button.clicked.connect(lambda x=value: self.print_text(x))
            self.buttons.append(button)

        # THIS DOES WORK:
        #values = []
        #for value in self.tree.findall('a'):
        #    values.append(value)
        #button = QtGui.QPushButton(values[0].text)
        #button.clicked.connect(lambda: self.print_text(values[0]))
        #self.buttons.append(button)
        #button = QtGui.QPushButton(values[1].text)
        #button.clicked.connect(lambda: self.print_text(values[1]))
        #self.buttons.append(button)
        #button = QtGui.QPushButton(values[2].text)
        #button.clicked.connect(lambda: self.print_text(values[2]))
        #self.buttons.append(button)
        #button = QtGui.QPushButton(values[3].text)
        #button.clicked.connect(lambda: self.print_text(values[3]))
        #self.buttons.append(button)

        box = QtGui.QHBoxLayout()
        for i in self.buttons:
            box.addWidget(i)
        central_widget = QtGui.QWidget()
        central_widget.setLayout(box)
        self.setCentralWidget(central_widget)

    def print_text(self, value):
        print(value)
        print(value.text)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    myapp = Main()
    myapp.show()
    sys.exit(app.exec_())

要素を渡したいのですがxml.etree、ラムダ関数を使用すると、得られるのはFalse. 各ボタンを明示的に作成すると、すべてが正常に機能します。

4

2 に答える 2

4

クロージャーは、変数の値ではなく、変数名をキャプチャします。変数の値は反復ごとに変化するため、関数が最終的に使用されると、 の現在の値が参照されますitem。これを回避するには、新しいスコープ (別の関数) で関数を作成する必要があります。

def create_printer(item):
    def printer():
        print(item)
    return printer

a = [1, 2, 3, 4]
b = []

for item in a:
    func = create_printer(item)
    b.append(func)

print(a)
for func in b:
  func()

ただし、実際には既に存在する関数に引数を提供しているだけです。そのため、代わりに使用できますfunctools.partial

from functools import partial

a = [1, 2, 3, 4]
b = []

for item in a:
    func = partial(print, item)
    b.append(func)

print(a)
for func in b:
  func()

特定の Qt の問題

Qt コードが機能しない理由は、指定したデフォルトの引数が上書きされているためです。これが、デフォルトの引数でラムダを使用してクロージャーを作成してはならない理由の 1 つです (ここで説明したように)。の署名 (関数を接続しているもの) を見ると、引数clickedを取ることがわかります。boolこれがあなたが得ている理由ですFalse。引数なしの関数を提供すると、このブール値は Qt によって破棄されます。上記の 2 つの方法を使用して、このブール値の引数が関数に渡されないようにします。

于 2016-05-01T15:22:05.723 に答える
3

Dunes は、どのコードを使用できるかという点で既に適切な回答を提供しているため、再度説明することはしませんが、なぜそれを行う必要があるのか​​を説明したいと思います。

コメントで提供された修正の背後にある理由は次のとおりです。通常、Python は値ではなく名前を保存します。だからあなたが関数を書くとき

lambda: print(item)

後で呼び出すと、その関数は、呼び出さitemれたときに名前に関連付けられた (「バインドされた」) ものを出力します。これにより、何が起こっているのかが明確になります。

>>> item = 1
>>> func = lambda: print(item)
>>> func()
1
>>> item = 2
>>> func()
2

ただし、引数のデフォルト値は例外です。あなたが書くとき

lambda item=item: print(item)

Python は、関数が宣言さitemれた時点で名前にバインドされている値を保存し、関数を呼び出すときに引数を指定しない場合は、それをデフォルトとして使用します。

>>> item = 1
>>> func = lambda item=item: print(item)
>>> func()
1
>>> item = 2
>>> func()
1

これは、後でプログラムで使用するために現在のスコープから値を保存する便利な方法として使用できます。しかし、ここに問題があります。これはデフォルト値です。関数に引数を与えない場合にのみ使用されます。前のコード サンプルの続き:

>>> func(3)
3
>>> item = 4
>>> func()
1
>>> func(item)
4

あなたはアイデアを得る。

接続している PyQt スロット、つまりQAbstractButton.clicked()が引数を取ることあります。これは、ボタンが現在「チェック」されているかどうかを示すブール値です。print_text(Qt では、ボタンはバイナリ状態がアタッチされたトグル コントロールです。標準のボタンと考えられるのは、Falseトグル状態を決して設定しない単なる Qt ボタンです)。ボタンの状態。「適切な」解決策の 1 つは、次のことを反映する明示的な引数を挿入することです。

button.clicked.connect(lambda checked, x=value: self.print_text(x))

もちろん、コードを明確にするためには、Dunes が提案しpartialたようにオブジェクトを使用する方がよいでしょう。そうすれば、関数に渡される引数によってオーバーライドできない方法で値を保存できます。

button.clicked.connect(partial(self.print_text, value))

もう 1 つのオプションは、値を格納する独自のオブジェクトを作成し、そのオブジェクトのメソッドを に渡すことconnect()です。print_textこのオブジェクト自体で関数を定義できます。

class MyPartial:
    def __init__(self, value):
        self.value = value
    def print_text(self):
        # ...

...

button.clicked.connect(MyPartial(value).print_text)

または、オブジェクトを呼び出し可能にprint_textしてその__call__メソッドに実装することもできます。これにより、オブジェクト自体が関数のように動作します。

class MyPartial:
    def __init__(self, value):
        self.value = value
    def __call__(self):
        # equivalent of print_text() goes here

...

button.clicked.connect(MyPartial(value))

print_text()または、プログラムに適している場合は、実際のメソッドを持つものへの参照をオブジェクトに与えることもできます。

class MyPartial:
    def __init__(self, func, value):
        self.func = func
        self.value = value
    def __call__(self):
        self.func(self.value)

...

button.clicked.connect(MyPartial(self.print_text, value))

など。これらのオプションはすべて、ビルトインの機能のバリエーションfunctools.partialです。

于 2016-05-01T15:44:28.437 に答える