永远不会调用作为本地实例化 class 方法的插槽

Slot as method of a locally instantiated class is never called

我有一个 mp3 文件列表,每当按下不同的按钮时,我都会尝试通过 pygame 播放这些文件(每个按钮一个文件)。由于这些文件的数量是可变的,我只是实现了一个 for 循环,并且我有一个 AudioPlayer class 每次实例化如下:

import sys, pygame
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class AudioPlayer(QObject):
    def __init__(self, filename):
        super().__init__()
        self.filename = filename
        print("Created " + filename)

    def play(self):
        print("Playing " + self.filename)
        pygame.mixer.music.load(self.filename)
        pygame.mixer.music.play()

class Session(QMainWindow):
    def __init__(self):
        super().__init__()
        self.mainWid = QWidget(self)
        self.vbox = QVBoxLayout()
        self.mainWid.setLayout(self.vbox)
        self.setCentralWidget(self.mainWid)
        self.show()

        pygame.mixer.init()

        filenames = [r'C:\...\file1.mp3', r'C:\...\file2.mp3']

        for filename in filenames:
            playButton = QPushButton('Play', self)
            localPlay = AudioPlayer(filename)
            playButton.clicked.connect(localPlay.play)
            self.vbox.addWidget(playButton)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    Session()
    sys.exit(app.exec_())

我的问题很简单,按下按钮时文件不播放,消息也根本不打印;就像插槽永远不会被调用一样:

admin@home> python main2.py
Created C:\...\file1.mp3
Created C:\...\file2.mp3
admin@home>

如果我在循环之外手动播放文件,就像这样,它会起作用:

class Session(QMainWindow):
    def __init__(self):

        # ...

        filenames = [r'C:\...\file1.mp3', r'C:\...\file2.mp3']

        pygame.mixer.music.load(filenames[0])
        pygame.mixer.music.play()

如您所见,我确保AudioPlayer继承了QObject并调用了__init__,所以我相信它应该可以接收信号。那么这里发生了什么?是局部变量问题吗?

问题是由于在循环中创建的 AudioPlayer 对象是局部变量,所以当它们完成 运行 构造函数时,它们会从内存中删除。有两种可能的解决方案,第一种是使它们成为 class 的属性,或者第二种是将它们传递给父级,因为它们继承自 QObject,我将使用第二种方法:

class AudioPlayer(QObject):
    def __init__(self, filename, parent=None):
        super().__init__(parent=parent)
        self.filename = filename
        print("Created " + filename)

    @pyqtSlot()
    def play(self):
        print("Playing " + self.filename)
        pygame.mixer.music.load(self.filename)
        pygame.mixer.music.play()

class Session(QMainWindow):
    def __init__(self):
        super().__init__()
        self.mainWid = QWidget(self)
        self.vbox = QVBoxLayout()
        self.mainWid.setLayout(self.vbox)
        self.setCentralWidget(self.mainWid)
        self.show()

        pygame.mixer.init()

        filenames = [r'C:\...\file1.mp3', r'C:\...\file2.mp3']

        for filename in filenames:
            playButton = QPushButton('Play', self)
            localPlay = AudioPlayer(filename, self)
            playButton.clicked.connect(localPlay.play)
            self.vbox.addWidget(playButton)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = Session()
    sys.exit(app.exec_())