【改訂版】【PyQt6】各QWidgetの設定をYAMLファイルに記述する(辞書型変数に対してパターンマッチを使用すると簡潔に書けました)

はじめに

以前同じことをやりました。
touch-sp.hatenablog.com
今回、Python 3.10から導入されたパターンマッチ(「match」「case」)を使ってスクリプトを書き換えました。

結果&考察

「PyYAML」でYAMLファイルを読み込むとデータが辞書型変数に格納されます。

そして「match」「case」は辞書型変数に対しても使えます。

つまり何が伝えたかったかというと、「PyYAML」とパターンマッチ(「match」「case」)の相性が良過ぎて幸せな気持ちになれるということです。

幸せになる使い方

match settings_dict:
    case {'height': height, 'width': width}:
        self.setFixedSize(QSize(width, height))

解説すると、


「settings_dict」という辞書型変数に「'height'」というキーは存在しますか?
存在するならその値を「height」という変数に格納して下さい。


同様に

「settings_dict」という辞書型変数に「'width'」というキーは存在しますか?
存在するならその値を「width」という変数に格納して下さい。

そして

「height」と「width」がともに値をもつ場合に限り「setFixedSize」を実行して下さい。

このようになります。
どうですか、非常に簡潔にかけて幸せになりませんか?

ちなみに従来通りに書くとこのようになると思います。

height = settings_dict.get('height')
width = settings_dict.get('width')
if height is not None and width is not None:
    self.setFixedSize(QSize(width, height))

4行が3行になっただけですが私は非常に感動しました。みなさんはどうでしょう?

もう一つ例を示します。

従来通りに書く場合

orientation = settings_dict.get('orientation')
if orientation == 'h':
    self.setOrientation(Qt.Orientation.Horizontal)
elif orientation == 'v':
    self.setOrientation(Qt.Orientation.Vertical)

パターンマッチを使った場合

match settings_dict.get('orientation'):
    case 'h':
        self.setOrientation(Qt.Orientation.Horizontal)
    case 'v':
        self.setOrientation(Qt.Orientation.Vertical)

余計な変数を作らなくて済みます。

Pythonスクリプト

from PyQt6.QtCore import Qt, QSize
from PyQt6.QtWidgets import QLabel, QPushButton, QSlider, QFrame
from PyQt6.QtGui import QFont

import yaml

class Label(QLabel):
    def __init__(self, fname = "", settings = ""):
        super().__init__()

        if not fname == "":
            with open(fname, 'r') as f:
                yaml_data = yaml.load(f, Loader=yaml.SafeLoader)
            
            settings_dict = yaml_data.get(settings)

            if settings_dict is not None:

                match settings_dict:
                    case {'height': height, 'width': width}:
                        self.setFixedSize(QSize(width, height))
                
                font = QFont()
                match settings_dict:
                    case {'fontFamily': fontFamily}:
                        font.setFamily(fontFamily)
                match settings_dict:
                    case {'fontPoint': fontPoint}:
                        font.setPointSize(fontPoint)
                match settings_dict:
                    case {'fontBold': fontBold}:
                        font.setBold(fontBold)
                self.setFont(font)

                match settings_dict:
                    case {'text': text}:
                        self.setText(text)            

                match settings_dict.get('alignment'):
                    case 'center':
                        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
                    case 'right':
                        self.setAlignment(Qt.AlignmentFlag.AlignRight)
                    case 'left':
                        self.setAlignment(Qt.AlignmentFlag.AlignLeft)

                match settings_dict:
                    case {'linewidth': linewidth}:
                        self.setLineWidth(linewidth)

                match settings_dict:
                    case {'shape': 'box', 'shadow': 'plain'}:
                        self.setFrameStyle(QFrame.Shape.Box.value | QFrame.Shadow.Plain.value)
                    case {'shape': 'box', 'shadow': 'raised'}:
                        self.setFrameStyle(QFrame.Shape.Box.value | QFrame.Shadow.Raised.value)
                    case {'shape': 'box', 'shadow': 'sunken'}:
                        self.setFrameStyle(QFrame.Shape.Box.value | QFrame.Shadow.Sunken.value)
                    case {'shape': 'panel', 'shadow': 'plain'}:
                        self.setFrameStyle(QFrame.Shape.Panel.value | QFrame.Shadow.Plain.value)
                    case {'shape': 'panel', 'shadow': 'raised'}:
                        self.setFrameStyle(QFrame.Shape.Panel.value | QFrame.Shadow.Raised.value)
                    case {'shape': 'panel', 'shadow': 'sunken'}:
                        self.setFrameStyle(QFrame.Shape.Panel.value | QFrame.Shadow.Sunken.value)

                color_list = []
                match settings_dict:
                    case {'color': color}:
                        color_list.append('color: %s'%color)
                match settings_dict:
                    case {'background-color': background_color}:
                        color_list.append('background-color: %s'%background_color)
                if len(color_list) > 0:
                    self.setStyleSheet(';'.join(color_list))

