Как избежать циклического импорта с помощью PySide6Python

Программы на Python
Ответить
Anonymous
 Как избежать циклического импорта с помощью PySide6

Сообщение Anonymous »

Исходный файл mre.py
import sys

from PySide6.QtCore import (Qt, QSize, QPoint, QRect,
QEvent, QObject,
Signal, SignalInstance, Slot,
)

from PySide6.QtGui import (QResizeEvent, QMoveEvent,
QFocusEvent, )

from PySide6.QtWidgets import (QWidget, QPushButton, QLabel,
QHBoxLayout, QVBoxLayout,
QMainWindow, QApplication,
QSizePolicy, QLayout, QLayoutItem
)

class CustomEventFilter(QObject):
"""Installs eventFilter obj on a widget.

Args:
widget: QWidget instance
"""
def __init__(self, widget: QWidget):
# if you don't pass widget to be a parent of this evFilter,
# then the event filter will most likely be destroyed.
super().__init__(widget)

# set protected variable
self._widget = widget

# Install this filterObj instance on the given widget
self.widget.installEventFilter(self)

# Encapsulation stuff
@property
def widget(self):
return self._widget

def eventFilter(self, obj: QWidget, event: QEvent):
match (event.type()):
case _:
# For all events other than move and resize, handle according to QObject built-in eventFilter().
return super().eventFilter(obj, event)

class MeatballEventFilter(CustomEventFilter):
def __init__(self, widget, sig: Signal = None):
super().__init__(widget)
self._sig = sig

@property
def widget(self) -> QWidget:
return self._widget

@property
def sig(self) -> SignalInstance:
return self._sig

def eventFilter(self, obj: QWidget, event: QEvent):
match (event.type()):
case QEvent.FocusOut:
# print("Lost focus!\nReason: {}".format(event.reason()))
# if event.reason() == Qt.FocusReason.ActiveWindowFocusReason:
# return True

# self.sig.emit(self.widget)
return True
case QEvent.FocusIn:
# print("Gained focus!")
return True
case _:
return super().eventFilter(obj, event)

class WindowEventFilter(CustomEventFilter):
def __init__(self, widget):
super().__init__(widget)

@property
def widget(self):
return super().widget

def eventFilter(self, obj: QWidget, event:QEvent):
match (event.type()):
case QEvent.MouseButtonRelease:
#TODO
print("mouse window!")
sub: SubBox = obj.findChild(SubBox)

evFilter: SubBoxEventFilter = sub.findChild(SubBoxEventFilter)
return evFilter.eventFilter(sub, event)

# return sub.eventFilter(sub, event) # this doesn't work.
case _:
return super().eventFilter(obj, event)

class SubBoxEventFilter(CustomEventFilter):
"""blah blah blah
"""
def __init__(self, widget: QWidget):
super().__init__(widget)

@property
def widget(self):
return super().widget

def eventFilter(self, obj: QObject, event:QEvent):
# print(type(obj))
match (event.type()):
case QEvent.Resize: # TODO
# Won't matter for final product(I think?) but for test purposes will be implemented here

return True

case QEvent.Move:
# Check meatball
print("Sub moving meatball!")
if obj.findChild(TestSongMeatball):
meatball: TestSongMeatball = obj.findChild(TestSongMeatball)
meatball.findPos()
return True

# Return True to notify app that moving took place.
return True

case _:
# For all events other than move and resize, handle according to QObject built-in eventFilter().
return super().eventFilter(obj, event)

class TestSong(QWidget):

# Create signals to relay info about meatball
meatballCreated = Signal(QWidget, name="meatballCreated")
meatballDestroyed = Signal(QWidget, name="meatballDestroyed")

def __init__(self):
super().__init__()

self.setStyleSheet("background-color: blue")

# Init layout on widget
layout = QHBoxLayout(self)

# Crate widgets
label = QLabel("Label")
meatball = TestSongButton("Meatball button")

layout.addWidget(label)
layout.addWidget(meatball)

# connect clicked signal to relay into a meatballCreate signal, passing this widget
# as a param
meatball.clicked.connect(
lambda checked: self.meatballCreated.emit(self))

class TestSongButton(QPushButton):
def __init__(self, text):
super().__init__(text)

self.setStyleSheet("background-color: purple")

