【Python】PEPを訳して読むーPEP213【ほぼ日】

PEP(Python Enhancement Proposal)はPythonを改良する案、リリーススケジュール、コーディングスタイルなど開発を円滑に進める上で重要なことを文書にまとめておく場所。ここには、Pythonをコーディングするすべての人にとって重要な内容がきっと入っているはず。ビギナーが言うようにもちろん、英語。よって、これをできるだけ毎日、翻訳しながら読み解きまとめていくことで備忘録としていきたいと思います。英語は非常に苦手なので誤訳はご指摘ください。

※以下、アイコンが出てくる箇所は私のコメントになります。それ以外は、翻訳または翻訳のまとめです。

基本情報

Attribute Access Handlers

PEP:213
Title:Attribute Access Handlers
属性アクセスハンドラ
Author:paul at prescod.net (Paul Prescod)
Status:Deferred
Type:Standards Track
Created:21-Jul-2000
Python-Version:2.1
Post-History:

イントロ

インスタンスのクライアントコードが属性を設定して代わりにコードを実行しようとしたときにPythonコードや拡張モジュールで「trap」することは可能であり比較的一般的です。基になる実装が直接バインディングを変更するのではなく何らかの計算を行っている場合でも、ユーザーが属性の割り当て/取得/削除の構文を使用できるようにすることができます。

このPEPは、Pythonインスタンス用にこれらのハンドラを実装することをより簡単に、より効率的に、そしてより安全にする機能を説明しています。

正当化

シナリオ1

stdout“という名前の属性を処理するデプロイ済みクラスがあります。割り当ての時点でstdoutが本当に “write“メソッドを持つオブジェクトであることを確認するのが良い。setstdoutメソッド(デプロイされたコードと互換性がない)に変更するのではなく、trapしてオブジェクトの型を確認します。

シナリオ2

属性割り当ての概念を持つオブジェクトモデルとできるだけ互換性があるべきです。例えばW3C Document Object Modelまたは特定のCOMインターフェース(例えば、PowerPointインターフェース)でそうあるべきです。基礎となる実装では属性をまったく使用しなくても、モデル内の属性をPythonインタフェースの属性として表示することを推奨します。

シナリオ3

ユーザーが属性を読み取り専用にしたい。

この機能により、プログラマは目的を問わず、基礎となる実装から自分のモジュールのインタフェースを切り離すことができます。繰り返しますが、これは新しい機能ではなく、単に既存の規約の新しい構文です。

現在のソリューション

一部の属性を読み取り専用にするには

class foo:
    def __setattr__( self, name, val ):
        if name=="readonlyattr":
            raise TypeError
        elif name=="readonlyattr2":
            raise TypeError
    ...
    else:
        self.__dict__["name"]=val

これには以下の問題があります。

1.
メソッドの作成者は、クラス階層__setattr__のどこか他の場所で特定の目的のためにtrapされているかどうかを熟知している必要があります。辞書に割り当てるのではなく、そのメソッドを呼び出さなければなりません。__setattr__をオーバーロードするにはさまざまな理由があるため、衝突の可能性はかなりあります。例えば、オブジェクトデータベースの実装は、全く関係のない目的のためにsetattrをオーバーロードすることがよくあります。
2.
文字列ベースのswitch文は、すべての属性ハンドラをコード内の1か所に指定することを強制しています。タスク固有のメソッド(モジュール化のため)にディスパッチすることがありますが、これはパフォーマンス上の問題を引き起こす可能性があります。
3.
設定、取得、削除のロジックは__getattr____setattr____delattr__でなければなりません。これは追加レベルのメソッド呼び出しによって軽減できますが、これは非効率的です。

提案されている構文

特別なメソッドは、次の形式の宣言で自分自身を宣言する必要があります。

class x:
    def __attr_XXX__(self, op, val ):
        if op=="get":
            return someComputedValue(self.internal)
        elif op=="set":
            self.internal=someComputedValue(val)
        elif op=="del":
            del self.internal

クライアント側のコードはこのようになります。

fooval=x.foo
x.foo=fooval+5
del x.foo

セマンティクス

3種類すべての属性参照でこのメソッドを呼び出す必要があります。opパラメータは “get” / “set” / “del“です。この文字列はインターンされるので、文字列の実際のチェックは非常に高速になります。

