【Python】クラスのプロパティ(getter/setter)の書き方【入門第35回】

134, 2019-08-28

目次

クラスのプロパティ(property)とは?

こんにちは、narupoです。

今回もクラスの機能ですが、プロパティという機能についてやっていきます。
プロパティはクラスのインスタンス変数へのゲッターセッターを書くのに非常に便利な機能です。
このプロパティを使うとより綺麗にクラスを設計できると言えるでしょう。

具体的には↓を見ていきます。

  • プロパティの必要性

  • プロパティの書き方

  • ゲッター(getter)

  • セッター(setter)

プロパティの必要性

プロパティの必要性についてです。
まず以下のようなクラスがあるとします。

:::python
class Cat:
    def __init__(self):
        self.name = 'ミケ'

↑のクラスCatのインスタンス変数nameにアクセスするには↓のような方法が考えられます。

:::python
cat = Cat()
cat.name = 'タマ'

これはこれで簡便で便利です。
しかし、この設計には問題があります。

たとえば、クラスの設計を進めていくうちに、インスタンス変数nameの変数名を、たとえばcute_nameなどに変えたくなったとします。
そこで思い切りがいいあなたは変数名を変更しました。

:::python
class Cat:
    def __init__(self):
        self.cute_name = 'ミケ'

ここで問題が発生します。
Catのインスタンスを使っている他のところでは、変更前のインスタンス変数nameを使っている可能性があるからです。

:::python
# 他ではnameを参照してるよ~
cat = Cat()
print(cat.name)

namecute_nameに変更してしまったので、従来のnameを使っているコードは動かなくなってしまいます。
もちろん他のコードも変更すればいいのですが、変更箇所が100個とかになるとゲンナリしてきませんか?
実際に、プログラミングではそういう事態がありえるのです。

従来のゲッター、セッター

そこで、こういう事態を避けるため、旧来の方法ではゲッターセッターを定義するのが普通でした。
ゲッターというのは、変数の値を取得するだけのメソッドです。
たとえば↓のようにです。

:::python
class Cat:
    def __init__(self):
        self.name = 'ミケ'

    def get_name(self):
        return self.name


cat = Cat()
print(cat.get_name())

↑のget_nameというメソッドが俗にゲッターと呼ばれるものです。
このゲッターを使ってインスタンス変数にアクセスするようにしていれば、たとえインスタンス変数nameの名前がcute_nameになったとしても、他のコードには影響が及びません。

:::python
class Cat:
    def __init__(self):
        self.cute_name = 'ミケ'

    def get_name(self):
        return self.cute_name


cat = Cat()
print(cat.get_name())

変数に値をセットしたい時は、セッターというものを作ります。
たとえば↓のようにです。

:::python
class Cat:
    def __init__(self):
        self.name = 'ミケ'

    def set_name(self, name):
        self.name = name


cat = Cat()
cat.set_name('タマ')

↑のset_nameというメソッドが俗にセッターと呼ばれるものです。
こうすることで、先ほどのゲッターと同じようにインスタンス変数名が変更されても他のコードには影響を与えずに済みます。

プロパティによるゲッター、セッター

旧来のゲッター、セッターは、たしかに便利なのですが、できれば↓のように書けると一番楽です。

:::python
cat = Cat()
print(cat.name)
cat.name = 'タマ'

つまり、インスタンス変数に直接アクセスするのが一番書いててわかりやすいし、楽だということですね。
しかし、この方法だと変数名の変更などに対応できません。
この書き方でかつ、変数名の変更などに対応できれば最高ということになります。

これを実現するのがプロパティです。
つまり、プロパティを使えば、↓のようにインスタンス変数にアクセスできるのです。

:::python
cat = Cat()
print(cat.name)
cat.name = 'タマ'

もちろん、Cat内部のインスタンス変数の変数名は自由に変更できますし、その影響もクラスの外には伝わりません。
なかなか魔法みたいな機能ですが、さっそくプロパティの書き方を紹介したいと思います。

プロパティの書き方

プロパティは↓のように書きます。

:::text
@property
def プロパティ名(self):
    pass

たとえば↓のようなクラスがあります。

:::python
class Cat:
    def __init__(self):
        self.name = 'ミケ'

このクラスのname変数にプロパティを設定したい場合は↓のようにします。

:::python
class Cat:
    def __init__(self):
        self.__name = 'ミケ'

    @property
    def name(self):
        pass

インスタンス変数nameがプライベート変数になって、__nameに変わっていることに注意してください。
これはプロパティ名との衝突を避けるためです。
これでプロパティの設定は完了ですが、これだけではゲッターやセッターは使えません。

ゲッター(getter)

プロパティのゲッターを作るには↓のようにします。

:::text
@プロパティ名.getter
def プロパティ名(self):
    return self.変数名

さきほどのCatクラスにゲッターを書くには↓のようにします。

:::python
class Cat:
    def __init__(self):
        self.__name = 'ミケ'

    @property
    def name(self):
        pass

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

↓の部分がゲッターを書いている所です。

:::python
    @name.getter
    def name(self):
        return self.__name

このゲッターを使うには↓のようにします。

:::python
cat = Cat()
print(cat.name)

従来のインスタンス変数へのアクセス方法と代わってませんね。
↑の出力結果はミケになります。
本当にさっきのゲッターが呼ばれているのでしょうか?
↓のようにprintを仕込んでみましょう。

:::python
    @name.getter
    def name(self):
        print('ゲッターが呼ばれました')
        return self.__name

これで実行すると↓のような結果になります。

ゲッターが呼ばれました
ミケ

ちゃんとゲッターが呼ばれているようですね。

ゲッターの省略

ちなみに、先ほどのゲッターですが↓のように省略することができます。

:::python
class Cat:
    def __init__(self):
        self.__name = 'ミケ'

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

@propertyのところに直接ゲッターの処理内容を書いています。
実行結果は先ほどの結果と同じです。

セッター(setter)

変数に値を設定するセッターを書くには↓のようにします。

:::text
@プロパティ名.setter
def プロパティ名(self, val):
    self.変数名 = val

例によってCatクラスを例にすると↓のようになります。

:::python
class Cat:
    def __init__(self):
        self.__name = 'ミケ'

    @property
    def name(self):
        pass

    @name.setter
    def name(self, name):
        self.__name = name

↑の↓の部分がセッターを書いている所です。

:::python
    @name.setter
    def name(self, name):
        self.__name = name

このようにセッターを書いておくと、↓のようにしてセッターを呼び出すことができます。

:::python
cat = Cat()
cat.name = 'タマ'

↑の場合、タマという文字列が、セッターの引数nameに渡されます。
見た感じはただの代入ですが、しっかりセッターが呼ばれています。

セッター内で値のバリデーション

セッターを書くと値のバリデーションを書けるようになります。
たとえば↓のように。

:::python
    @name.setter
    def name(self, name):
        if name is None:
            raise TypeError('invalid name')
        self.__name = name

↑のように変数に値をセットする前に、セットする値が正しいかどうか検証(バリデート)します。
こうすることで、よりクラスの設計が堅牢になります。
たとえば↓のようにnameNoneを入れようとすると、

:::python
cat = Cat()
cat.name = None

↓のようにTypeErrorが発生します。

TypeError: invalid name

おわりに

Pythonのクラスのプロパティ機能は、非常に便利で使い勝手のいい機能です。
ぜひ覚えて使えるようになっておきましょう。

以上、次回に続きます。

また見てね

関連動画