web-dev-qa-db-ja.com

@propertyとゲッターおよびセッターの使用

これは純粋な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プログラムを設計する場合、どのアプローチを使用しますか、そしてそれはなぜですか?

681
Zaur Nasibov

プロパティを優先する 。それは彼らがそこにいるものです。

その理由は、すべての属性がPythonで公開されているからです。アンダースコア1または2で名前を始めることは与えられた属性がコードの将来のバージョンで変わらないかもしれない実装の詳細であるという単なる警告です。それはあなたが実際にその属性を取得または設定することを妨げるものではありません。したがって、標準的な属性アクセスは、通常のPythonicの方法で、属性にアクセスする方法です。

プロパティの利点は、それらが属性アクセスと構文的に同一であるということです。そのため、クライアントコードを変更することなく、プロパティを変更できます。プロパティを使用するクラスの1つのバージョン(たとえば、契約によるコード作成やデバッグ用)と、本番用ではないクラスを使用することもできますが、それを使用するコードを変更する必要はありません。同時に、後でアクセスをより適切に制御する必要がある場合に備えて、すべてのものに対してゲッターとセッターを作成する必要はありません。

574
kindall

Pythonでは、単に楽しむためだけにゲッターやセッター、あるいはプロパティを使用することはしません。最初は単に属性を使用し、その後必要な場合にのみ、クラスを使用してコードを変更しなくても最終的にプロパティに移行します。

実際には拡張子が.pyのコードがたくさんあります。単純なTupleでも構いませんが、Pythonを使用してC++またはJavaで書いている人のコードです。

それはPythonコードではありません。

144
6502

プロパティを使用すると、通常の属性アクセスから始めて その後必要に応じてゲッターとセッターでバックアップします

114

簡単な答えは、プロパティが勝ち残るです。常に。

ゲッターやセッターが必要になることもありますが、それでも、私はそれらを外の世界に「隠す」ことにします。 Pythonでこれを行うにはたくさんの方法があります(getattrsetattr__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)

これはPythonのゲッターとセッターのトピックを紹介する簡単な記事です

68
mac

[ 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__メソッドでなぜdefaultXdefaultYを呼び出さなかったのか疑問に思われるかもしれません。その理由は、私たちの場合は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__xyの値を初期化しなければならないことです。 (もちろん、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をクラスメソッド、valueUberPropertyの値とします。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

(いやー、私は遅く働いている)

ここではgetValuesetValue、およびclearValueを使用しました。これは、これらが自動的に返されるようにするための手段にまだリンクしていないためです。

しかし、私は疲れているので、これは今のところやめておくべき良い場所だと思います。また、必要なコア機能が整っていることもわかります。残りはウィンドウドレッシングです。重要なユーザビリティウィンドウドレッシング、しかしそれは私が記事を更新するための変更があるまで待つことができます。

次の投稿で、これらのことに対処して例を完成させます。

  • UberObjectの__init__が常にサブクラスによって呼び出されるようにする必要があります。

    • だから我々はそれをどこかで呼ばれることを強制するか、あるいはそれが実行されるのを防ぐ。
    • メタクラスを使ってこれを行う方法を見ていきます。
  • 次のように、誰かが他のものへの機能を「エイリアス」する一般的なケースを確実に処理する必要があります。

      class Example(object):
          @uberProperty
          def x(self):
              ...
    
          y = x
    
  • デフォルトでe.x.getValue()を返すにはe.xが必要です。

    • 私たちが実際に見るのは、これがモデルが失敗する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]私はこれがまだそうであるかどうかについて遅れているかもしれません。

58
Adam Donahue

私は両方とも彼らの場所を持っていると思います。 @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)メカニズムを使用する必要があります。

24
NeilenMarais

プロパティを使うことは私にとってより直感的で、ほとんどのコードにうまく収まります。

比較する

o.x = 5
ox = o.x

vs.

o.setX(5)
ox = o.getX()

どちらが読みやすいかは私には非常に明白です。またプロパティはプライベート変数をはるかに簡単に考慮します。

20
Hobblin

私はほとんどの場合どちらも使わない方がいいでしょう。プロパティの問題は、それらがクラスの透明度を低下させることです。特にセッターから例外を発生させるのであれば、これは問題です。たとえば、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メソッド。

私は、プロパティが有用である場合がないと言っているわけではないことに注意してください、あなたがあなたのクラスをあなたがそれらを必要としないほど十分単純で透明にすることができるならばあなたはより良いかもしれません。

12
user2239734

実際にそれらが必要なときだけ、プロパティがゲッターとセッターを書くことのオーバーヘッドを得ることを可能にすることについて私は感じています。

Javaプログラミング文化は、プロパティへのアクセスを決して与えず、代わりにゲッターとセッターを通過し、実際に必要なものだけを通過することを強く推奨します。これらの明らかなコードを常に書くことは少々冗長であり、そのうちの70%は決して自明ではないロジックに置き換えられていないことに注意してください。

Pythonでは、人々は実際にその種のオーバーヘッドを気にかけているので、次のような慣習を受け入れることができます。

  • 必要でなければ、最初はゲッターとセッターを使わないでください。
  • コードの他の部分の構文を変更せずにそれらを実装するには、@propertyを使用します。
9
fulmicoton

複雑なプロジェクトでは、明示的なセッター関数を使って読み取り専用のプロパティ(またはゲッター)を使うのが好きです。

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()
5
otognan

@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
    
3
Cyker