Жалоба 15.10.2026: на «Тексте профиля» стрелка «назад» ничего не делала, а Alt+← работал. Причина: в сборке Nuitka отписка по методу (signal.disconnect(obj.method)) не находит подписку — метод там при каждом обращении новый объект compiled_method; ошибка глоталась. У стрелки оставались два обработчика: библиотечный qrouter.pop переключал страницу, журнал тут же возвращал её обратно. Из исходников всё работало, поэтому стенд не ловил. Проверено на win10 модулем, собранным Nuitka: отписка по методу — «is not connected», 2 обработчика; по квитанции — 1. - utils/qt_signal_links: подписка снимается по объекту соединения, который вернул connect() (disconnect_link, SignalLinks). - Стрелка «назад»: отвязываются все прежние обработчики, библиотечный список страниц выключен целиком; если обработчиков не один — ошибка в журнале. - Те же молчаливые отписки в сборке исправлены везде: всплывающие меню и выпадающие списки (обработчики копились с каждым меню), страницы оркестратора (каждая настройка добавляла по обработчику), Telegram Proxy, подписка Premium, проверка текста профиля, списки и анимация строк, подсказка стратегий. Карточки больше не получают вторую подписку на смену темы: библиотека её в конструкторе не создаёт. - architecture_checks запрещает отписку по обработчику. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
64 lines
2.2 KiB
Python
64 lines
2.2 KiB
Python
"""Подписки снимаются по квитанции connect(), а не по обработчику.
|
||
|
||
В сборке Nuitka метод объекта при каждом обращении — новый объект, PyQt его
|
||
не узнаёт, и ``signal.disconnect(self.on_x)`` молча оставлял обработчик.
|
||
Здесь это изображает обработчик, который каждый раз новый: квитанция
|
||
снимает подписку и тогда.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import unittest
|
||
|
||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||
|
||
from PyQt6.QtCore import QObject, pyqtSignal
|
||
|
||
from utils.qt_signal_links import SignalLinks, disconnect_link
|
||
|
||
|
||
class _Source(QObject):
|
||
fired = pyqtSignal()
|
||
|
||
|
||
class QtSignalLinksTests(unittest.TestCase):
|
||
def test_link_removes_handler_that_cannot_be_recognized_again(self) -> None:
|
||
source = _Source()
|
||
calls = []
|
||
link = source.fired.connect(lambda: calls.append(1))
|
||
# Обработчик «по имени» снять нечем: тот же код — уже другой объект.
|
||
with self.assertRaises(TypeError):
|
||
source.fired.disconnect(lambda: calls.append(1))
|
||
|
||
disconnect_link(link)
|
||
source.fired.emit()
|
||
|
||
self.assertEqual(calls, [])
|
||
self.assertEqual(source.receivers(source.fired), 0)
|
||
|
||
def test_empty_or_repeated_link_is_not_an_error(self) -> None:
|
||
source = _Source()
|
||
link = source.fired.connect(lambda: None)
|
||
disconnect_link(link)
|
||
disconnect_link(link)
|
||
disconnect_link(None)
|
||
self.assertEqual(source.receivers(source.fired), 0)
|
||
|
||
def test_group_removes_only_its_own_links(self) -> None:
|
||
source = _Source()
|
||
calls = []
|
||
links = SignalLinks()
|
||
links.connect(source.fired, lambda: calls.append("mine"))
|
||
links.connect(source.fired, lambda: calls.append("mine too"))
|
||
source.fired.connect(lambda: calls.append("someone else"))
|
||
|
||
links.disconnect_all()
|
||
source.fired.emit()
|
||
|
||
self.assertEqual(calls, ["someone else"])
|
||
self.assertEqual(len(links), 0)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|