Python のディスクリプターの紹介

Python のディスクリプターを使用して属性へのアクセスを管理する

Python で簡単にディスクリプターを作成して適用する方法を学びましょう。

Alex Starostin, QA Engineer, IBM

Photo of Alexander StarostinAlex は IBM オーストラリアの開発研究所の QA 技術者です。彼は主にアジャイル環境の開発チームに対するソフトウェアのテストと QA を担当しています。通常の業務とは別に、彼は新しい技術やプログラミング言語を学ぶことが好きです。



2012年 7月 26日

はじめに

Python のディスクリプターは、新しいスタイル・クラスと共に Python 2.2 で導入されましたが、あまり広く使われてはいません。Python では、ディスクリプターは管理される属性を作成するための手段です。管理される属性にはさまざまなメリットがありますが、管理される属性を使用すると、属性の値が変更されないように保護することや、属性の値が変更されたときにその属性と依存関係のある属性の値が自動的に更新されるようにすることができます。

ディスクリプターを理解すると Python に関する理解を深めることや、コーディング・スキルを磨くことができます。この記事では、ディスクリプター・プロトコルについて紹介するとともに、ディスクリプターの作成方法と使用方法について説明します。

ディスクリプター・プロトコル

Python のディスクリプター・プロトコルは、モデルで属性を参照した場合に何が起こるかを規定する手段にすぎません。ディスクリプター・プロトコルを使用すると、属性へのアクセス (以下に記載) を簡単かつ効率的に管理することができます。

  • 属性の設定 (set)
  • 属性の取得 (get)
  • 属性の削除 (delete)

他のプログラミング言語では、ディスクリプターはセッター (setter) やゲッター (getter) と呼ばれます。セッターやゲッターではパブリック・メソッドを使用してプライベート変数を設定 (set) または取得 (get) します。Python にはプライベート変数という概念はなく、ディスクリプター・プロトコルはプライベート変数に似たものを Python で実現する手段とみなすことができます。

一般に Python でのディスクリプターは、属性を扱う動作を備えたオブジェクトであり、その属性へのアクセスはディスクリプター・プロトコルのメソッドによってオーバーライドされます。それらのメソッドは __get____set____delete__ であり、このいずれかが定義されているオブジェクトはディスクリプターと呼ばれます。リスト 1 に示すこれらのメソッドをよく見てください。

リスト 1. ディスクリプターのメソッド
__get__(self, instance, owner)
__set__(self, instance, value)
__delete__(self, instance)

ここで、

__get__ は属性にアクセスし、属性の値を返します。要求された属性が存在しない場合には AttributeError 例外をスローします。

__set__ は属性を設定する操作の中で呼び出されます。このメソッドは何も返しません。

__delete__ は削除操作を行います。このメソッドも何も返しません。

ディスクリプターはインスタンスに対して設定されるのではなく、クラスに設定されるという点に注意することが重要です。つまりディスクリプターのコードに手を加えるのではなく、クラスに対して変更を行うことで、ディスクリプター自体が変更されたり、削除されたりします。


ディスクリプターが必要な場合

email 属性について考えてみてください。email 属性に値を設定する際には、その前に適切な e-メール・アドレスの形式になっているかどうかを検証する必要があります。このような機能を持ったディスクリプターでは、正規表現を使って、email 属性に設定する値の形式を検証してから、その値を設定することができます。

多くの場合、Python のプロトコル・ディスクリプターは、属性へのアクセスを制御すること (name 属性を保護するなど) ができます。


ディスクリプターを作成する

ディスクリプターは以下のようにいくつかの方法で作成することができます。

  • クラスを作成し、ディスクリプター・メソッド (__set____ get____delete__) をオーバーライドする方法。この方法は、さまざまなクラスおよび属性で同じディスクリプターが必要な場合 (型を検証する場合など) に使用されます 。
  • プロパティー型を使用する方法。プロパティー型を使用した方が単純かつ容易にディスクリプターを作成することができます。
  • プロパティー・デコレーターの強力さを使用する方法。この方法はプロパティー型を使用する方法と Python のデコレーターとを組み合わせた方法です。

