Особенность работы lambda в цикле
У меня было три QLineEdit и функция upper(), написанная вне класса. Функция должна была переводить вводимый текст в верхний регистр.
Задал вопрос связанный с особенностями работы lambda в цикле. Получил ответ - для того, чтобы lambda работала не только для последней итерации, надо в неё передать необязательный аргумент:
i.textChanged.connect(lambda i=i: upper(i.text()))
И действительно, lambda стала работать и для других итераций, но возникла ошибка:
Код:
self.lineedit1 = QtWidgets.QLineEdit()
self.lineedit2 = QtWidgets.QLineEdit()
self.lineedit3 = QtWidgets.QLineEdit()
temp = [self.lineedit1, self.lineedit2, self.lineedit3]
for i in temp:
i.textChanged.connect(lambda i=i: upper(i.text()))
Ошибка:
AttributeError: 'str' object has no attribute 'text'
По какой-то причине i в lambda - строка, а не объект QLineEdit.
Решил явно указать объект и текст:
for i in temp:
i.textChanged.connect(lambda obj=i, text=i.text(): upper(obj, text))
def upper(object, text):
object.setText(text.upper())
Возникла ошибка:
AttributeError: 'str' object has no attribute 'setText'
Почему-то object всё равно оставался str хотя я явно указал, что он QLineEdit.
Решил поменять obj и text местами в lambda и upper():
for i in temp:
i.textChanged.connect(lambda text=i.text(), obj=i: upper(text, obj))
def upper(text, object):
object.setText(text.upper())
Всё работает как надо, но остались вопросы:
- Почему
lambdaпервым аргументом принимает вводимый текст, независимо от того, что записано в её аргументах? - Если
i-str, то почему работает сточкаtext=i.text()ведьstrне имеет атрибута.text()?
Запись lambda text=i, obj=i: upper(text, obj) работает.
Добавил в upper() строчки:
print(type(text)) -> <class 'str'>
print(type(object)) -> <class 'PyQt5.QtWidgets.QLineEdit'>
- Как так получается, что
i- это иstr, иQLineEdit?
Ответы (1 шт):
Смотрим https://doc.qt.io/qt-5/qlineedit.html#signals
void QLineEdit::
textChanged(const QString &text) [signal]Этот сигнал испускается всякий раз, когда текст изменяется.
text- это новый текст.В отличие от
textEdited(), этот сигнал также излучается при программном изменении текста, например, путем вызоваsetText().Примечание: сигнал уведомителя для
property text. Больше https://doc.qt.io/qt-5/qlineedit.html#text-prop
Т.е. сигнал textChanged и предназначен для того, чтобы в случае изменения
текста в QLineEdit уведомить вас об этом и передать
этот новый текст первым параметром. В слоте надо принять это новое значение.
def upper(new_text):
print(f'Это новый текст: {new_text}')
В вашем случае вы хотите дополнительно передать некий объект, который в слоте будет приниматься Вторым аргументом.
temp = [self.lineedit1, self.lineedit2, self.lineedit3]
for i in temp:
i.textChanged.connect(lambda text, obj=i: self._upper(text, obj))
def _upper(self, text, object):
object.setText(text.upper())
Почему
lambdaпервым аргументом принимает вводимый текст, независимо от того, что записано в её аргументах?
Потому что так решили разработчики, создавая данный сигнал:
void QLineEdit::`textChanged`(const QString &text) [signal]
Если i - str, то почему работает сточка text=i.text() ведь str не имеет атрибута .text()?
# `i` - это не `str`, а один из объектов, находящихся в списке `temp`:
temp = [self.lineedit1, self.lineedit2, self.lineedit3]
for i in temp:
...
Не надо писать так lambda text=i, obj=i: upper(text, obj)
это приведет к некоему непониманию, у читающих ваш код.
Как так получается, что i - это и str, и QLineEdit?
Пишите так и это будет понятно всем:
lambda text, obj=self.line_edit : self.on_text_changed(text, obj)
from PyQt5 import QtWidgets, QtCore
class Widget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.line_edit = QtWidgets.QLineEdit()
# 1 self.line_edit.textChanged.connect(self.on_text_changed)
self.line_edit.textChanged.connect( # 2
lambda text, obj=self.line_edit : self.on_text_changed(text, obj)
)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.line_edit)
# 1 def on_text_changed(self, new_text):
def on_text_changed(self, new_text, obj): # 2
print(f'Это новый текст: {new_text}')
print(f'Это некий объект: {obj}') # 2
if __name__ == '__main__':
app = QtWidgets.QApplication([])
mw = Widget()
mw.show()
app.exec()
