[Tkinter] Lambda seems to call expression 1 extra time every time it's called. - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: GUI (https://python-forum.io/forum-10.html) +--- Thread: [Tkinter] Lambda seems to call expression 1 extra time every time it's called. (/thread-18417.html) Pages:
1
2
|
Lambda seems to call expression 1 extra time every time it's called. - DreamingInsanity - May-16-2019 Now, excuse if this is in the forum forum, even though I use a GUI, I don't beleive that's what's causing the problem. The title makes this a little more confusing so let me explain. I if do a = lambda: some_function(16)then call 'a' (I do this when a button is clicked, but that's beside the point). And 'some_function' is: def some_function(some_number): print(some_number)the output should be Then when 'a' is called again, it should again print However, with my code, I call 'a' once, and like expected it prints '12'. I then call it again, but this time, instead of printing '12' once, it print it twice. Like this: Even though the example is what happens, I will explain it with my real code, as it slightly different. When a button is pressed, a random number 1 through 6 is picked. When another button is pressed, it will call a function with parameters of either "P1" meaning player 1 or "P2" meaning player 2 and the random number. The player is changed every go (e.g. You press the button, a number is generated, you move your character. It becomes player 2's go. Player 2 does the same thing, it's now player 1's go). This is how I do it: a = lambda: self.move_players("P1" if self.currPl == 1 else "P2", random_number) #self.currPl evaluates to either 1 or two.Now, on the first go, if player 1 rolles a 3, the function is called like self.move_players("P1", 3)Fine - it works. Then it becomes player 2's go. Let's say player 2 roll a 5, the function is, again, called like self.move_players("P2", 5)However, here lies the problem: if in the 'self.move_playersI(self, players, spaces)' function I do 'print(players, spaces)'. On player 1's first go it says Then on player 2's go it says Remember how player 1 rolled a 3. It calls the function once with player 1's value for player 2, then again this time with player 2's actuall value. In some cases, this means player 2 moves double the intended spaces.Why does this happen? Is it the way I use lambda or the way I detect if a button is clicked? Thanks in advance, Dream the full script can be found here: https://pastebin.com/yh5jxHFU Full code import sys from PyQt5 import QtWidgets from PyQt5 import QtCore from PyQt5 import QtGui from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * import time import random from guiLoop import guiLoop from functools import partial class Settings(): SCENE_SIZE_X = 1200 #Settings for everything SCENE_SIZE_Y = 900 GRID_COUNT_X = 4 GRID_COUNT_Y = 5 GRID_COUNT_TOTAL = GRID_COUNT_X * GRID_COUNT_Y GRID_BOX_WIDTH = 100 GRID_BOX_HEIGHT = 100 DEF_FONT_SIZE = 15 DEF_PLAYER_SIZE = 25 DEF_LEFT_PADDING = 5 P1_TOP_PADDING = 10 P2_TOP_PADDING = DEF_PLAYER_SIZE * 2 + 10 ROLL_TIME = 20 class Drawing(): p1 = QPen(QColor(66, 134, 244), 1, Qt.SolidLine) #p1 blue, #4286f4 brushP1 = QBrush(QColor(66, 134, 244), Qt.SolidPattern) p2 = QPen(QColor(244, 65, 65), 1, Qt.SolidLine) #p2 red, #f44141 brushP2 = QBrush(QColor(244, 65, 65), Qt.SolidPattern) transparent = QPen(QColor(255, 255, 255), 1, Qt.SolidLine) transparentB = QBrush(QColor(255, 255, 255), Qt.SolidPattern) class Grid(QtWidgets.QGraphicsScene): def __init__(self): super().__init__() self.scrolled = 0 self.ps = Players() self.texts = [] self.initUI() def initUI(self): self.draw_grid(Settings.GRID_COUNT_X, Settings.GRID_COUNT_Y, Settings.GRID_BOX_WIDTH, Settings.GRID_BOX_HEIGHT) #drawing grid first self.draw_text(Settings.GRID_COUNT_X, Settings.GRID_COUNT_Y, Settings.GRID_BOX_WIDTH, Settings.GRID_BOX_HEIGHT, Settings.DEF_FONT_SIZE) #drawing text next rect = self.ps.draw_players("P1", 0) self.addRect(rect[0], Drawing.p1, Drawing.brushP1) rect = self.ps.draw_players("P2", 0) self.addRect(rect[1], Drawing.p2, Drawing.brushP2) def draw_grid(self, x_count, y_count, x_size, y_size): width = x_count * x_size height = y_count * y_size self.setSceneRect(0, 0, width, height) self.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex) pen = QPen(QColor(0,0,0), 1, Qt.SolidLine) for x in range(0,x_count+1): for y in range(0,y_count+1): #nested for loop for x and y (0,0) - (0,1) - (0,2) etc xc = x * x_size yc = y * y_size self.addLine(xc,0,xc,height,pen) self.addLine(0,yc,width,yc,pen) def draw_text(self, x_count, y_count, x_size, y_size, font_size): font = QFont("Courier New", font_size, QFont.Bold) total = Settings.GRID_COUNT_TOTAL row_count = Settings.GRID_COUNT_Y text_pos_ev = [] text_pos_od = [] for y in range(0,y_count): #another nested for loop with out the +1 to y and x count for x in range(0,x_count): xcTEXT = x * x_size ycTEXT = y * y_size if row_count % 2 == 0: #if the row is even text_pos_ev.append([xcTEXT, ycTEXT, total]) #add to even array else: text_pos_od.append([xcTEXT, ycTEXT, total]) #otherwise add to odd array total = total - 1 row_count = row_count - 1 for pos in text_pos_ev: #draws the even numbers text = self.addText(str(pos[2]), font) self.texts.append(text) text.setPos(pos[0], pos[1]) text.setOpacity(0.6) new_text_pos_od = reverse_section(text_pos_od, 4) #flips the odd numbers so they snake around the board for pos in new_text_pos_od: #draws new odd numbers text = self.addText(str(pos[2]), font) self.texts.append(text) text.setPos(pos[0], pos[1]) text.setOpacity(0.6) def move_players(self, player, spaces): rect = [] if(player == "P1"): self.remove_player("P1") rect = self.ps.draw_players("P1", spaces) self.addRect(rect[0], Drawing.p1, Drawing.brushP1) elif(player == "P2"): self.remove_player("P2") rect = self.ps.draw_players("P2", spaces) self.addRect(rect[1], Drawing.p2, Drawing.brushP2) def remove_player(self, player): rect = self.ps.clear_old_player(player) self.addRect(rect, Drawing.transparent, Drawing.transparentB) self.redraw_text() def redraw_text(self): for text in self.texts: self.removeItem(text) del self.texts[:] self.draw_text(Settings.GRID_COUNT_X, Settings.GRID_COUNT_Y, Settings.GRID_BOX_WIDTH, Settings.GRID_BOX_HEIGHT, Settings.DEF_FONT_SIZE) class Players(QRectF): def __init__(self): super().__init__() self.poses = [] self.row_count = 0 self.p1 = QRectF() self.p2 = QRectF() self.p1pos = 0 self.p2pos = 0 self.initUI() def initUI(self): self.gen_pos_arr() def draw_players(self, player, spaces): #space 1 is box 1, 2 is box 2, 3 is box 3.. if(player == "P1"): self.p1pos += spaces; elif(player == "P2"): self.p2pos += spaces; self.p1 = QRectF(self.poses[self.p1pos][0] + Settings.DEF_LEFT_PADDING, self.poses[self.p1pos][1] + Settings.P1_TOP_PADDING, Settings.DEF_PLAYER_SIZE, Settings.DEF_PLAYER_SIZE) self.p2 = QRectF(self.poses[self.p2pos][0] + Settings.DEF_LEFT_PADDING, self.poses[self.p2pos][1] + Settings.P2_TOP_PADDING, Settings.DEF_PLAYER_SIZE, Settings.DEF_PLAYER_SIZE) return [self.p1, self.p2] def clear_old_player(self, player): pos = [] if(player == "P1"): pos = self.poses[self.p1pos] pos[0] += Settings.DEF_LEFT_PADDING pos[1] += Settings.P1_TOP_PADDING elif(player == "P2"): pos = self.poses[self.p2pos] pos[0] += Settings.DEF_LEFT_PADDING pos[1] += Settings.P2_TOP_PADDING return QRectF(pos[0], pos[1], Settings.DEF_PLAYER_SIZE, Settings.DEF_PLAYER_SIZE) def gen_pos_arr(self): y = Settings.GRID_COUNT_Y-1 for i in range(1, Settings.GRID_COUNT_Y+1): if(y % 2 == 0): for x in range(0, Settings.GRID_COUNT_X): self.poses.append([x*Settings.GRID_BOX_WIDTH,y*Settings.GRID_BOX_HEIGHT]) else: for x in range(Settings.GRID_COUNT_X-1, -1, -1): self.poses.append([x*Settings.GRID_BOX_WIDTH,y*Settings.GRID_BOX_HEIGHT]) y = y - 1 class App(QtWidgets.QDialog): def __init__(self): super().__init__() self.images = ['1.png','2.png','3.png','4.png','5.png','6.png'] self.image = QLabel(self) self.currTurn = "<font color='#4286f4'>Player 1's turn</font>" self.turn = QLabel(self) self.roll = QPushButton("Roll!") self.move = QPushButton("Move player") self.currPl = 1 self.vbox1 = QVBoxLayout() self.vbox2 = QVBoxLayout() self.grid = Grid() self.initUI() def initUI(self): self.setGeometry(300, 100, Settings.SCENE_SIZE_X, Settings.SCENE_SIZE_Y) self.setWindowTitle("NEA Board Game") self.layout() @guiLoop def move_players(self, players, spaces): temp = 0 print(players, spaces) self.move.setEnabled(False) self.turn.setText(self.currTurn) #self.grid.move_players(players, spaces) if(self.currPl == 1): while(temp < spaces): self.grid.move_players("P1", 1) temp += 1 yield 0.5 else: while(temp < spaces): self.grid.move_players("P2", 1) temp += 1 yield 0.5 if(self.currPl == 1): self.currPl = 2 self.currTurn = "<font color='#f44141'>Player 2's turn</font>" self.turn.setText(self.currTurn) elif(self.currPl == 2): self.currPl = 1 self.currTurn = "<font color='#4286f4'>Player 1's turn</font>" self.turn.setText(self.currTurn) self.roll.setEnabled(True) spaces = 0 def layout(self): gridGview = QtWidgets.QGraphicsView(self.grid) d = self.dice() layout = QGridLayout(self) layout.addWidget(d[1], 2, 1) layout.addWidget(d[0], 1, 1) layout.addWidget(gridGview, 0, 1) def dice(self): rollBox = QGroupBox("") font = QtGui.QFont("Courier New", 23, QtGui.QFont.Bold) self.roll.clicked.connect(self.diceRoll) self.turn.setText(self.currTurn) self.turn.setFont(font) self.turn.setAlignment(QtCore.Qt.AlignCenter) self.vbox1.addWidget(self.turn) self.vbox1.addWidget(self.roll) rollBox.setLayout(self.vbox1) diceBox = QGroupBox("") self.vbox2.addWidget(self.move) self.move.setEnabled(False) diceBox.setLayout(self.vbox2) return [rollBox, diceBox] @guiLoop def diceRoll(self, argument): temp = 0 rollT = Settings.ROLL_TIME self.roll.setEnabled(False) while(temp <= rollT): yield ((temp * 0.02) + 0.05) img = random.choice(self.images) pixmap = QPixmap(img) self.image.setPixmap(pixmap) self.vbox2.addWidget(self.image) temp += 1 self.move.setEnabled(True) a = lambda: self.move_players("P1" if self.currPl == 1 else "P2", int(img[0:1])) self.move.clicked.connect(a) def reverse_section(arr, subsection): ml2 = [ (arr[i*subsection:(i+1)*subsection]) for i in range(int(len(arr)/subsection)) ] ml3 = [ sl[i][0:subsection-2] + [sl[len(sl)-i-1][2]] for sl in ml2 for i in range(len(sl)) ] return ml3 if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) app.setStyle("Fusion") ex = App() ex.show() sys.exit(app.exec_()) RE: Lambda seems to call expression 1 extra time every time it's called. - ichabod801 - May-16-2019 The problem is not with lambda: >>> def fred(number): ... print(number) ... >>> a = lambda: fred(16) >>> a() 16 >>> a() 16 >>>I expect the problem is that you are using yield in move_players instead of return. That means you are creating a generator on a click rather than returning from a function. RE: Lambda seems to call expression 1 extra time every time it's called. - DreamingInsanity - May-16-2019 (May-16-2019, 05:32 PM)ichabod801 Wrote: I expect the problem is that you are using yield in move_players instead of return. That means you are creating a generator on a click rather than returning from a function. Oh I see. How can I get around this? I have to use yield so my gui doesn't freeze in the while loop. RE: Lambda seems to call expression 1 extra time every time it's called. - micseydel - May-16-2019 This is interesting. I looked a little bit but don't have time to deep-dive on this. If you can provide a runnable snippet of code which reproduces this problem without GUI code though (ideally zero imports), I'll make time to look deeper. RE: Lambda seems to call expression 1 extra time every time it's called. - ichabod801 - May-16-2019 (May-16-2019, 06:09 PM)DreamingInsanity Wrote: Oh I see. How can I get around this? I have to use yield so my gui doesn't freeze in the while loop. I think you need to refigure how you are doing movement. When you click on something to move it, you should change it's state to 'trying to move to x, y'. Then you should periodically update your game. When you update things, you check every object for movement. If it is trying to move somewhere, you move it a little closer. If it gets there, change it's state to 'not moving'. RE: Lambda seems to call expression 1 extra time every time it's called. - DreamingInsanity - May-17-2019 (May-16-2019, 07:24 PM)micseydel Wrote: This is interesting. I looked a little bit but don't have time to deep-dive on this. If you can provide a runnable snippet of code which reproduces this problem without GUI code though (ideally zero imports), I'll make time to look deeper. I don't think I can without a gui. (May-16-2019, 07:53 PM)ichabod801 Wrote: I think you need to refigure how you are doing movement. When you click on something to move it, you should change it's state to 'trying to move to x, y'. Then you should periodically update your game. When you update things, you check every object for movement. If it is trying to move somewhere, you move it a little closer. If it gets there, change it's state to 'not moving'. If I remove out the while loops and add this line in: self.grid.move_players(players, spaces)which is commented out in my code I provided, even though there is no yield, I still get the same problem. So correct me if I'm wrong but, changing the way I do movement, won't really do much. D RE: Lambda seems to call expression 1 extra time every time it's called. - ichabod801 - May-17-2019 (May-17-2019, 05:22 PM)DreamingInsanity Wrote: If I remove out the while loops and add this line in: Seeing as what you describe is not what I suggested you do, it's not really a counter argument to what I suggested you do. And if you GUI hangs up without the loops and the yields, there's something else going on. RE: Lambda seems to call expression 1 extra time every time it's called. - DreamingInsanity - May-17-2019 (May-17-2019, 05:37 PM)ichabod801 Wrote: Seeing as what you describe is not what I suggested you do, it's not really a counter argument to what I suggested you do. And if you GUI hangs up without the loops and the yields, there's something else going on. Ok, fine, I will try out what you said. Also, my GUI only hangs if I am using while loops with time.sleep(x)(it hangs until the loop is over) To stop it hangning, I use this script (modified to work with PyQt5) and use yields instead of 'time.sleep()'. D RE: Lambda seems to call expression 1 extra time every time it's called. - Yoriz - May-17-2019 It looks like every time the roll button is clicked the method diceRoll is called in which a new lambda is connected to the move player button.player 1 rolls, 1 lambda function gets connected to the player button. player 1 clicks move, the 1st lambda function is called. player 2 rolls, another lambda function gets connected to the player button. player 2 clicks move, the 1st lambda plus the 2nd lambda gets called. This will continue to increase every time roll is clicked. RE: Lambda seems to call expression 1 extra time every time it's called. - DreamingInsanity - May-18-2019 (May-17-2019, 09:46 PM)Yoriz Wrote: It looks like every time the roll button is clicked the method That is kind of what I thought might have been happening. Can remove the lambda though so there is always 1? I have tried using partial from functools but that does the same. D |