5.4 사용자 정의 시그널 구현

1. Signal, Slot, Connect 라이브러리
from PySide6.QtCore import Signal, Slot, QObject, QUrl
- Signal은 PySide6.QtCore 에서 가져옵니다.
- PySide6는 Signal, Slot (PyQt6는 pyqtSignal, pyqtSlot)
2. Signal 선언 문법
A. Signal 선언 문법
mysignal = Signal(자료형...)
Signal은 클래스 변수(class attribute) 로 선언해야 합니다.
(※ 인스턴스 변수로 선언하면 동작하지 않습니다.)
B. Signal 선언 문법 예시
mysignal = Signal(int)
B.2 문자열 하나 전달
mysignal = Signal(str)
B.3 QUrl 전달
mysignal = Signal(QUrl)
B.3 여러 인자 전달 (int, str, int)
mysignal = Signal(int, str, int)
B.4 인자 없는 Signal
mysignal = Signal()
C. Signal 사용 예시
C.1 Signal (클래스 변수) 생성
class MyObjectA(QObject):
sig = Signal(int)
C.2 Signal 사용 방법
a = MyObjectA()
a.sig.connect(...)
a.sig.emit(10) # 정상 작동
- 시그널 살펴보기
A. 잘못된 Signal 사용 예시
A.1 Signal (클래스 변수) 생성
class MyObjectB(QObject):
def __init__(self):
self.sig = Signal(int)
A.2 Signal 사용 방법
a = MyObjectB()
a.sig.connect(...) # 에러 또는 동작 안함
a.sig.emit(10) # Qt 메타시스템에서 인식되지 않음
B. 잘못된 Signal 살펴보기
B.1 정상
class MyObjectA(QObject):
mysignal = Signal(int)
이 코드가 실행되면:
- Python이 MyObjectA 클래스를 정의하고
- PySide6가 클래스 내부의 Signal들을 스캔하여
- Qt 메타오브젝트 시스템에
→ “이 클래스는 mysignal(int) 시그널을 가진다” 라고 등록한다. - 이 등록 과정이 끝나야 emit() 과 connect() 가 정상적으로 동작함
→ 즉, Signal은 클래스 정의 시점(class body) 에 반드시 존재해야 한다.
B.2 비정상
class MyObjectB(QObject):
def __init__(self):
super().__init__()
self.mysignal = Signal(int) # Qt가 인식하지 못함
이렇게 하면:
- 클래스 정의 시점에는 Signal이 없음
- Signal(int) 는 Qt 에 등록되지 않은 일반 파이썬 변수일 뿐
- Qt 이벤트 시스템이 이 신호를 모름
- .connect() 에러 발생 가능
- .emit() 해도 Qt가 받지 못함
- 진짜 Signal로 동작하지 않음
- 이유: Qt는 클래스 단계에서만 Signal을 스캔하고 등록하기 때문.
B.3 상세
- 파이썬 OOP 관점에서, Signal은 “클래스 기능”이다. Signal은 객체마다 다른 데이터가 아니라
- 클래스 자체가 가진 기능(Functionality)
- 인스턴스마다 동일한 Signal 구조
- 예시
- 사람(Person)의 나이(age)는 객체마다 다르므로 >> 인스턴스 변수를 만들어 처리합니다.
- 하지만 “버튼이 클릭되면 신호를 발생시킨다” 같은 기능은
모든 버튼(Button) 객체가 공통으로 가지는 기능 >> 클래스 레벨에서 처리합니다.
- 따라서 Signal은 타입(type) 정보에 속하며 >> 인스턴스 변수로 만들 이유가 없다.
3. Slot 선언 문법
A. Slot은 일반 함수도 Slot이 될 수 있습니다.
def slot_function(self, value):
print(value)
B. @Slot() 데코레이터
: 슬롯을 더 명확하게 하려면 @Slot() 데코레이터를 사용합니다.
B.1 Slot 선언 기본
@Slot(int)
def slot_function(self, value):
print("받은 값:", value)
B.2 여러 인자를 사용하는 Slot 선언
@Slot(int, str, int)
def other_slot(self, a, b, c):
print(a, b, c)
B.3 반환 타입 명시도 가능
@Slot(int, result=str)
def convert_to_str(self, value):
return str(value)
- slot @데코레이터 살펴보기
A. @Slot 데코레이터는 필수가 아닙니다.
: 선택사항(Optional)으로, 아래 두 코드는 완전히 동일하게 동작합니다:
데코레이터 사용:
@Slot(int)
def on_slider_changed(self, value):
...
데코레이터 없음:
def on_slider_changed(self, value):
...
B. @Slot 을 사용하는 이유
- 성능 최적화 (미세하지만 존재)
Slot이 C++ Qt 구조에 더 효율적으로 연결됩니다. - 자료형 명시로 인자 오류 방지
Signal이 int를 보내는데 Slot이 str 받으면 오류 가능→ @Slot(int) 로 명확하게 문서화 및 검증 - 대규모 프로젝트에서 유지보수 용이
IDE 자동완성 도움실수 감소코드 명확성 증가 - Qt C++ 시그널/슬롯 구조와 더 동일한 형태
C. @Slot을 사용하지 않아도 되는 경우
- 작은 프로젝트
- 복잡한 시그널/파라미터 구조 없음
- 단순 버튼/슬라이더 이벤트 처리
D. 실전에서 가장 효과적으로 사용하는 방식
- 중소규모 프로젝트
@Slot 생략하고, 필요 시 @Slot 사용(자유롭고 유지보수 쉬움) - 대규모 구조화된 프로젝트 / 팀 개발 / Qt Designer 연동 프로젝트
→ Signal/Slot 타입 명시하는 것이 안정성↑
→ @Slot(int) 적극 활용 - 사용자 정의 Signal이 많을 경우→ Signal(int, str ...) → @Slot(int, str) 맞춰 쓰기(버그 예방)
5. Connect 선언 문법
: Signal → Slot 연결
mysignal = Signal(int)
A.2 Slot 선언 기본
@Slot(int)
def slot_function(self, value):
print("받은 값:", value)
self.mysignal.connect(self.slot_function)
self.mysignal.emit(123)
B.2 Slot 실행됨:
받은 값: 123
6. Signal / Slot 활용 예제
: QSlider 예제 확장 (1)
목표

