当我尝试删除之前在 PySide6 应用程序中“播放”的 .GIF 文件时,我将非常感谢您帮助解决这个 PermissionError 问题。 完整的可复制代码+下面的示例 .gif 文件。
在我的 PySide6 应用程序中,我创建一个
QLabel
并在其中显示一个 .GIF 文件。
这涉及到首先创建一个
QMovie
对象(它接收当前目录中 .GIF 文件的相对路径),然后使用 QLabel.setMovie(QMovie)
方法在标签内显示 .GIF 文件。 最后,QMovie.start()
方法开始播放.GIF。
此后,我希望能够删除 .GIF 文件。 我读过两个用于删除文件的Python函数:
os.remove()
和pathlib.Path.unlink()
。
在没有任何 PySide6 代码的情况下,这两个函数都可以按需要工作。 我可以从我的计算机中删除 .GIF 文件,而不会出现任何 PermissionError。
当我在 PySide6 应用程序中调用 QMovie.start()
来“播放”我的 .GIF 文件后,尝试调用这些函数中的任何一个时,会出现 PermissionError。 这是完整的 PermissionError 语句:
PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'test_gif_question_mark.gif'
我已经使用
psutil
读出了我机器上的所有进程,它告诉我进程
python.exe
正在“使用”我的 .GIF 文件,并且没有其他进程正在使用它。如果我不调用QMovie.start()
,则不会出现PermissionError,即在创建QMovie
对象后,甚至在调用
QLabel.setMovie(QMovie)
之后,我可以自由删除.GIF文件。
很明显,我的 PySide6 应用程序打开并读取 .GIF 文件中的数据,以便显示和“播放”与其关联的动画。 对我来说不明显的是如何“关闭”.GIF 文件,以便
python.exe
进程停止“使用”.GIF 文件,让我可以自由地删除它。
这是我尝试“关闭”.GIF 文件的一系列操作:打电话
QMovie.stop()
打电话
QLabel.clear()
使用
QMovie
del
对象。使用
QLabel
del
对象。关闭
QApplication
使用
QApplication
del
.使用
QMainWindow
del
实例 - 这是我的变量中除上述所有操作之外的唯一其他实例。如果我随后尝试使用
os.remove()
Path.unlink()
删除 .GIF 文件,上述方法都不会阻止 PermissionError 。 使用
psutil
,我可以看到即使在所有这些方法之后,
python.exe
仍然“使用”我的 .GIF 文件。
在我看来,
QMovie.stop()
没有做我想要它做的事情 - 我希望它“放开”并“关闭”.GIF文件,以便python.exe
进程不再使用它,我可以要求 Windows 删除该文件。
如果您知道我如何强制
python.exe
停止使用.GIF文件,或者有任何其他解决方案,我将非常感谢您的帮助。
完整的可重现代码:
import sys
import os
from pathlib import Path
from PySide6.QtWidgets import QMainWindow, QVBoxLayout, QWidget, QApplication, QPushButton, QLabel, QHBoxLayout
from PySide6.QtGui import QMovie
# Description: This script builds a small QApplication GUI with buttons that stimulate various steps in the process
# of using, displaying, stopping using, and finally trying to delete a .gif file.
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
# Specify the path to the gif file from the current directory:
self.path_to_gif = 'test_gif_question_mark.gif'
# Create an instance of the QLabel class:
self.my_label = QLabel()
# Placeholder for the QMovie object:
self.my_QMovie = None
# Create a button to make the QMovie object:
my_button_create_qmovie = QPushButton()
my_button_create_qmovie.setText('Create QMovie')
my_button_create_qmovie.clicked.connect(self.clicked_create_button)
# Create a button to display the QMovie object in the QLabel:
my_button_display_qmovie = QPushButton()
my_button_display_qmovie.setText('Display QMovie')
my_button_display_qmovie.clicked.connect(self.clicked_display_button)
# Create a button to try to stop using the gif:
my_button_stop_using_gif = QPushButton()
my_button_stop_using_gif.setText('Try to stop using gif file')
my_button_stop_using_gif.clicked.connect(self.clicked_stop_button)
# Create a button to try to delete the gif:
my_button_delete_gif = QPushButton()
my_button_delete_gif.setText('Delete gif file')
my_button_delete_gif.clicked.connect(self.clicked_delete_button)
# Place the buttons in a horizontal layout:
layout_h_1 = QHBoxLayout()
layout_h_1.addWidget(my_button_create_qmovie)
layout_h_1.addWidget(my_button_display_qmovie)
layout_h_2 = QHBoxLayout()
layout_h_2.addWidget(my_button_stop_using_gif)
layout_h_2.addWidget(my_button_delete_gif)
# Assign the horizontal layouts to empty widgets:
widget_h_1 = QWidget()
widget_h_1.setLayout(layout_h_1)
widget_h_2 = QWidget()
widget_h_2.setLayout(layout_h_2)
# Place the label & buttons in a vertical layout:
layout_v = QVBoxLayout()
layout_v.addWidget(self.my_label)
layout_v.addWidget(widget_h_1)
layout_v.addWidget(widget_h_2)
# Create a placeholder widget to hold the layout.
widget = QWidget()
widget.setLayout(layout_v)
# Set the central widget of the main window:
self.setCentralWidget(widget)
def clicked_create_button(self):
# Create a QMovie to display:
self.my_QMovie = QMovie(self.path_to_gif)
print('\nCreated QMovie object using path to .gif file')
def clicked_display_button(self):
# Display the gif inside the label:
self.my_label.setMovie(self.my_QMovie)
# Start the movie playing:
self.my_QMovie.start()
print('\nDisplayed and started QMovie')
def clicked_delete_button(self):
# Use os.remove to try to delete the gif file:
# os.remove(self.path_to_gif)
# Use pathlib.unlink to try to delete the gif file:
path_gif = Path(self.path_to_gif)
path_gif.unlink()
def clicked_stop_button(self):
# Try different things to get python to stop using the .gif file:
# Try 'stopping' the QMovie object:
self.my_QMovie.stop()
# Try clearing the QLabel:
self.my_label.clear()
# Try deleting the QMovie object:
del self.my_QMovie
# Try deleting the QLabel:
del self.my_label
# Run the QApplication:
app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
print('app finished')
# Try deleting the QApplication after it has finished:
del app
# Try deleting the MainWindow:
del w
# Try deleting the gif file even after the QApplication has closed:
os.remove('test_gif_question_mark.gif')
.用于测试的GIF文件:
test_gif_question_mark.gif
这是PySide6的一个bug。当您调用source
中看到的那样,绑定库假定标签拥有电影,但 C++ 实现实际上并没有使其发生。在这里报告错误怎么样? 无论如何,这可以通过多种方式来补救,例如
self.my_QMovie.deleteLater()
或self.my_QMovie.setDevice(None)
,但最好的方法是让标签在C++端拥有电影,如下例所示。
...
class MainWindow(QMainWindow):
...
def clicked_create_button(self):
self.my_QMovie = QMovie(self.path_to_gif, parent=self.my_label)
...
def clicked_stop_button(self):
self.my_QMovie.setParent(None)
self.my_QMovie = None
self.my_label.clear()
...
print('app finished')
# You don't need to delete the app
del w
os.remove('test_gif_question_mark.gif')