dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

[python 2系]ディスクリプタ(__get__, __set__, __delete__)

タイトルの通り、__get__ とか __set__ メソッドを実装したクラスの機能について。
python のバージョンは 2.7。

ディスクリプタと呼ぶらしい。こんな公式ドキュメントがある。

http://docs.python.jp/2/howto/descriptor.html

また、理解する上でこちらが大変参考になった。
Python を支える技術 ディスクリプタ編 #pyconjp - Qiita
(スライド版:https://speakerdeck.com/knzm/python-wozhi-eruji-shu-deisukuriputabian)

ディスクリプタとは

以下のように __get__, __set__, __delete__ メソッドを定義したクラスのこと。

class Descriptor(object):
  def __get__(self, obj, obj_type=None): pass
  def __set__(self, obj, value): pass
  def __delete__(self, obj): pass

どうやって使うのか?

まず重要なこととして、ディスクリプタはそのクラス単体ではあまり意味がない
他のクラスの属性にこのディスクリプタを設定することで、クラスの特定の属性へのアクセスをカスタマイズする ために用いる。

このディスクリプタを利用する側の、親となるクラスのことを
公式ドキュメントではオーナ (owner) クラスなどと呼んでいる。

サンプルコード

公式ドキュメント を参考にした。

# ディスクリプタ
class MyDescriptor(object):

    def __init__(self, value=None, name='attr'):
        self.value = value
        self.name = name

    def __get__(self, obj, obj_type=None):
        print 'Get "%s": %r' % (self.name, self.value)
        return self.value

    def __set__(self, obj, value):
        print 'Set "%s": %r' % (self.name, value)
        self.value = value
# インタラクティブシェルで実行
>>> class MyClass(object):  # owner クラス
...   x = MyDescriptor(10, 'attribute x')
...   y = 5
...
>>> m = MyClass()
>>> m.x
Get "attribute x": 10
10
>>> m.y
5
>>> m.x = 20
Set "attribute x": 20
>>> m.y = 50
>>> m.x
Get "attribute x": 10
10
>>> m.y
50

クラス MyClassx という属性へアクセスするときだけ挙動をカスタマイズできていることがわかる。

プロパティ(@property) と何が違うの?

特定の属性へのアクセスをカスタマイズするための手段としてはディスクリプタのほか、 プロパティ (property()) という関数がある。
http://docs.python.jp/2/library/functions.html#property

@property のようにデコレータとして使う方が一般的だろうか。

先ほどの例は、プロパティを使って次のように書くこともできる。

class MyClass(object):
    y = 5

    def __init__(self):
        self._x = 10
        self.name = 'attr x'

    @property
    def x(self):
        print 'Get "%s": %r' % (self.name, self._x)
        return self._x

    @x.setter
    def x(self, value):
        print 'Set "%s": %r' % (self.name, value)
        self._x = value

両者の違いだが、プロパティはディスクリプタの一種。ディスクリプタの方が汎用性が高い。
また、プロパティは特定のクラスに直接定義するのに対し、ディスクリプタはクラスとは独立して定義することができる。

__getattribute__ と何が違うの?

__getattribute__ でも属性アクセスをカスタマイズできるが、クラスの全部の属性アクセスが変更される。
一方、ディスクリプタを使うと 特定の属性のみ アクセスをカスタマイズできる。

その他

X.Y という書き方でクラス/インスタンスの属性にアクセスした時、

  • X がクラスかインスタンス
  • Y がプロパティかメソッドか通常の属性か

によって内部的に起こっていることが違うらしい。

そこらへん、上でも挙げたこの記事に非常に詳しく書かれているので勉強したい。
Python を支える技術 ディスクリプタ編 #pyconjp - Qiita

リファレンス

上で紹介したものも含む。