このページで解説している内容は、以下の YouTube 動画の解説で見ることができます。

【Python入門】デコレータ

デコレータ

 ここでは、クラス定義に役立つ複数のデコレータについて学びます。すでに見た@abstractmethod以外にも、プロパティを定義する@propertyや、オブジェクトを使わずに呼び出す静的メソッド(@staticmethod)・クラスメソッド(@classmethod)などが存在します。これらのデコレータを活用すると、クラスの可読性と安全性が高まるうえ、メソッドの使い方を直感的に統一できます。

プログラムのダウンロード

 「ダウンロード」から実行できるサンプルプログラムがダウンロードできます。ファイルは、ESET Endpoint Securityでウイルスチェックをしておりますが、ダウンロードとプログラムの実行は自己責任でお願いいたします。

1.プロパティでデータ属性を上手に管理する

1.1.取得専用のプロパティ(@property)

 Pythonでは、クラスの外部からオブジェクトのデータ属性に直接アクセスできます。ただし、値の設定を制限したり、取得時に自動計算を行いたいときは「プロパティ」を使う方法が便利です。次の例では、商品のnamepriceを保持するクラスを作り、priceを取得専用にしてみます。

class Product:
    def __init__(self, name, price):
        self.__name = name      # マングリング
        self.__price = price    # マングリング

    @property
    def name(self):
        """商品名を取得するプロパティ"""
        return self.__name

    @property
    def price(self):
        """取得専用の価格プロパティ"""
        return self.__price

def demo_property():
    print("◆プロパティのデモ◆")
    p = Product("チョコレート", 150)
    print("商品名:", p.name)
    print("価格:", p.price)
    
    print("priceに値を代入しようとするとエラーが発生します。")
    try:
        p.price = 200  # ここでエラーになる
    except AttributeError as e:
        print("エラー:", e)

if __name__ == "__main__":
    demo_property()
    print("プロパティのサンプルを終了します。")

実行結果

◆プロパティのデモ◆
商品名: チョコレート
価格: 150
priceに値を代入しようとするとエラーが発生します。
エラー: property 'price' of 'Product' object has no setter
プロパティのサンプルを終了します。

解説

  • __name__price は、マングリングによって外部から直接アクセスしづらくなっている。
  • @property を付けたpriceは取得専用。代入を試みると AttributeError が発生。
  • データ属性を外から無制限に変更されたくない場合は、プロパティを活用すると便利。

1.2.設定可能なプロパティ(@プロパティ名.setter)

 値を代入できるようにしたい場合は、@プロパティ名.setterを定義して「値を代入するときの動作」を書きます。以下のように、もし負の値が与えられたら0とするなどのバリデーションを行えます。

class Product:
    def __init__(self, name, price):
        self.__name = name
        self.__price = price

    @property
    def name(self):
        return self.__name

    @property
    def price(self):
        return self.__price
    
    @price.setter
    def price(self, value):
        if value < 0:
            value = 0
        self.__price = value

 こうすれば、外部コードからは「まるでデータ属性に直接アクセスしているかのような書き方」で利用できるものの、実際は背後でバリデーション処理などを挟めるため、クラス利用者に優しい設計ができます。

2.静的メソッドとクラスメソッド

2.1.静的メソッド(@staticmethod)

 通常のインスタンスメソッドは、呼び出しの際に「どのオブジェクトか」を表す引数selfを受け取ります。一方、静的メソッドはインスタンスを受け取らずに呼べるメソッドです。共通処理や単なる計算だけを行い、クラスやオブジェクトに依存しない機能を提供するときに便利です。

class Calc:
    @staticmethod
    def add(a, b):
        return a + b
    
    @staticmethod
    def multiply(a, b):
        return a * b

def demo_staticmethod():
    print("◆静的メソッドのデモ◆")
    result_add = Calc.add(3, 5)       # オブジェクト不要
    result_mul = Calc.multiply(3, 5)  # オブジェクト不要
    print("3 + 5 =", result_add)
    print("3 * 5 =", result_mul)

if __name__ == "__main__":
    demo_staticmethod()

実行結果

◆静的メソッドのデモ◆
3 + 5 = 8
3 * 5 = 15

解説

  • addmultiplyは「クラス名.メソッド名」で呼び出しOK。
  • インスタンスを作る必要がないため、単なるUtilityメソッドとしての役割を果たせる。

2.2.クラスメソッド(@classmethod)

 クラスメソッドは、呼び出し時にクラスそのものを表す引数clsを受け取り、クラスに依存した処理を行います。以下の例では、設定済みの「標準消費税率」をもとに金額に対する消費税を計算するクラスメソッドを用意しています。

class TaxCalculator:
    tax_rate = 0.10  # クラス属性(標準消費税率)

    @classmethod
    def set_tax_rate(cls, rate):
        """クラス全体の消費税率を変更する。"""
        cls.tax_rate = rate

    @classmethod
    def calc_price_with_tax(cls, price):
        """価格に消費税を加えた金額を返す。"""
        return int(price * (1 + cls.tax_rate))

def demo_classmethod():
    print("◆クラスメソッドのデモ◆")
    print("標準税率:", TaxCalculator.tax_rate)
    
    total = TaxCalculator.calc_price_with_tax(1000)
    print("1000円の税込価格:", total, "円")
    
    TaxCalculator.set_tax_rate(0.08)  # 税率を8%に変更
    total2 = TaxCalculator.calc_price_with_tax(1000)
    print("税率変更後 1000円の税込価格:", total2, "円")

if __name__ == "__main__":
    demo_classmethod()

実行結果

◆クラスメソッドのデモ◆
標準税率: 0.1
1000円の税込価格: 1100 円
税率変更後 1000円の税込価格: 1080 円

解説

  • @classmethod : 引数にclsを受け取り、クラスの属性(tax_rateなど)にアクセス・変更。
  • set_tax_rateでクラス属性を変更すれば、すべてのインスタンスやメソッドに反映される。
  • calc_price_with_taxを呼ぶ際には「TaxCalculator.calc_price_with_tax(1000)」のようにクラス名で呼び出し可能。

まとめ

  1. プロパティ(@property / @プロパティ名.setter)
    ・データ属性へのアクセスを細かく制御し、取得のみ/取得と設定両方を安全に行う仕組み。
    ・既存コードを変更せずに「ゲッター・セッター」を導入できる点が利点。
  2. 静的メソッド(@staticmethod)
    ・インスタンスに依存しない処理をクラス内にまとめるのに適しており、単なるユーティリティ関数などを実装するときに便利。
  3. クラスメソッド(@classmethod)
    ・クラス自体を対象にした処理を行い、クラス属性や他の静的メソッドを呼び出す際に有用。
    ・クラス名を変更しても呼び出し箇所を修正しなくて済むケースがある。

 デコレータは、クラス定義をより明確かつ機能的にするための強力な手段です。プロパティによる読み書きの制御、静的メソッドやクラスメソッドによるクラス単位の便利な関数などを覚えておけば、オブジェクト指向の設計やメンテナンスが大幅に楽になるでしょう。