何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

『Effective Python』Item 29: ゲッターやセッターの代わりに通常の属性を使おう

『Effective Python』の続き。ゲッターやセッターをPythonで実装しちゃうのは素人。 www.effectivepython.com

今回からChapter 4「メタクラスと属性」に突入。 タイトルの順序はメタクラスが先であるが、しばらくは属性の話が続く。

ゲッターやセッターと聞くとJavaが思い浮かぶが、PythonではJavaのようにゲッターやセッターを実装する必要なはい。

class OldResistor(object):
    def __init__(self, ohms):
        self._ohms = ohms

    def get_ohms(self):
        return self._ohms

    def set_ohms(self, ohms):
        self._ohms = ohms

r0 = OldResistor(50e3)
print('Before: %5r' % r0.get_ohms())
r0.set_ohms(10e3)
print('After:  %5r' % r0.get_ohms())

とやっても問題ないが、

r0.set_ohms(r0.get_ohms() + 5e3)

というアレなコードが出来てしまう。

Pythonの場合はシンプルに

class Resistor(object):
    def __init__(self, ohms):
        self.ohms = ohms
        self.voltage = 0
        self.current = 0

r1 = Resistor(50e3)
r1.ohms = 10e3

とすれば、

r1.ohms += 5e3

とできる。

ここまでではタイトオンリーな話題であるが、より複雑なことを行いたい場合は@propertyデコレータを使いましょう、と述べている。

例えば、電圧値をセットすると電流値を更新するようにできる。

class VoltageResistance(Resistor):
    def __init__(self, ohms):
        super().__init__(ohms)
        self._voltage = 0

    @property
    def voltage(self):
        return self._voltage

    @voltage.setter
    def voltage(self, voltage):
        self._voltage = voltage
        self.current = self._voltage / self.ohms

r2 = VoltageResistance(1e3)
print('Before: %5r amps' % r2.current)
r2.voltage = 10
print('After:  %5r amps' % r2.current)
>>>
Before:     0 amps
After:   0.01 amps

注意点は色々書いてあるが、雑に要約するならば「余計なことはするな」と言えるだろうか。 上記のように抵抗値と電圧値が決まっている状態で電流値が更新される、ならばまだ想定できるが、

class MysteriousResistor(Resistor):
    @property
    def ohms(self):
        self.voltage = self._ohms * self.current
        return self._ohms

    @ohms.setter
    def ohms(self, ohms):
        self._ohms = ohms

r7 = MysteriousResistor(10)
r7.current = 0.01
print('Before: %5r' % r7.voltage)
r7.ohms
print('After:  %5r' % r7.voltage)

と余計なことをすると

>>>
Before:     0
After:    0.1

のようにr7.ohmを呼び出しただけで電圧値が変わってしまう。