본문 바로가기

Dev/etc

[Python] PyQt와 PySide에 대한 잡설

 

 

PyQt & PySide

*본 포스팅에서는 PyQT나 PySide의 사용 방법에 대한 설명을 포함하지 않습니다.

 

PyQT와 PySide

필자는 본 블로그에 따로 포스팅하지는 않지만 종종 취미 겸, 개인 사용 목적으로 윈도 애플리케이션을 만듭니다.

이를테면 사진 수집용 매크로 프로그램이라던가 폴더관리 프로그램 등 그때그때 필요한 것들을 이 녀석을 이용해 만들고 있습니다.

즉 PyQT는 파이썬 기반의 데스크톱 GUI를 개발을 위한 프레임워크입니다.

 

본래 PyQT와 PySide는 C++ 기반의 Qt 프레임워크에서 바인딩된 것들입니다.

이 둘은 코드의 사용방법이 99% 이상 거의 동일합니다.

그렇다면 왜 두 가지의 이름으로 프레임워크가 공존하고 있는 것일까요?

이 두 프레임워크의 차이점은 바로 개발사와 라이선스에 있습니다.

 

PyQT와 PySide의 라이선스

PyQT는 RiveBank Computing이라는 곳에서, PySide는 The Qt Company에서 개발되었습니다.

The Qt Compony라는 사명에서 알 수 있듯이 사실상 Qt를 계승한 근본은 PySide에 있습니다.

하지만 먼저 릴리즈 된 것은 PyQT였고 PySide는 나중에 릴리즈 되었습니다.

 

PyQT의 라이선스는 기본적으로 GPL인데, GPL은 오픈소스 프로그램을 위한 라이선스입니다.

상업적으로 배포된 프로그램의 모든 소스 코드를 변경할 수 있고, 개발한 소스 코드를 공개할 의무가 있습니다.

만약 이 GPL의 범위를 벗어난 사용을 원할 경우 별도의 라이선스를 구매해야 합니다.

(대표적인 GPL 라이선스로는 리눅스 커널 소스코드가 있습니다.)

 

PySide가 개발되기 전 당시 Qt의 저작권을 갖고 있던 노키아가 RiverBank와 접촉해 GPL 라이선스인 PyQT를 LGPL로 변경해 줄 것을 요구했지만 이를 받아들이지 않아 개발된 것이 바로 PySide입니다.

 

즉 PySide는 기본적으로 LGPL 라이선스가 보장됩니다.

LGPL은 개발한 프로그램을 상업적으로 이용할 때는 GPL과 마찬가지로 라이브러리의 소스 코드를 공개해야 할 의무는 발생하지만, LGPL의 라이브러리를 링킹(linking)해 함께 동작하는 소스 코드는 따로 공개하지 않아도 됩니다.

(*링킹이란 라이브러리를 상업용 프로그램을 빌드 할 때 패키지에 포함하는 것을 말합니다.)

 

하지만 LGPL로 배포하는 라이브러리를 직접 수정 및 이를 기반한 새로운 라이브러리를 개발하는 경우에는 해당하는 소스 코드를 LGPL로 공개해야 한다는 차이가 있습니다.

 

따라서 상업적인 목적으로 개발할 경우 PySide를 이용하는 것이 조금 더 유리하겠습니다.

 

 

PyQT와 PySide의 개발 코드에서의 차이점

개인적으로 PyQT는 굉장히 쉬웠고 리눅스, 윈도, 맥 심지어 안드로이드용으로도 빌드가 가능한 크로스 플랫폼 프레임워크인 점이 굉장히 큰 매력으로 다가왔습니다.

필자는 PySide를 비교적 뒤늦게 접했는데, 최근에 PyQt6와 PySide6가 릴리즈 되어 한번 PySide6를 이용해봤습니다.

 

간단히 사용해 봤을 때 느낀 점은, 거의 대부분의 코드에서는 차이점이 없었으나, 멀티 스레드 부분에서 사용방법이 약간 상이했습니다.

 

멀티스레드란, 프로세스를 병렬로 처리하는 방식을 말하는데, 우리가 흔히 접하는 멀티스레드는 바로 로딩입니다.

많은 프로그램들이 어떠한 로딩 상태에 들어갔을 때도 대부분 조작이 가능했을 것입니다.

(예를 들자면 브라우저에서 어떠한 파일을 다운로드할 때에도 브라우저의 다양한 기능을 제어할 수 있는 것을 들 수 있습니다.)

 

먼저 PyQt에서의 멀티스레드 구현부를 보겠습니다.

 

# PyQT에서의 스레드 구현
import sys
from PyQt6.QtWidgets import *
from PyQt6.QtCore import QThread, pyqtSlot, pyqtSignal
import time