class PushButton(QPushButton):
    def __init__(self, fname = "", settings = ""):
        super().__init__()

        if not fname == "":
            with open(fname, 'r') as f:
                yaml_data = yaml.load(f, Loader=yaml.SafeLoader)
            
            settings_dict = yaml_data.get(settings)

            if settings_dict is not None:

                match settings_dict:
                    case {'height': height, 'width': width}:
                        self.setFixedSize(QSize(width, height))
                
                font = QFont()
                match settings_dict:
                    case {'fontFamily': fontFamily}:
                        font.setFamily(fontFamily)
                match settings_dict:
                    case {'fontPoint': fontPoint}:
                        font.setPointSize(fontPoint)
                match settings_dict:
                    case {'fontBold': fontBold}:
                        font.setBold(fontBold)
                self.setFont(font)

                match settings_dict:
                    case {'text': text}:
                        self.setText(text)            

class Slider(QSlider):
    def __init__(self, fname = "", settings = ""):
        super().__init__()
        
        if not fname == "":
            with open(fname, 'r') as f:
                yaml_data = yaml.load(f, Loader=yaml.SafeLoader)

            settings_dict = yaml_data.get(settings)

            if settings_dict is not None:

                match settings_dict:
                    case {'height': height, 'width': width}:
                        self.setFixedSize(QSize(width, height))
        
                match settings_dict.get('orientation'):
                    case 'h':
                        self.setOrientation(Qt.Orientation.Horizontal)
                    case 'v':
                        self.setOrientation(Qt.Orientation.Vertical)

                match settings_dict:
                    case {'max': max}:                    
                        self.setMaximum(max)

                match settings_dict:
                    case {'min': min}:                    
                        self.setMinimum(min)
                
                match settings_dict:
                    case {'default': default}:
                        self.setValue(default)

                match settings_dict.get('tickposition'): 
                    case 'both':
                        self.setTickPosition(QSlider.TickPosition.TicksBothSides)
                    case 'above':
                        self.setTickPosition(QSlider.TickPosition.TicksAbove)
                    case 'below':
                        self.setTickPosition(QSlider.TickPosition.TicksBelow)
                    case 'left':
                        self.setTickPosition(QSlider.TickPosition.TicksLeft)
                    case 'right':
                        self.setTickPosition(QSlider.TickPosition.TicksRight)

使用例

上のPythonファイルを「constructGUI2.py」、設定を書き込んだYAMLファイルを「settings.yml」という名前で保存した場合の例を示します。

Pythonファイル

from PyQt6.QtWidgets import QWidget, QApplication, QVBoxLayout

from constructGUI2 import Label

class Window(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()
        
    def initUI(self):
        
        self.label_1 = Label('settings.yml', 'label_1')
        self.label_2 = Label('settings.yml', 'label_2')

        layout = QVBoxLayout()
        layout.addWidget(self.label_1)
        layout.addWidget(self.label_2)

        self.setLayout(layout)

if __name__ == "__main__":
    app = QApplication([])
    ex =Window()
    ex.show()
    app.exec()

YAMLファイル

label_1:
  width: 300
  height: 100
  alignment: center
  fontFamily: times
  fontPoint: 40
  fontBold: True
  linewidth: 10
  shape: panel #(box or panel)
  shadow: raised #(plain or raised or sunken)
  text: start

label_2:
  width: 300
  height: 100
  alignment: center
  fontFamily: times
  fontPoint: 40
  fontBold: True
  linewidth: 10
  shape: panel #(box or panel)
  shadow: sunken #(plain or raised or sunken)
  text: end

このようなものができます。