Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
274 views
in Technique[技术] by (71.8m points)

python - PyQt connect inside for loop vs. separate calls results in different behavior

I'm building a plotting UI that lets a user generate multiple plots from loaded data sets. As part of this the user can add marker lines to their plots to examine (x, y) values by moving those marker lines across the plot.

The functions below work fine if the markers are added to each plot separately (i.e. add_marker to Plot1, Plot2, etc. separately via the elif block of code). However, if the "add to all" option is selected, resulting in the usage of the for loop block of code in the add_marker function all of the markers end up being children of the last plotItem in the list (plot_objects).

If I check the marker objects as the loop iterates when the add_marker function is called the objects, and their parents, are distinct. However, if I check the parents in the update_marker_vals function the parent for markers 1 and 2 are incorrect for all but the last plot in the list.

Not sure what's going on here, but I assume it has something to do with the two sigDragged.connect statements, seeing as everything else seems fine before then.

Code:

def add_marker(self):
        name = self.markersPlot_dropdown.currentText()

        if name == "All":
            for plot_name, plt in self.plot_objects.items():
                x_val = (plt.viewRange()[0][0]+plt.viewRange()[0][1])/2
                marker_one = plt.addLine(x=x_val*0.5, pen=pg.mkPen('g', width=2.0), movable=True)
                marker_two = plt.addLine(x=x_val*1.5, pen=pg.mkPen('c', width=2.0), movable=True)
                marker_one.sigDragged.connect(lambda: self.update_marker_vals(marker_one, "Marker One"))
                marker_two.sigDragged.connect(lambda: self.update_marker_vals(marker_two, "Marker Two"))

                self.plot_markers[plot_name] = {"Marker One": marker_one, "Marker Two:": marker_two}

        elif name:
            plt = self.plot_objects[name]
            x_val = (plt.viewRange()[0][0]+plt.viewRange()[0][1])/2
            marker_one = plt.addLine(x=x_val*0.5, pen=pg.mkPen('g', width=2.0), movable=True)
            marker_two = plt.addLine(x=x_val*1.5, pen=pg.mkPen('c', width=2.0), movable=True)
            marker_one.sigDragged.connect(lambda: self.update_marker_vals(marker_one, "Marker One"))
            marker_two.sigDragged.connect(lambda: self.update_marker_vals(marker_two, "Marker Two"))

            self.plot_markers[name] = {"Marker One": marker_one, "Marker Two:": marker_two}

    def update_marker_vals(self, marker, marker_num):
        plot_item = marker.parentItem().parentItem().parentItem()
        plot_name = list(self.plot_objects.keys())[list(self.plot_objects.values()).index(plot_item)]
        sampling_divisor = self.selected_curve[plot_name].getData()[0][1] - 
                           self.selected_curve[plot_name].getData()[0][0]
        index = int(marker.getXPos() / sampling_divisor)
        x_val = self.selected_curve[plot_name].getData()[0][index]
        y_val = self.selected_curve[plot_name].getData()[1][index]
        if marker_num == "Marker One":
            print(plot_name)
            print("Marker One, :" + str(index))
            print(x_val, y_val)

        elif marker_num == "Marker Two":
            print(plot_name)
            print("Marker Two, :" + str(index))
            print(x_val, y_val) 

Edit:

On a side note, as a solution I can separate this function out into two functions - one function that creates the two markers and then another function that takes the input from the QComboBox and creates one set of markers for a specific plot or creates markers for all the plots available. This works, and is my current solution, but I'm still curious as to what the issue is with the above code.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

When you connect a signal to a lambda function, the contents of the lambda function are evaluated when the signal is emitted, not when the signal is connected. As such, the variables you use (marker_one and marker_two) always point to the objects created in the last iteration of the loop.

One simple solution is to explicitly pass in marker_one and marker_two as default arguments to variables of the same name, in the signature of the lambda function:

lambda marker_one=marker_one: self.update_marker_vals(marker_one, "Marker One")
lambda marker_two=marker_two: self.update_marker_vals(marker_two, "Marker Two")

There are several useful answers relating to a very similar problem here, specifically the answer by ekhumoro, if you would like to know more (my answer to that question my also be of use, although ekhumoro's solution is cleaner)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...