Pythonでの設定のためのデータクラスの使用


導入


Pythonアプリケーションの設定スキームの定義はトリッキーです.この記事では、標準的なライブラリだけで構成を簡単に処理するための、シンプルで効果的な方法を紹介します.

フォーマット


このメソッドでは、任意のファイル形式は、Pythonに解析できる限り、動作しますdict .

簡単な例


この設定を簡単に設定しますwikipedia :
---
receipt:     Oz-Ware Purchase Invoice
date:        2012-08-06
customer:
    first_name:   Dorothy
    family_name:  Gale

items:
    - part_no:   A4786
      descrip:   Water Bucket (Filled)
      price:     1.47
      quantity:  4

    - part_no:   E1628
      descrip:   High Heeled "Ruby" Slippers
      size:      8
      price:     133.7
      quantity:  1

bill-to:  &id001
    street: |
            123 Tornado Alley
            Suite 16
    city:   East Centerville
    state:  KS

ship-to:  *id001

specialDelivery:  >
    Follow the Yellow Brick
    Road to the Emerald City.
    Pay no attention to the
    man behind the curtain.

これをPythonに解析してdictを得ることができます.
config = {
    "reciept": "Oz-Ware Purchase Invoice",
    "date": "2012-08-06",
    "customer": {
        "first_name": "Dorothy",
        "family_name": "Gale",
    },
    "items": [
        {
            "part_no": "A4786",
            "descrip": "Water Bucket (Filled)",
            "price": 1.47,
            "quantity": 4,
        },
        {
            "part_no": "E1628",
            "descrip": "High Heeled \"Ruby\" Slippers",
            "size": 8,
            "price": 133.7,
            "quantity": 1,
        },
    ],
    "bill-to": {
        "street" : "123 Tornado Alley\nSuite 16",
        "city": "East Centerville",
        "state": "KS",
    },
    "ship-to": {
        "street" : "123 Tornado Alley\nSuite 16",
        "city": "East Centerville",
        "state": "KS",
    },
    "specialDelivery": "Follow the Yellow Brick Road to the Emerald City. Pay no attention to the man behind the curtain.",
}
ただし、コードでこれを使用すると面倒です.config["customer"]["first_name"] エラーが起こりやすく、リファクタリングが難しい.

データクラス


DataClassは私たちの生活をはるかに簡単になります.設定プロパティ、サブプロパティ、およびconfig.py ファイル
import typing as t
from dataclasses import dataclass
from datetime import date

@dataclass
class Item:
    part_no: str
    description: str
    price: float
    quantity: int
    size: int = None

    def __post_init__(self):
        # Do some validation
        if self.quantity <= 0:
            raise ValueError("quantity must be greater than zero")

    @classmethod
    def from_dict(cls: t.Type["Item"], obj: dict):
        return cls(
            part_no=obj["part_no"],
            description=obj["descrip"],
            price=obj["price"],
            quantity=obj["quantity"],
            size=obj.get("size"),
        )

@dataclass
class Customer:
    first_name: str
    family_name: str

    @classmethod
    def from_dict(cls: t.Type["Customer"], obj: dict):
        return cls(
            first_name=obj["first_name"],
            last_name=obj["family_name"],
        )

@dataclass
class Address:
    street: str
    city: str
    state: str

    @classmethod
    def from_dict(cls: t.Type["Address"], obj: dict):
        return cls(
            street=obj["street"],
            city=obj["city"],
            state=obj["state"],
        )

@dataclass
class Order:
    reciept: str
    date: date
    customer: Customer
    items: t.Sequence[Item]
    bill_to: Address
    ship_to: Address
    special_delivery: str = None

    @classmethod
    def from_dict(cls: t.Type["Order"], obj: dict):
        return cls(
            receipt=obj["reciept"],
            date=date(obj["date"]),
            customer=Customer.from_dict(obj["customer"]),
            items=[Item.from_dict(item) for item in obj["items"]),
            bill_to=Address.from_dict(obj["bill-to"]),
            ship_to=Address.from_dict(obj["ship-to"]),
            special_delivery=obj.get("specialDelivery"),
        )
さて、アプリケーションで設定を使いたい場合は、次のようにします.
raw_config = {...}

config = Order.from_dict(raw_config)

config.customer.first_name
この方法には利点があります.
  • エディタでコード補完と型ヒントを取得します
  • 1つの場所でconfigプロパティ名を変更しなければならないので
  • のバージョンの和解を実装することができますfrom_dict 方法
  • エディタは自動リファクタクラスのプロパティ名を
  • データコードを直接インスタンス化することができるので、Pythonコードで設定を定義できますsettings.py 例えばファイル
  • テスト可能です.
  • import unittest
    from .config import Order
    
    class TestOrderConfig(unittest.TestCase):
        def test_example_config(self):
            raw_config = {...}
            expected = Order(
                customer=Customer(...),
                ...
            )
    
            self.assertEqual(Order.from_dict(raw_config), expected)
    
    うまくいけば、この便利な発見し、いくつかのプロジェクトをクリーンアップするには、このメソッドを使用することができます!