當我試圖從QPlainTextEdit中執行一個字串/檔案時遇到了一些問題,這似乎是某種范圍問題。 發生的情況是,當代碼EXECUTABLE_STRING從全域范圍運行時,它可以正常作業。 然而,當它從區域范圍運行時,例如通過AbstractPythonCodeWidget,它要么找不到要做繼承的物件TypeError: super(type, obj): obj must be an instance or subtype of type,要么遇到一個名稱錯誤NameError: name 'Test' is not defined。 奇怪的是,這一點根據運行時exec(EXECUTABLE_STRING)行是否被注釋/未注釋而改變。 如果有任何幫助,我們將不勝感激。
import sys
from PyQt5.QtWidgets import QApplication, QPlainTextEdit
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor
app = QApplication(sys.argv)
EXECUTABLE_STRING = ""
from PyQt5.QtWidgets import QLabel, QApplication
從PyQt5.QtCore匯入Qt
從PyQt5.QtGui匯入QCursor
class Test(QLabel):
def __init__(self, parent=None):
super(Test, self).__init__(parent)
self.setText("Test")
a = 測驗()
a.show()
a.move(QCursor.pos())
""
class AbstractPythonCodeWidget(QPlainTextEdit)。
def __init__(self, parent=None)。
super(AbstractPythonCodeWidget, self).__init__(parent)
self.setPlainText(EXECUTABLE_STRING)
def keyPressEvent(self, event)。
if event.modifiers() == Qt.ControlModifier:
if event.key() == Qt.Key_Return:
# this does not work[/span].
#exec(compile(self.toPlainText(), "script", "exec"), globals(), locals()
exec(self.toPlainText())
return QPlainTextEdit.keyPressEvent(self, event)
w = AbstractPythonCodeWidget()
w.show()
w.move(QCursor.pos())
w.resize(512, 512)
# 當在這里運行時可以作業,但當在按鍵事件上運行時就不行了。
# exec(EXECUTABLE_STRING)
sys.exit(app.exec_())
uj5u.com熱心網友回復:
首先,基于用戶輸入運行exec可能是一個安全問題,但最重要的是通常會導致致命的崩潰,除非采取很多預防措施,因為你的程式和用戶代碼都使用同一個解釋器:基本上,如果用戶的代碼失敗,你的程式也會失敗,但這并不是唯一的問題。
你的代碼不能正常運行的原因實際上有點復雜,它與類名的范圍有關,當與exec一起運行super()時,它變得有點復雜。
一個有趣的方面是,如果你洗掉了 super 的引數 (你應該這樣做,因為 Python 3),程式將不會引發任何錯誤。
但是這還遠遠不夠。a是一個區域變數,一旦exec結束,它就會被垃圾回收,而且由于標簽被分配給了這個變數,它也將被一起銷毀。
一個可能的解決方案是使參考持久化,例如通過將其分配給self(因為self存在于執行腳本的范圍內)。這是一個關于EXECUTABLE_STRING的作業例子:
from PyQt5.QtWidgets import QLabel
from PyQt5.QtGui import QCursor
class Test(QLabel)。
def __init__(self, parent=None) 。
super().__init__(parent)
self.setText("Test")
self.a = Test()
self.a.show()
self.a.move(QCursor.pos())
正如你所看到的,我們不需要再匯入所有的東西,我們只匯入了QLabel,因為它在主腳本中沒有被匯入,但是其他的東西都已經存在于腳本的范圍中,包括當前的QApplication實體。
這就是說,上述所有內容只是為了了解目的,因為你不應該不使用exec來運行用戶代碼。
例如,嘗試在文本編輯中粘貼以下內容,并運行它:
self.document().setHtml('這應該<b>not</b>發生!!<br/><br/>再見!')
self.setReadOnly(True)
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication
QTimer.singleShot(2000, QApplication.quit)
正如你所看到的,上面的代碼不僅能夠改變輸入,而且能夠完全控制整個應用程式。
你可以通過呼叫一個基本上可以限制執行范圍的函式來防止這種情況:
def runCode(code)。
try:
exec(code)
except Exception as e:
return e.
class AbstractPythonCodeWidget(QPlainTextEdit)。
# ....
def keyPressEvent(self, event)。
if event.modifiers() == Qt.ControlModifier:
if event.key() == Qt.Key_Return:
error = runCode(self.toPlainText())
if error:
QMessageBox.critical(self, 'Script crash!', str(error))
return QPlainTextEdit.keyPressEvent(self, event)
但這只是因為沒有涉及到self:你仍然可以使用w.a = Test()與上面的例子。
因此,如果你想在你的程式中運行用戶制作的腳本,exec可能不是一個可接受的解決方案,除非你采取足夠的預防措施。
如果你不需要在你的程式和用戶腳本之間進行直接的互動,一種可能性是使用subprocess模塊,并通過另一個python解釋器實體來運行腳本。
[1] 如果有人有有效的資源/答案,可能會對這個話題有所啟發,請評論。
uj5u.com熱心網友回復: 在這里發現了一個類似的問題,它更深入地探討了在exec中Globals/Locals的范圍如何作業。
python exec()中的globals和locals 不想復制/粘貼整個主題,但這個帖子中對我有用的答案是:
標籤:d = dict(locals(), **globals()
exec (code, d, d)