class TestSongMeatball(QWidget):
def __init__(self, songWidget: TestSong, parent=None):
super().__init__(parent)

# Set reference to song widget
self._songWidget = songWidget

self.setStyleSheet("background-color: red")

# Create sizePolicy obj, init with horizontal and vertical preferences
policy = QSizePolicy()
policy.setHorizontalPolicy(QSizePolicy.Policy.Preferred)
policy.setVerticalPolicy(QSizePolicy.Policy.Preferred)

# Set policy, min and max size of the widget.
self.setSizePolicy(policy)
self.setMinimumSize(QSize(70, 50))
self.setMaximumSize(QSize(160, 80))

fPolicy = Qt.FocusPolicy(Qt.StrongFocus)
self.setFocusPolicy(fPolicy)

# Assign layout to widget
layout = QVBoxLayout(self)

# Add demo label for clarity
text = QLabel("Meatball Menu")
layout.addWidget(text)

@property
def songWidget(self):
return self._songWidget

def findPos(self):
p = self.parent()
# First check that there is a song meatball created as a child of monitorObj (_widget)
if p.findChild(TestSongMeatball):
# find song item and store its position
item_pos = self.songWidget.pos()

# Type hint for Python intellisense purposes
# find button within song item and store its position
button: TestSongButton = self.songWidget.findChild(TestSongButton)
button_pos = button.pos()

# since item sits inside SubBox, the meatball will have the same geometry starting point (I think?)
# So to find the button we need to add them
x,y = item_pos.x(), item_pos.y()
nx,ny, = button_pos.x(), button_pos.y()

# Add co-ordinates to get button's top left corner
meatball_pos = QPoint(x+nx,y+ny)

# Move to new position, then allow QT to find correct size, using SizePolicy.
self.move(meatball_pos)
self.adjustSize()

return

def sizeHint(self):
return QSize(80,60)

class SubBox(QWidget):
def __init__(self):
super().__init__()

self.setStyleSheet("background-color: green")

# Create and apply layout
layout = QVBoxLayout(self)

@Slot()
def addWid(self, item: TestSong):
if self.findChild(TestSongMeatball):
print("Meatball already present.")
return

# Create meatball widget
meatball = TestSongMeatball(item, self)
evFilter = MeatballEventFilter(meatball, item.meatballDestroyed)

meatball.findPos()
meatball.show() # show meatball
meatball.setFocus()

return

@Slot()
def remWid(self, meatball: TestSongMeatball):
meatball.deleteLater()
meatball = None

return

class TestWindow(QMainWindow):
def __init__(self):
super().__init__()

window_widget = QWidget()
window_layout = QVBoxLayout(window_widget)

winFilter = WindowEventFilter(window_widget)

fPolicy = Qt.FocusPolicy(Qt.StrongFocus)
self.setFocusPolicy(fPolicy)

#TODO: put this into a function
# Give total layout a label
window_layout.addWidget(QLabel("Window"))

# Create sub-box
sub = SubBox()

# Install event filter to SubBox
subFilter = SubBoxEventFilter(sub)

test_widget = TestSong()

sub.layout().addWidget(test_widget)

window_layout.addWidget(sub)

test_widget.meatballCreated.connect(
lambda item: sub.addWid(item))

test_widget.meatballDestroyed.connect(
lambda meatball : sub.remWid(meatball))

# set window as central
self.setCentralWidget(window_widget)

def main():
# Create app
app = QApplication(sys.argv)
window = TestWindow()
window.show()
app.exec()

if __name__ == '__main__':
main()

Мне интересно, как лучше всего разделить этот код. До сих пор я пытался переместить классы CustomEventFilter, MeatballEventFilter, SubBoxEventFilter, WindowEventFilter в отдельный файл под названием EvFilter.py. Затем импортируйте SubBox, TestSongMeatball в EvFilter.py, чтобы QWidget.findChild() мог найти рассматриваемый виджет.
В результате у меня осталось два файла: EvFilter.py и mre.py, первый из которых содержит все подклассы CustomEventFilter и импортирует SubBox, TestSongMeatball к нему; а последний содержит TestSong, TestSongButton, TestSongMeatball, SubBox, TestWindow и main(), а затем импортирует все подклассы CustomEventFilter.
Я ясно вижу, как это приведет к циклическому импорту, и попытался исправить проблему с помощью исправлений, которые я нашел в Интернете.
Когда пытаюсь импортировать весь файл а затем получить доступ к отдельным классам, например:
import mre

