これは純粋なPython固有の設計上の質問です。
class MyClass(object):
...
def get_my_attr(self):
...
def set_my_attr(self, value):
...
そして
class MyClass(object):
...
@property
def my_attr(self):
...
@my_attr.setter
def my_attr(self, value):
...
Pythonでは、どちらの方法でも実行できます。 Pythonプログラムを設計する場合、どのアプローチを使用しますか、そしてそれはなぜですか?
プロパティを優先する 。それは彼らがそこにいるものです。
その理由は、すべての属性がPythonで公開されているからです。アンダースコア1または2で名前を始めることは与えられた属性がコードの将来のバージョンで変わらないかもしれない実装の詳細であるという単なる警告です。それはあなたが実際にその属性を取得または設定することを妨げるものではありません。したがって、標準的な属性アクセスは、通常のPythonicの方法で、属性にアクセスする方法です。
プロパティの利点は、それらが属性アクセスと構文的に同一であるということです。そのため、クライアントコードを変更することなく、プロパティを変更できます。プロパティを使用するクラスの1つのバージョン(たとえば、契約によるコード作成やデバッグ用)と、本番用ではないクラスを使用することもできますが、それを使用するコードを変更する必要はありません。同時に、後でアクセスをより適切に制御する必要がある場合に備えて、すべてのものに対してゲッターとセッターを作成する必要はありません。
Pythonでは、単に楽しむためだけにゲッターやセッター、あるいはプロパティを使用することはしません。最初は単に属性を使用し、その後必要な場合にのみ、クラスを使用してコードを変更しなくても最終的にプロパティに移行します。
実際には拡張子が.pyのコードがたくさんあります。単純なTupleでも構いませんが、Pythonを使用してC++またはJavaで書いている人のコードです。
それはPythonコードではありません。
プロパティを使用すると、通常の属性アクセスから始めて その後必要に応じてゲッターとセッターでバックアップします 。
簡単な答えは、プロパティが勝ち残るです。常に。
ゲッターやセッターが必要になることもありますが、それでも、私はそれらを外の世界に「隠す」ことにします。 Pythonでこれを行うにはたくさんの方法があります(getattr
、setattr
、__getattribute__
など)が、非常に簡潔でわかりやすい方法は次のとおりです。
def set_email(self, value):
if '@' not in value:
raise Exception("This doesn't look like an email address.")
self._email = value
def get_email(self):
return self._email
email = property(get_email, set_email)
[ 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()
オブジェクトの__init__
メソッドでなぜdefaultX
とdefaultY
を呼び出さなかったのか疑問に思われるかもしれません。その理由は、私たちの場合はsomeDefaultComputation
メソッドが時間とともに変化する値を返すと仮定したい、そしてx
(またはy
)が設定されていないとき(この例では "not set")です。 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つの厄介なことは、__init__
でx
とy
の値を初期化しなければならないことです。 (もちろん、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
など。
パラメータ化可能な属性の特殊なケースでこれを行う方法も欲しいのですが、それでもデフォルトの代入ケースを機能させることができます。私がこれにどのように取り組んだかがわかります。
今ポイントに(やあ!ポイント!)私がこれを考え出した解決策は以下の通りです。
プロパティの概念を置き換えるために新しいオブジェクトを作成します。このオブジェクトは、それに設定された変数の値を格納することを目的としていますが、デフォルトの計算方法を知っているコードのハンドルも保持しています。その仕事は、set 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
を追加しています。別の方法はある種の監視役です。
これは基本的に私たちが欲しいことができるオブジェクトを私たちに与えますが、どのように実際に私たちのクラスにそれを置くのでしょうか?プロパティはデコレータを使用しています。なぜ私たちはできないのですか?それがどのように見えるかを見てみましょう(これからは、ただ1つの 'attribute'、x
を使うことに固執するつもりです)。
class Example(object):
@uberProperty
def x(self):
return defaultX()
もちろんこれは実際にはまだうまくいきません。 uberProperty
を実装し、それがgetとsetの両方を確実に処理するようにしなければなりません。
ゲットから始めましょう。
私の最初の試みは単に新しいUberPropertyオブジェクトを作成してそれを返すことでした:
def uberProperty(f):
return UberProperty(f)
Pythonは呼び出し可能オブジェクトをオブジェクトにバインドすることは決してなく、関数を呼び出すためにはオブジェクトが必要です。クラスにデコレータを作成してもうまくいきません。今はクラスがありますが、作業するオブジェクトがまだないからです。
だから我々はここでもっとできるようになる必要があるだろう。メソッドは一度だけ表現すればよいことがわかっているので、先に進んでデコレータを付けたままにしますが、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__
を調べて、属性の値が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.getValue()
を返すにはe.x
が必要です。
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]私はこれがまだそうであるかどうかについて遅れているかもしれません。
私は両方とも彼らの場所を持っていると思います。 @property
を使用することに関する1つの問題は、標準のクラスメカニズムを使用してサブクラスでゲッターまたはセッターの振る舞いを拡張するのが難しいということです。問題は、実際のgetter/setter関数がプロパティに隠されていることです。
あなたは実際に関数をつかむことができます。と
class C(object):
_p = 1
@property
def p(self):
return self._p
@p.setter
def p(self, val):
self._p = val
getterおよびsetter関数にC.p.fget
およびC.p.fset
としてアクセスすることはできますが、それらを拡張するために通常のメソッド継承(たとえばsuper)機能を簡単に使用することはできません。 superの複雑さを詳しく調べた後、 can で本当にこのようにsuperを使うことができます。
# Using super():
class D(C):
# Cannot use super(D,D) here to define the property
# since D is not yet defined in this scope.
@property
def p(self):
return super(D,D).p.fget(self)
@p.setter
def p(self, val):
print 'Implement extra functionality here for D'
super(D,D).p.fset(self, val)
# Using a direct reference to C
class E(C):
p = C.p
@p.setter
def p(self, val):
print 'Implement extra functionality here for E'
C.p.fset(self, val)
ただし、super()を使用すると、プロパティを再定義する必要があるため非常に扱いにくくなり、pのバインドされていないコピーを取得するには、少し直感に反するsuper(cls、cls)メカニズムを使用する必要があります。
プロパティを使うことは私にとってより直感的で、ほとんどのコードにうまく収まります。
比較する
o.x = 5
ox = o.x
vs.
o.setX(5)
ox = o.getX()
どちらが読みやすいかは私には非常に明白です。またプロパティはプライベート変数をはるかに簡単に考慮します。
私はほとんどの場合どちらも使わない方がいいでしょう。プロパティの問題は、それらがクラスの透明度を低下させることです。特にセッターから例外を発生させるのであれば、これは問題です。たとえば、Account.emailプロパティがあるとします。
class Account(object):
@property
def email(self):
return self._email
@email.setter
def email(self, value):
if '@' not in value:
raise ValueError('Invalid email address.')
self._email = value
その場合、クラスのユーザーは、プロパティに値を代入すると例外が発生する可能性があることを期待していません。
a = Account()
a.email = 'badaddress'
--> ValueError: Invalid email address.
その結果、例外が処理されず、コールチェーン内で伝播して正しく処理されないか、非常に役に立たないトレースバックがプログラムユーザに表示される可能性があります(これは残念ながらpythonとJavaの世界ではあまりに一般的です)。 ).
ゲッターやセッターの使用も避けます。
プロパティやゲッター/セッターの代わりに、検証メソッドのように明確に定義された場所で複雑なロジックを実行することを好みます。
class Account(object):
...
def validate(self):
if '@' not in self.email:
raise ValueError('Invalid email address.')
またはよく似たAccount.saveメソッド。
私は、プロパティが有用である場合がないと言っているわけではないことに注意してください、あなたがあなたのクラスをあなたがそれらを必要としないほど十分単純で透明にすることができるならばあなたはより良いかもしれません。
実際にそれらが必要なときだけ、プロパティがゲッターとセッターを書くことのオーバーヘッドを得ることを可能にすることについて私は感じています。
Javaプログラミング文化は、プロパティへのアクセスを決して与えず、代わりにゲッターとセッターを通過し、実際に必要なものだけを通過することを強く推奨します。これらの明らかなコードを常に書くことは少々冗長であり、そのうちの70%は決して自明ではないロジックに置き換えられていないことに注意してください。
Pythonでは、人々は実際にその種のオーバーヘッドを気にかけているので、次のような慣習を受け入れることができます。
@property
を使用します。複雑なプロジェクトでは、明示的なセッター関数を使って読み取り専用のプロパティ(またはゲッター)を使うのが好きです。
class MyClass(object):
...
@property
def my_attr(self):
...
def set_my_attr(self, value):
...
長生きのプロジェクトでは、コードを書くよりデバッグとリファクタリングに時間がかかります。デバッグをさらに困難にする@property.setter
を使用することにはいくつかの欠点があります。
1)pythonは既存のオブジェクトに対して新しい属性を作成することを可能にします。これは、以下の誤記を追跡するのを非常に困難にします。
my_object.my_atttr = 4.
もしあなたのオブジェクトが複雑なアルゴリズムなら、なぜそれが収束しないのかを見つけるためにかなりの時間を費やすでしょう(上の行の余分な 't'に注意してください)
2)設定者は時々複雑で遅い方法に進化するかもしれません(例えばデータベースに当たる)。他の開発者にとって、次の機能が非常に遅い理由を理解するのは非常に困難です。 my_object.my_attr = 4.
が実際にスローダウンの原因である間、彼はdo_something()
メソッドのプロファイリングに多くの時間を費やすかもしれません:
def slow_function(my_object):
my_object.my_attr = 4.
my_object.do_something()
@property
と伝統的なゲッターおよびセッターの両方に利点があります。それはあなたのユースケースによります。
@property
の利点データアクセスの実装を変更しながら、インターフェイスを変更する必要はありません。プロジェクトが小さい場合は、おそらく直接属性アクセスを使用してクラスメンバーにアクセスしたいと思うでしょう。たとえば、メンバーfoo
を持つFoo
型のオブジェクトnum
があるとします。それならnum = foo.num
でこのメンバーを取得するだけです。プロジェクトが大きくなるにつれて、単純な属性アクセスについていくつかのチェックやデバッグが必要になると感じるかもしれません。それなら@property
within クラスでそれができます。データアクセスインタフェースは同じままなので、クライアントコードを変更する必要はありません。
PEP-8 :から引用
単純なパブリックデータ属性の場合は、複雑なアクセサメソッドやミューテータメソッドを使用せずに、属性名だけを公開するのが最善です。単純なデータ属性で機能的な振る舞いを増やす必要がある場合は、Pythonが将来の機能拡張への簡単なパスを提供することに留意してください。その場合は、単純なデータ属性アクセス構文の背後に機能的実装を隠すためにプロパティを使用します。
Pythonでデータアクセスに@property
を使用することは Pythonic と見なされます。
それはPython(Javaではない)プログラマーとしてのあなたの自己識別を強化することができます。
インタビュアーがJavaスタイルのゲッターおよびセッターが アンチパターン であると考えている場合は、就職の面接に役立ちます。
従来のゲッターとセッターは、単純な属性アクセスよりも複雑なデータアクセスを可能にします。たとえば、クラスメンバを設定しているとき、何かが完璧に見えない場合でも、この操作を強制したい場所を示すフラグが必要な場合があります。 foo.num = num
のように直接のメンバアクセスを増強する方法は明らかではありませんが、追加のforce
パラメータで伝統的なセッターを容易に増強することができます。
def Foo:
def set_num(self, num, force=False):
...
伝統的なゲッターとセッターはクラスメンバーアクセスがメソッドを通して行われることを 明示的 にします。これの意味は:
結果として得られるものは、そのクラス内に正確に格納されているものと同じではないかもしれません。
アクセスが単純な属性アクセスのように見えても、パフォーマンスはそれとは大きく異なる可能性があります。
クラスユーザーがすべての属性アクセスステートメントの背後に隠れている@property
を期待しているのでなければ、そのようなことを明示的にすることで、クラスユーザーの驚きを最小限に抑えることができます。
@NeilenMarais および で述べたように、このポスト では、サブクラスで従来のゲッターとセッターを拡張する方が、プロパティを拡張するよりも簡単です。
伝統的なゲッターとセッターは長い間さまざまな言語で広く使われてきました。チームの経歴が異なる人々がいる場合、彼らは@property
よりもよく知られているように見えます。また、プロジェクトが大きくなるにつれて、Pythonから@property
を持たない別の言語に移行する必要がある場合は、従来のゲッターとセッターを使用すると移行がスムーズになります。
名前の前に二重下線を使用している場合でも、@property
も従来のゲッターおよびセッターもクラスメンバーを非公開にしません。
class Foo:
def __init__(self):
self.__num = 0
@property
def num(self):
return self.__num
@num.setter
def num(self, num):
self.__num = num
def get_num(self):
return self.__num
def set_num(self, num):
self.__num = num
foo = Foo()
print(foo.num) # output: 0
print(foo.get_num()) # output: 0
print(foo._Foo__num) # output: 0