以下に挙げる例はすべて、操作の観点からは似ていますが、実装が異なります。


クラス・メソッドを使用してディスクリプターを作成する

リスト 2 を見ると、Python では属性を設定して制御するのが簡単であることがわかります。

リスト 2. クラス・メソッドを使用してディスクリプターを作成する
class Descriptor(object):

    def __init__(self):
        self._name = ''

    def __get__(self, instance, owner):
        print "Getting: %s" % self._name
        return self._name

    def __set__(self, instance, name):
        print "Setting: %s" % name
        self._name = name.title()

    def __delete__(self, instance):
        print "Deleting: %s" %self._name
        del self._name

class Person(object):
    name = Descriptor()

このコードを使用すると、以下のような出力が得られます。

>>> user = Person()
>>> user.name = 'john smith'
Setting: john smith
>>> user.name
Getting: John Smith
'John Smith'
>>> del user.name
Deleting: John Smith

親クラスの __set__() メソッド、__get__() メソッド、__delete__() メソッドをオーバーライドしてディスクリプター・クラスが作成されており、以下の動作をするようになっています。

  • 取得動作では、「Getting: ~」と出力されます。
  • 削除動作では、「Deleting: ~」と出力されます。
  • 設定動作では、「Setting: ~」と出力されます。

そして属性に値を設定する前に、値をタイトルの形式 (単語の先頭の文字は大文字にし、残りの文字は小文字にする形式) に変更しています。これは、例えば名前を格納したり出力したりする場合に便利です。

大文字への変換を __get__() メソッドに移すこともできます。その場合には、_value に元の値が保持され、取得要求があるとタイトル形式に変換されます。


プロパティー型を使用してディスクリプターを作成する

リスト 2 のディスクリプターは有効で実際に機能しますが、別の方法としてプロパティー型を使用してディスクリプターを作成する方法があります。property() を使用すると、任意の属性に対して使用可能なディスクリプターを簡単に作成することができます。property() を作成するための構文は property(fget=None, fset=None, fdel=None, doc=None) です。ここで指定されているメソッドは以下のとおりです。

  • fget ― 属性を取得するメソッド
  • fset ― 属性を設定するメソッド
  • fdel ― 属性を削除するメソッド
  • doc ― docstring

プロパティーを使用して、リスト 2 の例をリスト 3 のように書き直します。

リスト 3. プロパティー型を使用してディスクリプターを作成する
class Person(object):
    def __init__(self):
        self._name = ''

    def fget(self):
        print "Getting: %s" % self._name
        return self._name
    
    def fset(self, value):
        print "Setting: %s" % value
        self._name = value.title()

    def fdel(self):
        print "Deleting: %s" %self._name
        del self._name
    name = property(fget, fset, fdel, "I'm the property.")

このコードを使用すると、以下のような出力が得られます。

>>> user = Person()
>>> user.name = 'john smith'
Setting: john smith
>>> user.name
Getting: John Smith
'John Smith'
>>> del user.name
Deleting: John Smith

明らかに、結果は同じです。ここで注意する点として、fget メソッド、fset メソッド、fdel メソッドはオプションですが、これらのメソッドが指定されていない場合、そのメソッドに対応する操作をしようとすると AttributeError 例外が発生します。例えば、name のプロパティーで fsetNone として宣言しておきながら、name 属性に値を設定しようとすると、AttributeError 例外が発生します。

この方法を使用すると、以下のようにシステム内で読み取り専用の属性を定義することができます。

name = property(fget, None, fdel, "I'm the property")
user.name = 'john smith'

これによる出力は、以下のようになります。

Traceback (most recent call last):
File stdin, line 21, in mоdule
user.name = 'john smith'
AttributeError: can't set attribute

プロパティー・デコレーターを使用してディスクリプターを作成する