sub = obj.findChild(mre.SubBox)

Функция findChild() вернет None.
Очевидно, что мне нужно реструктурировать программу, чтобы избежать этого недостатка, но не делайте этого. Я не знаю, как лучше всего это сделать. Или мне нужно найти дочерний элемент QWidget без использования findChild().
EvFilter.py
from PySide6.QtCore import (QEvent, QObject,
Signal, SignalInstance,
)

from PySide6.QtWidgets import (QWidget, )

from mre import (TestSongMeatball, SubBox, )

class CustomEventFilter(QObject):
"""Installs eventFilter obj on a widget.

Args:
widget: QWidget instance
"""
def __init__(self, widget: QWidget):
# if you don't pass widget to be a parent of this evFilter,
# then the event filter will most likely be destroyed.
super().__init__(widget)

# set protected variable
self._widget = widget

# Install this filterObj instance on the given widget
self.widget.installEventFilter(self)

# Encapsulation stuff
@property
def widget(self):
return self._widget

def eventFilter(self, obj: QWidget, event: QEvent):
match (event.type()):
case _:
# For all events other than move and resize, handle according to QObject built-in eventFilter().
return super().eventFilter(obj, event)

class MeatballEventFilter(CustomEventFilter):
def __init__(self, widget, sig: Signal = None):
super().__init__(widget)
self._sig = sig

@property
def widget(self) -> QWidget:
return self._widget

@property
def sig(self) -> SignalInstance:
return self._sig

def eventFilter(self, obj: QWidget, event: QEvent):
match (event.type()):
case QEvent.FocusOut:
# print("Lost focus!\nReason: {}".format(event.reason()))
# if event.reason() == Qt.FocusReason.ActiveWindowFocusReason:
# return True

# self.sig.emit(self.widget)
return True
case QEvent.FocusIn:
# print("Gained focus!")
return True
case _:
return super().eventFilter(obj, event)

class WindowEventFilter(CustomEventFilter):
def __init__(self, widget):
super().__init__(widget)

@property
def widget(self):
return super().widget

def eventFilter(self, obj: QWidget, event:QEvent):
match (event.type()):
case QEvent.MouseButtonRelease:
#TODO
print("mouse window!")
sub: SubBox = obj.findChild(SubBox)

evFilter: SubBoxEventFilter = sub.findChild(SubBoxEventFilter)
return evFilter.eventFilter(sub, event)

# return sub.eventFilter(sub, event) # this doesn't work.
case _:
return super().eventFilter(obj, event)

class SubBoxEventFilter(CustomEventFilter):
"""blah blah blah
"""
def __init__(self, widget: QWidget):
super().__init__(widget)

@property
def widget(self):
return super().widget

# Actual eventFilter function. Obj will be the widget this filterObj is installed on,
# Event is a subclass of QEvent and can be accessed through its enum:
# QEvent.Resize is similar to QResizeEvent
def eventFilter(self, obj: QObject, event:QEvent):
# print(type(obj))
match (event.type()):
case QEvent.Resize: # TODO
# Won't matter for final product(I think?) but for test purposes will be implemented here

return True

case QEvent.Move:
# Check meatball
print("Sub moving meatball!")
if obj.findChild(TestSongMeatball):
meatball: TestSongMeatball = obj.findChild(TestSongMeatball)
meatball.findPos()
return True

# Return True to notify app that moving took place.
return True

case _:
# For all events other than move and resize, handle according to QObject built-in eventFilter().
return super().eventFilter(obj, event)


mre.py, без фильтров событий
import sys

from PySide6.QtCore import (Qt, QSize, QPoint, QRect,
QEvent, QObject,
Signal, SignalInstance, Slot,
)

from PySide6.QtGui import (QResizeEvent, QMoveEvent,
QFocusEvent, )

from PySide6.QtWidgets import (QWidget, QPushButton, QLabel,
QHBoxLayout, QVBoxLayout,
QMainWindow, QApplication,
QSizePolicy, QLayout, QLayoutItem
)

import EvFilter

class TestSong(QWidget):

# Create signals to relay info about meatball
meatballCreated = Signal(QWidget, name="meatballCreated")
meatballDestroyed = Signal(QWidget, name="meatballDestroyed")