파이참에서 프로젝트 생성하기
1. 일반 프로젝트 생성하기더보기 2. 새 프로젝트 시작하기더보기Case A. 새 프로젝트 생성 Case B. 새 프로젝트 생성 Case C. 새 프로젝트 생성 3. 커스텀 가상환경으로 프로젝트 시작하기 더보기
basiclike.tistory.com
Signal & Slot 구조 이해하기 (3)
1. 학습 목표 (Learning Objectives):슬라이더(QSlider) 위젯을 사용해 Signal/Slot 구조를 이해더보기슬라이더(QSlider)의 valueChanged(int) Signal 구조 이해Slider가 Signal을 발생시키며 “값(data)”을 Slot 함수에 자
basiclike.tistory.com
C.


import sys
from PySide6.QtCore import Qt, Signal, Slot
from PySide6.QtWidgets import QApplication, QWidget, QSlider, QVBoxLayout

# [1] 예제 위젯 생성
class SliderExample(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Signal & Slot 확장 예제")
self.resize(300, 150)
# [2] 슬라이더 생성
self.slider = QSlider(Qt.Horizontal)
self.slider.setMinimum(1)
self.slider.setMaximum(100)
self.slider.setValue(25)

# [3] 슬롯 함수 생성
@Slot(int)
def respond_to_slider(self, value):
print("slider moved to:", value)

# [4] Signal → Slot 연결
self.slider.valueChanged.connect(self.respond_to_slider)

# [5] 레이아웃 추가 (이 부분이 없으면 화면에 안 보임)
layout = QVBoxLayout(self)
layout.addWidget(self.slider)

# [6] 실행 영역
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SliderExample()
window.show()
sys.exit(app.exec())

import sys
from PySide6.QtCore import Qt, Signal, Slot
from PySide6.QtWidgets import QApplication, QWidget, QSlider, QVBoxLayout
# [1] 예제 위젯 생성
class SliderExample(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Signal & Slot 확장 예제")
self.resize(300, 150)
# [2] 슬라이더 생성
self.slider = QSlider(Qt.Horizontal)
self.slider.setMinimum(1)
self.slider.setMaximum(100)
self.slider.setValue(25)
# [4] Signal → Slot 연결
self.slider.valueChanged.connect(self.respond_to_slider)
# [5] 레이아웃 추가 (이 부분이 없으면 화면에 안 보임)
layout = QVBoxLayout(self)
layout.addWidget(self.slider)
# [3] 슬롯 함수 생성
@Slot(int)
def respond_to_slider(self, value):
print("slider moved to:", value)
# [6] 실행 영역
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SliderExample()
window.show()
sys.exit(app.exec())
- Qt 에서 구현 과제입니다.
7. Signal / Slot 활용 예제
: QSlider 예제 확장 (2)
목표


# [1] 사용자 정의 Signal: 처리된 값을 전달하고 싶을 때 사용
processedValueSignal = Signal(int)

from PySide6.QtWidgets import QApplication, QWidget, QSlider, QVBoxLayout, QLabel
# [2] UI 구성
self.label_original = QLabel("원본 값: -")
self.label_processed = QLabel("처리된 값(×2): -")

# [3] 레이아웃 추가
layout.addWidget(self.label_original)
layout.addWidget(self.label_processed)

# [4] 레이블에 출력 슬롯 로직
self.label_original.setText(f"원본 값: {value}")
# [5] 사용자 정의 *Signal - 슬롯 내부에 시그널 역할 추가
# value × 2 를 Custom Signal 로 emit
processed_value = value * 2
self.processedValueSignal.emit(processed_value)

# [6] 사용자 정의 *Signal을 받는 Slot 구현
@Slot(int)
def on_processed_value(self, value):
print("커스텀 시그널:", value)
self.label_processed.setText(f"처리된 값(×2): {value}")

# [7] 사용자 정의 Signal → 사용자 정의 Slot 연결
self.processedValueSignal.connect(self.on_processed_value)

import sys
from PySide6.QtCore import Qt, Signal, Slot
from PySide6.QtWidgets import QApplication, QWidget, QSlider, QVBoxLayout, QLabel
class SliderExample(QWidget):
# [1] 사용자 정의 Signal: 처리된 값을 전달하고 싶을 때 사용
processedValueSignal = Signal(int)
def __init__(self):
super().__init__()
self.setWindowTitle("Signal & Slot 확장 예제")
self.resize(300, 150)
# [2] UI 구성
self.label_original = QLabel("원본 값: -")
self.label_processed = QLabel("처리된 값(×2): -")
self.slider = QSlider(Qt.Horizontal)
self.slider.setMinimum(1)
self.slider.setMaximum(100)
self.slider.setValue(25)
self.slider.valueChanged.connect(self.respond_to_slider)
# [7] 사용자 정의 Signal → 사용자 정의 Slot 연결
self.processedValueSignal.connect(self.on_processed_value)
layout = QVBoxLayout(self)
layout.addWidget(self.slider)
# [3] 레이아웃 추가
layout.addWidget(self.label_original)
layout.addWidget(self.label_processed)
@Slot(int)
def respond_to_slider(self, value):
print("slider moved to:", value)
# [4] 레이블에 출력 슬롯 로직
self.label_original.setText(f"원본 값: {value}")
# [5] 사용자 정의 *Signal - 슬롯 내부에 시그널 역할 추가
# value × 2 를 Custom Signal 로 emit
processed_value = value * 2
self.processedValueSignal.emit(processed_value)
# [6] 사용자 정의 *Signal을 받는 Slot 구현
@Slot(int)
def on_processed_value(self, value):
print("커스텀 시그널:", value)
self.label_processed.setText(f"처리된 값(×2): {value}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SliderExample()
window.show()
sys.exit(app.exec())
- Qt 에서 구현 과제입니다.