class Worker(QThread):
	# 스레드 함수 신호 수신
	proc = pyqtSignal(int)
    def __init__(self):
        super().__init__()
    
    #실제 스레드 실행부
    def run(self):
        count = 0
        for i in range(0, 100):
            count += 1
            self.proc.emit(count) # 변경 스레드 함수 호출 신호를 준다.
            time.sleep(1)

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

        self.worker = Worker()
        self.worker.start()        
        # Worker 클래스의 proc 함수와 자기자신의 proc함수를 연결한다.
        self.worker.proc.connect(self.proc)
	
    # 스레드에서 송신하는 스레드를 받는다.
    @pyqtSlot(int)
    def proc(self, count):
        print(count)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = WindowClass()
    window.show()
    sys.exit(app.exec())

 

위 코드는 PyQT에서 창을 실행할 때 스레드를 생성하고 스레드에서 1초에 걸쳐 100번씩 카운트를 증가시켜 증가할 때마다 증가된 값을 WindowClass로 보내주는 간단한 스레드 구현 코드입니다.

 

먼저 코드에서 보면 알 수 있듯, 스레드를 담당 실행하는 Worker클래스를 정의하고 WindowClass에서는 이 Worker 클래스를 호출해 초기화하고 start() 함수를 수행하고 있습니다.

start() 함수가 스레드의 시작을 지시하는 함수입니다.

 

스레드에서 그때그때 변경되는 값을 송신할 pyqtSignal 함수를 선언하고 송신할 값의 타입이 int 형이므로 pyqtSitnal(int)로 정수형 데이터를 송신할 것이다라고 선언합니다.

그리고 run 함수 내 emit 함수로 변경 신호를 송신합니다.

 

신호를  송신했으면 수신부도 작성해야 하는데, 그게 WindowClass의 proc함수입니다.

송신하는 함수가  값을 int형으로 보낼 것이므로 @pyqtSlot(int) 어노테이션으로 받아야 할 값을 정의해줍니다.

 

수신자와 송신자의 관계를 생각하면 이해가 쉬울 듯합니다.

 

아래는 위 코드와 동일한 기능을 하는 PySide 코드입니다.

 

# PySide에서의 스레드 구현
import sys
from PySide6.QtWidgets import *
from PySide6.QtCore import QThread, SIGNAL
import time

class Worker(QThread):
    def __init__(self, proc):
        super().__init__()
        self.proc = proc

    def run(self):
        count = 0
        for i in range(0, 100):
            count += 1
            # windoww 클래스에서 넘겨받은 함수를 수신부로 지정하고 값을 송신한다.
            self.emit(SIGNAL(self.proc(count)))
            time.sleep(1)

class WindowClass(QMainWindow):
    def __init__(self):
        super().__init__()
        # 스레드 클래수에 수신 함수를 넘겨준다.
        self.worker = Worker(self.proc)
        self.worker.start()

    def proc(self, count):
        print(count)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    myWindow = WindowClass()

    myWindow.show()
    sys.exit(app.exec())

 

차이점이 보이시나요?

PyQT에서와는 다르게 애초에 스레드로 인해 발생할 신호를 받을 함수를 아예 Worker 클래스로 넘겨줍니다.

그리고 emit과 SIGNAL 함수를 통해 넘겨받은 함수 proc에 신호 송수신을 발생시키고 있습니다.

 

즉, PyQT와 PySide의 스레드 구현의 차이점은 PyQT에서는 스레드 수신자를 윈도 클래스에서 정하고 PySide에서는 스레드 수신자를 스레드 클래스에서 정한다 정도가 되겠습니다.

개인적으로 Pycharm에서는 어노테이션(@) 구문은 기본적으로 노란색으로 강조를 해주므로 코드가 많아지면 구분하기에는 PyQT의 스레드 쪽이 좋아 보입니다만, 코드 구현의 편의성은 PySide 쪽이 좀 더 괜찮다고 느꼈습니다.

네이밍만 잘 신경 쓴다면 (thread_a, thread_b 등으로) PySide 쪽이 생산성이 약간은 우세하지 않을까 생각해봅니다.

(우열을 가리는 게 의미는 없지만)

 

이를 응용해서 pyqt와 pyside로 구현한 간단한 cpu, 램 모니터링 프로그램을 만들어봤습니다.

두 프레임워크를 비교해보는 것도 나름 유쾌한 시간이 되지 않을까 생각합니다.

링크 공유를 끝으로 이번 포스팅을 마치겠습니다.

 

PyQT

https://github.com/overload-dev/pc-task-monitor-qt5

 

overload-dev/pc-task-monitor-qt5

Contribute to overload-dev/pc-task-monitor-qt5 development by creating an account on GitHub.

github.com

 

PySide

https://github.com/overload-dev/pc-task-monitor-pyside6

 

overload-dev/pc-task-monitor-pyside6

Contribute to overload-dev/pc-task-monitor-pyside6 development by creating an account on GitHub.

github.com