def __init__(self):
super().__init__()

self.setStyleSheet("background-color: blue")

# Init layout on widget
layout = QHBoxLayout(self)

# Crate widgets
label = QLabel("Label")
meatball = TestSongButton("Meatball button")

layout.addWidget(label)
layout.addWidget(meatball)

# connect clicked signal to relay into a meatballCreate signal, passing this widget
# as a param
meatball.clicked.connect(
lambda checked: self.meatballCreated.emit(self))

class TestSongButton(QPushButton):
def __init__(self, text):
super().__init__(text)

self.setStyleSheet("background-color: purple")

class TestSongMeatball(QWidget):
def __init__(self, songWidget: TestSong, parent=None):
super().__init__(parent)

# Set reference to song widget
self._songWidget = songWidget

self.setStyleSheet("background-color: red")

# Create sizePolicy obj, init with horizontal and vertical preferences
policy = QSizePolicy()
policy.setHorizontalPolicy(QSizePolicy.Policy.Preferred)
policy.setVerticalPolicy(QSizePolicy.Policy.Preferred)

# Set policy, min and max size of the widget.
self.setSizePolicy(policy)
self.setMinimumSize(QSize(70, 50))
self.setMaximumSize(QSize(160, 80))

fPolicy = Qt.FocusPolicy(Qt.StrongFocus)
self.setFocusPolicy(fPolicy)

# Assign layout to widget
layout = QVBoxLayout(self)

# Add demo label for clarity
text = QLabel("Meatball Menu")
layout.addWidget(text)

@property
def songWidget(self):
return self._songWidget

def findPos(self):
p = self.parent()
# First check that there is a song meatball created as a child of monitorObj (_widget)
if p.findChild(TestSongMeatball):
# find song item and store its position
item_pos = self.songWidget.pos()

# Type hint for Python intellisense purposes
# find button within song item and store its position
button: TestSongButton = self.songWidget.findChild(TestSongButton)
button_pos = button.pos()

# since item sits inside SubBox, the meatball will have the same geometry starting point (I think?)
# So to find the button we need to add them
x,y = item_pos.x(), item_pos.y()
nx,ny, = button_pos.x(), button_pos.y()

# Add co-ordinates to get button's top left corner
meatball_pos = QPoint(x+nx,y+ny)

# Move to new position, then allow QT to find correct size, using SizePolicy.
self.move(meatball_pos)
self.adjustSize()

return

def sizeHint(self):
return QSize(80,60)

class SubBox(QWidget):
def __init__(self):
super().__init__()

self.setStyleSheet("background-color: green")

# Create and apply layout
layout = QVBoxLayout(self)

@Slot()
def addWid(self, item: TestSong):
if self.findChild(TestSongMeatball):
print("Meatball already present.")
return

# Create meatball widget
meatball = TestSongMeatball(item, self)
evFilter = EvFilter.MeatballEventFilter(meatball, item.meatballDestroyed)

meatball.findPos()
meatball.show() # show meatball
meatball.setFocus()

return

@Slot()
def remWid(self, meatball: TestSongMeatball):
meatball.deleteLater()
meatball = None

return

class TestWindow(QMainWindow):
def __init__(self):
super().__init__()

window_widget = QWidget()
window_layout = QVBoxLayout(window_widget)

winFilter = EvFilter.WindowEventFilter(window_widget)

fPolicy = Qt.FocusPolicy(Qt.StrongFocus)
self.setFocusPolicy(fPolicy)

#TODO: put this into a function
# Give total layout a label
window_layout.addWidget(QLabel("Window"))

# Create sub-box
sub = SubBox()

# Install event filter to SubBox
subFilter = EvFilter.SubBoxEventFilter(sub)

test_widget = TestSong()

sub.layout().addWidget(test_widget)

window_layout.addWidget(sub)

test_widget.meatballCreated.connect(
lambda item: sub.addWid(item))

test_widget.meatballDestroyed.connect(
lambda meatball : sub.remWid(meatball))

# set window as central
self.setCentralWidget(window_widget)

def main():
# Create app
app = QApplication(sys.argv)
window = TestWindow()
window.show()
app.exec()

if __name__ == '__main__':
main()


Подробнее здесь: https://stackoverflow.com/questions/792 ... th-pyside6
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»