リスト 4 のように、Python のデコレーターを使用してディスクリプターを作成することもできます。Python のデコレーターは、関数やメソッドを手軽に変更できるように Python の構文に特定の変更を加えたものです。この例では属性を管理するためのメソッドを変更しています。Python のデコレーターの適用方法については、developerWorks の記事、「魅力的な Python: デコレーターで魔法を身近に」を参照してください。

リスト 4. プロパティー・デコレーターを使用してディスクリプターを作成する
class Person(object):

    def __init__(self):
        self._name = ''

    @property
    def name(self):
        print "Getting: %s" % self._name
        return self._name

    @name.setter
    def name(self, value):
        print "Setting: %s" % value
        self._name = value.title()

    @name.deleter
    def name(self):
        print ">Deleting: %s" % self._name
        del self._name

実行時にディスクリプターを作成する

ここまで挙げた例はすべて、name 属性に対して操作を行ったものです。これらの方法の制約として、各属性に対して別々に __set__()__get__()__delete__() をオーバーライドしなければなりません。リスト 5 は、実行時にプロパティー属性を追加したい場合に考えられる方法の 1 つです。この方法ではプロパティー型を使用してデータのディスクリプターを作成します。

リスト 5. 実行時にディスクリプターを作成する
class Person(object):

    def addProperty(self, attribute):
        # create local setter and getter with a particular attribute name 
        getter = lambda self: self._getProperty(attribute)
        setter = lambda self, value: self._setProperty(attribute, value)

        # construct property attribute and add it to the class
        setattr(self.__class__, attribute, property(fget=getter, \
                                                    fset=setter, \
                                                    doc="Auto-generated method"))

    def _setProperty(self, attribute, value):
        print "Setting: %s = %s" %(attribute, value)
        setattr(self, '_' + attribute, value.title())    

    def _getProperty(self, attribute):
        print "Getting: %s" %attribute
        return getattr(self, '_' + attribute)

このコードを使用すると、以下のような出力が得られます。

>>> user = Person()
>>> user.addProperty('name')
>>> user.addProperty('phone')
>>> user.name = 'john smith'
Setting: name = john smith
>>> user.phone = '12345'
Setting: phone = 12345
>>> user.name
Getting: name
'John Smith'
>>> user.__dict__
{'_phone': '12345', '_name': 'John Smith'}

このコードでは、name 属性と phone 属性を実行時に作成しています。name 属性と phone 属性に対応する名前によって name 属性と phone 属性にアクセスすることはできますが、_setProperty メソッドで指定することにより、name 属性と phone 属性は _name と _phone としてオブジェクト名前空間辞書に格納されます。基本的に、name phone は内部にある _name 属性と _phone 属性に対するアクセサーです。

この場合に name プロパティー属性を追加しようとすると、システム内の _name 属性がどうなるのか、皆さんは疑問を持たれるかもしれません。答えとしては、その場合は新しいプロパティー属性で既存の _name 属性を上書きします。このコードにより、クラス内での属性の処理方法を制御することができます。


まとめ

Python のディスクリプターを利用すると、新しいスタイル・クラスを使用して強力かつ柔軟に属性を管理することができます。Python のディスクリプターをデコレーターと組み合わせるとスマートにプログラミングすることができ、セッターとゲッターを作成したり、読み取り専用の属性を作成したりすることができます。また、要求に対して、属性の値や型の検証を実行することができます。ディスクリプターはさまざまな用途に使用することができますが、オブジェクトの通常の動作をオーバーライドすることによってコードが必要以上に複雑にならないように、よく注意して使用してください。

参考文献

学ぶために

製品や技術を入手するために

  • 皆さんの目的に最適な方法で IBM 製品を評価してください: 製品の試用版をダウンロードする方法、オンラインで製品を試す方法、クラウド環境で製品を使う方法、あるいは SOA Sandbox で数時間を費やし、サービス指向アーキテクチャーの効率的な実装方法を学ぶ方法などがあります。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=826724
ArticleTitle=Python のディスクリプターの紹介
publish-date=07262012