実際には__attr_XXX__という名前のメソッドと同じインスタンス内にXXXという名前の属性を持つことはできません。

適切な属性が見つからなかった場合にのみ__getattr__が呼び出されることになっているという原則に基づいて、__attr_XXX__の実装は__getattr__の実装よりも優先されます。

__attr_XXX__の実装は、一貫性を保つために__setattr__の実装よりも優先されます。反対もまた実行可能であるように思われます。 __del_y__についても同じことが言えます。

提案された実装

属性アクセスハンドラと呼ばれる新しいオブジェクトタイプがあります。このタイプのオブジェクトには、以下の属性があります。

name (e.g. XXX, not __attr__XXX__)
method (pointer to a method object)

PyClass_Newでは、適切な形式のメソッドが検出され、オブジェクトに変換されます(非バインドメソッドオブジェクトとまったく同じです)。これらはXXXという名前でクラス__dict__に格納されています。元のメソッドは、元の名前で非バインドメソッドとして保存されます。

インスタンス内に属性アクセスハンドラがまったく存在しない場合は、フラグが設定されます。それを一旦 “I_have_computed_attributes“と呼びます。派生クラスは基本クラスからフラグを継承します。インスタンスはクラスからフラグを継承します。

オブジェクトが返される直前まで、getは通常どおり進行します。返されたオブジェクトがメソッドであるかどうかの現在のチェックに加えて、返されたオブジェクトがアクセスハンドラであるかどうかもチェックします。もしそうなら、それはgetterメソッドを呼び出して値を返すでしょう。属性アクセスハンドラを削除するには、辞書を直接操作します。

セットは “I_have_computed_attributes“フラグをチェックすることによって進行します。設定されていない場合は、いままでと同じように進みます。設定されている場合は、要求されたオブジェクト名を辞書で取得する必要があります。属性アクセスハンドラを返す場合は、その値を使ってsetter関数を呼び出します。他のオブジェクトが返された場合は、結果を破棄していままでと同じように続行します。属性アクセスハンドラを持つことは、特定のインスタンス上のすべてのセットの属性 “setting“パフォーマンスに多少影響を及ぼしますが、__setattr__を使用することいままでよりも影響は少なくなります。getsは前の__getattr__よりも効率的です。

I_have_computed_attributesフラグは、この機能を使用していないオブジェクトに対して、 “set“ごとに余分な “get“が発生することによるパフォーマンスの低下を防ぐためのものです。このフラグをチェックすると、すべてのオブジェクトに対してパフォーマンス上の影響がわずかに発生します。
deleteの実装はsetの実装と同様です。

注意事項

1.
属性がインスタンスの辞書に追加されたり削除されたりするときに、I_have_computed_attributesフラグを最新に保つロジックを提案していないことにお気づきかもしれません。これは現在のPythonとのままです。使用中のオブジェクトに__setattr__メソッドを追加した場合、そのメソッドは「コンパイル」時に使用可能であったようには動作しません。ダイナミズムは、追加の実装作業に見合う価値はありません。このスニペットは現在の動作を示しています。

>>> def prn(*args):print args
>>> class a:

...    __setattr__=prn
>>> a().foo=5
(<__main__.a instance at 882890>, 'foo', 5)

>>> class b: pass
>>> bi=b()
>>> bi.__setattr__=prn
>>> b.foo=5
2.
__dict __["XXX"]への代入は、__attr_XXX__の属性アクセスハンドラを上書きする可能性があります。アクセスハンドラは情報をプライベートな__XXX変数に格納します。
3.
オブジェクト自体でsetattrまたはgetattrを呼び出そうとする属性アクセスハンドラは、無限ループを引き起こす可能性があります(__getattr__と同様)。__XXXなどの特別な(通常はプライベートな)変数を使用することが解決策です。

特記

PEP252に記述されている記述子メカニズムは、より直接的にサポートするのに十分強力です。これを可能にするために ‘getset‘コンストラクタを言語に追加するという手段があります。

class C:
    def get_x(self):
        return self.__x
    def set_x(self, v):
        self.__x = v
    x = getset(get_x, set_x)

構文を追加するか、命名規則を認識することができます。

コメントする

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です