[ TL; DR?コード例については、最後までスキップ できます。]
私は実際には別のイディオムを使用することを好みます。これは、1回限りの使用としては少し複雑ですが、より複雑なユースケースがある場合は便利です。
最初に少し背景を説明します。
プロパティは、値の設定と取得の両方をプログラムで処理できる一方で、属性に属性としてアクセスできるという点で便利です。「取得」を「本質的に」「計算」に変えることができ、「セット」を「イベント」に変えることができます。それでは、Javaのようなゲッターとセッターでコーディングした次のクラスがあるとします。
class Example(object):
def __init__(self, x=None, y=None):
self.x = x
self.y = y
def getX(self):
return self.x or self.defaultX()
def getY(self):
return self.y or self.defaultY()
def setX(self, x):
self.x = x
def setY(self, y):
self.y = y
def defaultX(self):
return someDefaultComputationForX()
def defaultY(self):
return someDefaultComputationForY()
私は呼んでなかった理由あなたは不思議に思われるかもしれませんdefaultX
し、defaultY
オブジェクトの中__init__
の方法。その理由は、私たちのケースでは、someDefaultComputation
メソッドがタイムスタンプなどの時間とともに変化する値を返し、いつでもx
(またはy
)が設定されていない場合(この例では、「設定されていない」は「設定されている」という意味ですなし」)x
の(またはy
)のデフォルト計算の値が欲しい。
したがって、これは上記のいくつかの理由で不十分です。プロパティを使用して書き換えます。
class Example(object):
def __init__(self, x=None, y=None):
self._x = x
self._y = y
@property
def x(self):
return self.x or self.defaultX()
@x.setter
def x(self, value):
self._x = value
@property
def y(self):
return self.y or self.defaultY()
@y.setter
def y(self, value):
self._y = value
# default{XY} as before.
私たちは何を得ましたか?これらの属性を属性として参照できるようになりましたが、舞台裏では、メソッドを実行しています。
もちろん、プロパティの本当の力は、これらのメソッドが単に値を取得して設定するだけでなく、何かを実行したいということです(そうでなければ、プロパティを使用しても意味がありません)。私はゲッターの例でこれを行いました。値が設定されていない場合は、基本的に関数本体を実行してデフォルトを取得します。これは非常に一般的なパターンです。
しかし、何が失われ、何ができないのでしょうか。
私の見解では、主な問題は、(ここで行うように)ゲッターを定義する場合、セッターも定義する必要があることです[1]。これは、コードを雑然とさせる余分なノイズです。
もう1つの問題は、x
とのy
値をまだ初期化する必要があること__init__
です。(もちろん、それらを使用して追加することもできますsetattr()
が、それは追加のコードです)。
3番目に、Javaのような例とは異なり、ゲッターは他のパラメーターを受け入れることができません。さて、すでに言っているように聞こえますが、パラメーターを使用している場合、それはゲッターではありません!正式な意味では、そうです。しかし、実際には、名前付き属性をパラメーター化して(たとえば)、x
特定のパラメーターにその値を設定できないようにする必要はありません。
次のようなことができればいいのですが。
e.x[a,b,c] = 10
e.x[d,e,f] = 20
例えば。取得できる最も近いのは、いくつかの特別なセマンティクスを意味するように割り当てをオーバーライドすることです。
e.x = [a,b,c,10]
e.x = [d,e,f,30]
もちろん、最初の3つの値を辞書のキーとして抽出し、その値を数値などに設定する方法をセッターが確実に認識できるようにします。
しかし、たとえそれを行ったとしても、パラメーターをゲッターにまったく渡すことができないため、値を取得する方法がないため、プロパティでそれをサポートすることはできませんでした。したがって、すべてを返さなければならず、非対称性が導入されました。
Javaスタイルのgetter / setterではこれを処理できますが、getter / setterが必要に戻っています。
私が本当に望んでいるのは、次の要件を捉えたものです。
ユーザーは特定の属性に対して1つのメソッドのみを定義し、その属性が読み取り専用か読み取り/書き込みかをそこで指定できます。属性が書き込み可能である場合、プロパティはこのテストに失敗します。
ユーザーが関数の基礎となる追加の変数を定義する必要はないので、__init__
またはsetattr
をコードで必要としません。変数は、この新しいスタイルの属性を作成したという事実によって存在します。
属性のデフォルトコードは、メソッド本体自体で実行されます。
属性を属性として設定し、それを属性として参照できます。
属性をパラメーター化できます。
コードに関しては、次のように書く方法が必要です。
def x(self, *args):
return defaultX()
そしてそれを行うことができる:
print e.x -> The default at time T0
e.x = 1
print e.x -> 1
e.x = None
print e.x -> The default at time T1
など。
また、パラメーター化可能な属性の特殊なケースでこれを行う方法が必要ですが、デフォルトの割り当てケースが機能することを許可します。以下でこれにどのように取り組んだかがわかります。
さてポイントに達しました(イェー!ポイント!)。私がこれのために思いついた解決策は次のとおりです。
プロパティの概念を置き換える新しいオブジェクトを作成します。このオブジェクトは、変数に設定された値を格納することを目的としていますが、デフォルトの計算方法を知っているコードのハンドルも保持しています。その役割は、セットを保存value
するmethod
か、その値が設定されていない場合にを実行することです。
それをと呼びましょうUberProperty
。
class UberProperty(object):
def __init__(self, method):
self.method = method
self.value = None
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def clearValue(self):
self.value = None
self.isSet = False
私はmethod
ここがクラスメソッドでvalue
あり、の値であると想定しています。実際の値である可能性があるためUberProperty
、これを追加しましisSet
た。None
これにより、本当に「値がない」ことを宣言するクリーンな方法が可能になります。別の方法は、ある種の歩哨です。
これは基本的に、私たちが望むことを実行できるオブジェクトを提供しますが、実際にはどのようにしてクラスに配置するのでしょうか。まあ、プロパティはデコレータを使用します。なぜできないのですか?それがどのように見えるかを見てみましょう(ここからは、単一の「属性」だけを使用することにしますx
)。
class Example(object):
@uberProperty
def x(self):
return defaultX()
もちろん、これは実際にはまだ機能していません。実装uberProperty
して、getとsetの両方を処理できることを確認する必要があります。
getから始めましょう。
私の最初の試みは、単に新しいUberPropertyオブジェクトを作成してそれを返すことでした:
def uberProperty(f):
return UberProperty(f)
もちろん、これは機能しないことがすぐにわかりました。Pythonは呼び出し可能オブジェクトをオブジェクトにバインドすることはなく、関数を呼び出すためにオブジェクトが必要です。クラスでデコレーターを作成しても機能しません。これで、クラスはできましたが、操作するオブジェクトがまだありません。
したがって、ここでさらに多くのことができるようにする必要があります。メソッドは1回だけ表す必要があることはわかっているので、先に進んでデコレーターを保持しますがUberProperty
、method
参照のみを格納するように変更します。
class UberProperty(object):
def __init__(self, method):
self.method = method
また、呼び出し可能ではないため、現時点では何も機能していません。
どのようにして画像を完成させますか?さて、新しいデコレータを使用してサンプルクラスを作成すると、どうなるでしょうか。
class Example(object):
@uberProperty
def x(self):
return defaultX()
print Example.x <__main__.UberProperty object at 0x10e1fb8d0>
print Example().x <__main__.UberProperty object at 0x10e1fb8d0>
どちらの場合もUberProperty
、もちろん呼び出し可能ではないものを返すので、これはあまり役に立ちません。
必要なのはUberProperty
、クラスが作成された後でデコレータによって作成されたインスタンスをクラスのオブジェクトに動的にバインドしてから、そのオブジェクトがそのユーザーに返されて使用されるようにする方法です。ええと、それは__init__
電話です、おい。
検索結果を最初にしたいものを書きましょう。UberProperty
インスタンスにをバインドしているので、返すのが明らかなのはBoundUberPropertyです。これが実際にx
属性の状態を維持する場所です。
class BoundUberProperty(object):
def __init__(self, obj, uberProperty):
self.obj = obj
self.uberProperty = uberProperty
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def getValue(self):
return self.value if self.isSet else self.uberProperty.method(self.obj)
def clearValue(self):
del self.value
self.isSet = False
今、私たちは表現です。これらをオブジェクトに取得するにはどうすればよいですか?いくつかのアプローチがありますが、説明するのが最も簡単な__init__
方法は、そのマッピングを行うためにメソッドを使用するだけです。__init__
呼ばれるときまでに、デコレータが実行されているので、オブジェクトの__dict__
属性を調べて、属性の値がtypeである属性を更新する必要がありますUberProperty
。
さて、uber-propertiesはすばらしいので、おそらくそれらをたくさん使いたいと思うでしょう。そのため、すべてのサブクラスに対してこれを行う基本クラスを作成するだけでも理にかなっています。基本クラスが何と呼ばれるか知っていると思います。
class UberObject(object):
def __init__(self):
for k in dir(self):
v = getattr(self, k)
if isinstance(v, UberProperty):
v = BoundUberProperty(self, v)
setattr(self, k, v)
これを追加し、サンプルを継承するように変更しUberObject
、そして...
e = Example()
print e.x -> <__main__.BoundUberProperty object at 0x104604c90>
変更後x
:
@uberProperty
def x(self):
return *datetime.datetime.now()*
簡単なテストを実行できます。
print e.x.getValue()
print e.x.getValue()
e.x.setValue(datetime.date(2013, 5, 31))
print e.x.getValue()
e.x.clearValue()
print e.x.getValue()
そして、必要な出力が得られます。
2013-05-31 00:05:13.985813
2013-05-31 00:05:13.986290
2013-05-31
2013-05-31 00:05:13.986310
(うわ、私は遅くまで働いています。)
私が使用していることに注意してくださいgetValue
、setValue
と、clearValue
ここに。これは、これらを自動的に返すための手段にまだリンクしていないためです。
でも今は立ち止まるのにいい場所だと思います。また、必要なコア機能が整っていることも確認できます。残りは窓のドレッシングです。重要な使いやすさのウィンドウのドレッシングですが、投稿を更新する変更があるまで待つことができます。
次の投稿では、次のことを取り上げて例を完成させます。
UberObject __init__
が常にサブクラスから呼び出されるようにする必要があります。
- したがって、どこかで強制的に呼び出されるか、実装されないようにします。
- メタクラスでこれを行う方法を見ていきます。
誰かが関数を他の何かに「エイリアス」する一般的なケースを確実に処理する必要があります。
class Example(object):
@uberProperty
def x(self):
...
y = x
デフォルトでe.x
戻る必要がありe.x.getValue()
ます。
- 実際に表示されるのは、モデルが失敗する領域の1つです。
- 値を取得するには、常に関数呼び出しを使用する必要があることがわかります。
- しかし、それを通常の関数呼び出しのように見せることができ、を使用する必要がなくなります
e.x.getValue()
。(まだ修正していない場合は、これを行うのは明らかです。)
のe.x directly
ように、設定をサポートする必要がありe.x = <newvalue>
ます。親クラスでもこれを行うことができますが、それを__init__
処理するようにコードを更新する必要があります。
最後に、パラメーター化された属性を追加します。これをどのように行うかについても、かなり明白なはずです。
これまでのコードを次に示します。
import datetime
class UberObject(object):
def uberSetter(self, value):
print 'setting'
def uberGetter(self):
return self
def __init__(self):
for k in dir(self):
v = getattr(self, k)
if isinstance(v, UberProperty):
v = BoundUberProperty(self, v)
setattr(self, k, v)
class UberProperty(object):
def __init__(self, method):
self.method = method
class BoundUberProperty(object):
def __init__(self, obj, uberProperty):
self.obj = obj
self.uberProperty = uberProperty
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def getValue(self):
return self.value if self.isSet else self.uberProperty.method(self.obj)
def clearValue(self):
del self.value
self.isSet = False
def uberProperty(f):
return UberProperty(f)
class Example(UberObject):
@uberProperty
def x(self):
return datetime.datetime.now()
[1]これがまだ当てはまるかどうか、私は遅れているかもしれません。