@propertyとgetterおよびsetterの使用


727

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

回答:


613

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

その理由は、Pythonではすべての属性がパブリックであるためです。アンダースコアまたは2つで始まる名前は、指定された属性が実装の詳細であり、コードの将来のバージョンでは変更されない可能性があることを警告するだけです。実際にその属性を取得または設定することを妨げるものではありません。したがって、標準の属性アクセスは、属性にアクセスするための通常のPythonの方法です。

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


90
二重下線付きの属性名は、Pythonによって特別に処理されます。それは単なる慣習ではありません。参照してくださいdocs.python.org/py3k/tutorial/classes.html#private-variables
6502

63
それらは異なる方法で処理されますが、それらにアクセスできないわけではありません。PS:AD 30 C0
キンドル

4
そして、「@」文字はpythonコードでは醜いので、@ decoratorsを逆参照すると、スパゲッティコードと同じ感覚になります。
ベリーTsakala 2013年

18
同意しません。構造化コードはスパゲッティコードとどのように等しいのですか?Pythonは美しい言語です。しかし、適切なカプセル化や構造化クラスのような単純なものに対するより良いサポートがあればさらに良いでしょう。

69
ほとんどの場合は同意しますが、@ propertyデコレーターの背後にある遅いメソッドを隠すことに注意してください。APIのユーザーは、プロパティアクセスが変数アクセスと同じように機能することを期待しており、その期待から離れすぎると、APIが不快になる可能性があります。
defrex 2014

153

Pythonでは、面白さのためだけにゲッター、セッター、またはプロパティを使用しません。最初に属性を使用し、その後、必要な場合にのみ、最終的にはクラスを使用してコードを変更せずにプロパティに移行します。

確かに、拡張子.pyを使用するコードはたくさんあります。たとえば、単純なタプルが使用するところならどこでもゲッターとセッター、継承、無意味なクラスを使用しますが、Pythonを使用してC ++またはJavaで記述している人々からのコードです。

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


46
@ 6502、「[…]単純なタプルがどこでもできる無意味なクラス」と言ったとき:タプルに対するクラスの利点は、クラスインスタンスがその部分にアクセスするための明示的な名前を提供する一方で、タプルは提供しないことです。 。名前は、特に現在のモジュールの外部に渡される場合、タプルの添え字よりも読みやすく、エラーを回避するのに優れています。
Hibou57

15
@ Hibou57:クラスが役に立たないと言っているのではありません。ただし、タプルで十分な場合もあります。しかし問題は、他の可能性がそれらの言語で使用するのが面倒なので、JavaまたはC ++が誰から来るのかは、すべてのクラスを作成するしかないということです。Pythonを使用したJava / C ++プログラミングのもう1つの典型的な症状は、Pythonでダックタイピングのおかげで独立したクラスを使用できる理由もなく、抽象クラスと複雑なクラス階層を作成することです。
6502

39
そのための@ Hibou57はnamedtupleを使用することもできます:doughellmann.com/PyMOTW/collections/namedtuple.html
hugo24 '25

5
@JonathonReinhart:それはIS 2.6以降の標準ライブラリで...見 docs.python.org/2/library/collections.html
6502

1
「必要に応じて最終的にプロパティに移行する」ときは、クラスを使用してコードを中断することがよくあります。プロパティはしばしば制約を導入します-これらの制約を予期していないコードは、導入するとすぐに機能しなくなります。
yaccob 2017年

118

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


3
@GregKrsakというのはおかしいようです。「同意する大人のもの」は、プロパティが追加される前のpythonミームでした。それは、アクセス修飾子の欠如について不平を言う人々への株の反応でした。プロパティを追加すると、突然カプセル化が必要になります。同じことが抽象基本クラスでも起こりました。「Pythonは常にカプセル化の破壊と戦っていました。自由は奴隷制度です。ラムダは1行に収まるべきです。」
johncip

71

簡単に言えば、プロパティは勝者です。常に。

ゲッターとセッターが必要になることもありますが、それでも私はそれらを外の世界に「隠し」ます。そこの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でのゲッターとセッターのトピックを紹介する簡単な記事次に示します。


1
@BasicWolf-私はフェンスのプロパティ側にいることが暗黙のうちに明らかであると思いました!:)しかし、私はそれを明確にするために私の回答にパラを追加します。
Mac

9
ヒント:「常に」という言葉は、著者が主張ではなく、主張であなたを説得しようとしているヒントです。太字フォントの存在もそうです。(つまり、代わりにCAPSが表示された場合は、-whoa-正しいはずです。)見てください。「プロパティ」機能は、たまたまJavaとは異なるため(Pythonは何らかの理由で事実上の宿敵です)、そのため、Pythonのコミュニティグループが考えるより良いと宣言します。実際には、プロパティは「明示的であるより暗黙的である」という規則に違反していますが、誰もそれを認めたくありません。それはそれを言語にしたので、今ではトートロジーの引数を介して「Pythonic」と宣言されています。
Stuart Berg

3
感情を傷つけることはありません。:-Pこの場合、「Pythonic」の規則が一貫していないことを指摘しようとしていpropertyます。「暗黙的よりも明示的」の方が、の使用と直接競合します。(それ単純な割り当てのように見えますが、関数を呼び出します。)したがって、「Pythonic」は、トートロジーの定義を除いて、本質的に意味のない用語です:「Pythonic規則はPythonicであると定義したものです。」
Stuart Berg、

1
現在、テーマに沿った一連の規則を設けるというアイデア素晴らしいものです。そのような一連の規則が存在する場合は、それを一連の公理として使用して、覚えるトリックの長いチェックリストではなく、思考を導くことができます。公理は外挿に使用でき、まだ誰も見たことのない問題に取り組むのに役立ちます。このproperty機能がPythonicの公理のアイデアをほとんど価値のないものにすると脅かしているのは残念です。したがって、あとはチェックリストだけです。
Stuart Berg

1
同意しません。私はほとんどの状況でプロパティを好みますが、何かを設定すると、selfオブジェクトを変更する以外に副作用があることを強調したい場合は、明示的なセッターが役立つことがあります。たとえばuser.email = "..."、属性を設定するだけのように見えるため、例外が発生するようには見えませんが、例外のようなuser.set_email("...")副作用がある可能性があることを明確にします。
bluenote10

65

[ 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回だけ表す必要があることはわかっているので、先に進んでデコレーターを保持しますがUberPropertymethod参照のみを格納するように変更します。

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

(うわ、私は遅くまで働いています。)

私が使用していることに注意してくださいgetValuesetValueと、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]これがまだ当てはまるかどうか、私は遅れているかもしれません。


53
はい、これは「tldr」です。ここで何をしようとしているかを要約していただけますか?
poolie 2013

9
@Adam return self.x or self.defaultX()これは危険なコードです。ときに何が起こりself.x == 0ますか?
ケリー・トーマス、

参考までに、ゲッターをパラメーター化できるように作成できます。変数をカスタムクラスにして、__getitem__メソッドをオーバーライドしたとします。ただし、完全に非標準のpythonを使用することになるので、それは奇妙なことです。
意志

2
@KellyThomas例を単純に保つようにしています。これを正しく行うには、Noneの値でさえも具体的に設定されている可能性があるため、x dictエントリを作成して完全に削除する必要があります。しかし、そうです、これは本当のユースケースで考慮する必要があることです。
アダムDonahue 14

Javaのようなゲッターを使用すると、まったく同じ計算を実行できますね。
qed

26

私は両方とも彼らの立場を持っていると思います。使用に関する1つの問題@propertyは、標準のクラスメカニズムを使用してサブクラスのゲッターまたはセッターの動作を拡張するのが難しいことです。問題は、実際のゲッター/セッター関数がプロパティに隠されていることです。

あなたは実際に関数を手に入れることができます、例えば

class C(object):
    _p = 1
    @property
    def p(self):
        return self._p
    @p.setter
    def p(self, val):
        self._p = val

あなたはとゲッターとセッターの機能にアクセスすることができますC.p.fgetし、C.p.fsetしかし、あなたは簡単にそれらを拡張するために、通常の方法の継承(例えばスーパー)機能を使用することはできません。スーパーの複雑さを少し掘り下げた後、実際にスーパーを次のように使用できます。

# 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)

ただし、プロパティを再定義する必要があり、pのバインドされていないコピーを取得するには、直感に反するsuper(cls、cls)メカニズムを使用する必要があるため、super()の使用は非常に不格好です。


20

プロパティの使用は私にとってより直感的であり、ほとんどのコードによりよく適合します。

比較する

o.x = 5
ox = o.x

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

読みやすい方は私にはかなり明白です。また、プロパティを使用すると、プライベート変数をはるかに簡単に使用できます。


12

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

プロパティが有用である場合はないというわけではないことに注意してください。クラスを必要のないほど単純かつ透明にすることができれば、より良い場合があるだけです。


3
@ user2239734プロパティの概念を誤解していると思います。プロパティの設定中に値を検証できますが、検証する必要はありません。validate()クラスにはプロパティとメソッドの両方を含めることができます。プロパティは、単純なobj.x = y割り当ての背後に複雑なロジックがある場合に使用され、ロジックの内容次第です。
Zaur Nasibov 2013

12

プロパティは、ゲッターとセッターを実際に必要な場合にのみ記述することでオーバーヘッドを取得できるようにするものだと思います。

Javaプログラミングの文化では、プロパティへのアクセスを絶対に与えないことを強くお勧めします。代わりに、ゲッターとセッターを通過し、実際に必要なものだけを通過するようにします。これらの明白なコードを常に記述することは少し冗長であり、時間の70%が重要なロジックで置き換えられることは決してないことに注意してください。

Pythonでは、人々は実際にその種のオーバーヘッドを気にするので、次のプラクティスを採用できます。

  • 必要がない場合は、最初にゲッターとセッターを使用しないでください。
  • @property残りのコードの構文を変更せずにそれらを実装するために使用します。

1
「また、70%の確率で重要なロジックに置き換えられることはありません。」-それはかなり具体的な数値です。それはどこかから来ているのでしょうか、それとも、ハンドウェーブの「大多数」のようなものとして意図しているのですか(その数値を定量化する研究があれば、私は面倒ではありません。それを読むことに本当に興味がある)
アダム・パーキン

1
ああごめんなさい。この数値をバックアップするためにいくつかの研究があるように思えますが、それは「ほとんどの場合」という意味でした。
フルミコトン

7
それは人々がオーバーヘッドを気にするということではなく、Pythonではクライアントコードを変更せずに直接アクセスからアクセサメソッドに変更できるため、最初にプロパティを直接公開することで失うものは何もありません。
Neil G

10

私は、誰もがプロパティは、ディスクリプタクラスのメソッドをバインドしていることを言及していないことに驚いていますアダム・ドノヒューNeilenMarais:ゲッターとセッターが機能しているとするために使用することができることを-彼らのポストに、まさにこの考え方でGET

  • 検証
  • データを変更する
  • アヒル型(強制型から別型)

これは、正規表現や型キャストのような実装の詳細やコードの荒廃を隠すためのスマートな方法を提供します。ただし、ブロック、アサーション、計算値を除きます。

一般に、オブジェクトに対してCRUDを実行することは、かなりありふれたものですが、リレーショナルデータベースに永続化されるデータの例を検討してください。ORMは、オブジェクトコードで醜いif .. elif .. elseラダーを管理するプロパティクラスで定義されたfget、fset、fdelにバインドされたメソッドで、特定のSQL言語の実装の詳細を非表示にできます。ORM を使用するself.variable = something開発者のために、エレガントで詳細を排除します。

プロパティをボンデージおよびディシプリン言語(Javaなど)の退屈な痕跡としてのみ考える場合、それらは記述子のポイントを失っています。


6

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

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)セッターは複雑で時間がかかる方法に進化する場合があります(データベースにアクセスするなど)。次の関数が非常に遅い理由を別の開発者が理解するのは非常に困難です。彼はプロファイリングdo_something()方法に多くの時間を費やすかもしれませんが、my_object.my_attr = 4.実際には速度低下の原因です:

def slow_function(my_object):
    my_object.my_attr = 4.
    my_object.do_something()

5

@property従来のゲッターとセッターの両方に利点があります。ユースケースによって異なります。

の利点 @property

  • データアクセスの実装を変更するときに、インターフェイスを変更する必要はありません。プロジェクトが小さい場合は、直接属性アクセスを使用してクラスメンバーにアクセスすることをお勧めします。たとえば、メンバーを持つfooタイプのオブジェクトがあるとします。その後、単にこのメンバーをで取得できます。プロジェクトが成長するにつれて、単純な属性アクセスに対していくつかのチェックまたはデバッグが必要になるように感じるかもしれません。その後、クラス内でそれを行うことができます。データアクセスインターフェイスは同じなので、クライアントコードを変更する必要はありません。Foonumnum = foo.num@property

    PEP-8から引用:

    単純なパブリックデータ属性の場合、複雑なアクセサー/ミューテーターメソッドを使用せずに、属性名のみを公開するのが最善です。単純なデータ属性が機能的な動作を拡張する必要があることがわかった場合、Pythonは将来の拡張への簡単なパスを提供することに注意してください。その場合は、プロパティを使用して、機能の実装を単純なデータ属性アクセス構文の背後に隠します。

  • @propertyPythonでのデータアクセスに使用すると、Pythonicと見なされます。

    • Python(Javaではない)プログラマーとしての自己認識を強化できます。

    • 面接担当者がJavaスタイルのゲッターとセッターをアンチパターンであると考えている場合は、面接に役立ちます。

従来のゲッターとセッターの利点

  • 従来のゲッターとセッターでは、単純な属性アクセスよりも複雑なデータアクセスが可能です。たとえば、クラスメンバーを設定する場合、何かが完璧に見えなくても、この操作を強制する場所を示すフラグが必要になることがあります。のような直接メンバーアクセスを拡張する方法は明らかではありませんfoo.num = numが、追加のforceパラメーターを使用して従来のセッターを簡単に拡張できます。

    def Foo:
        def set_num(self, num, force=False):
            ...
  • 従来のゲッターとセッターは、クラスメンバーのアクセスがメソッドを介して行われることを明示的にしています。これの意味は:

    • 結果として得られるものは、そのクラス内に正確に格納されているものとは異なる場合があります。

    • アクセスが単純な属性アクセスのように見えても、パフォーマンスはそれと大きく異なる可能性があります。

    クラスユーザーが@propertyすべての属性アクセスステートメントの後ろに隠れることを期待しない限り、そのようなことを明示的にすることで、クラスユーザーの驚きを最小限に抑えることができます。

  • で述べたように@NeilenMaraisとでこの記事、サブクラスで伝統的なゲッターとセッターを拡張してプロパティを拡張するよりも簡単です。

  • 従来のゲッターとセッターは、さまざまな言語で長い間広く使用されてきました。チームにさまざまなバックグラウンドの人がいる場合、彼らはに比べて見慣れているようです@property。また、プロジェクトの成長に伴い、Pythonを持たない別の言語に移行する必要がある場合は@property、従来のゲッターとセッターを使用すると移行がスムーズになります。

注意事項

  • @property名前の前に2つのアンダースコアを使用しても、従来のゲッターとセッターはクラスメンバーをプライベートにしません。

    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

2

これは、「効果的なPython:より良いPythonを書くための90の特定の方法」からの抜粋です(すばらしい本。私はそれを強くお勧めします)。

覚えておくべきこと

simple単純なパブリック属性を使用して新しいクラスインターフェイスを定義し、セッターメソッドとゲッターメソッドを定義しないようにします。

@必要に応じて、@ propertyを使用して、オブジェクトの属性にアクセスするときの特別な動作を定義します。

least驚きの少ないルールに従い、@ propertyメソッドの奇妙な副作用を回避します。

@ @propertyメソッドが高速であることを確認します。遅い作業や複雑な作業(特にI / Oや副作用が発生する作業)の場合は、代わりに通常の方法を使用してください。

@propertyの高度で一般的な用途の1つは、かつて単純な数値属性であったものを、オンザフライ計算に移行することです。これにより、呼び出しサイトを書き換えることなく、クラスの既存のすべての使用法を移行して新しい動作を実現できます(制御できない呼び出しコードがある場合は特に重要です)。@propertyはまた、時間の経過とともにインターフェイスを改善するための重要な一時的余裕を提供します。

@propertyは、時間の経過とともにより良いデータモデルに向けて段階的に進歩できるため、特に気に入っています。
@propertyは、実際のコードで遭遇する問題に対処するのに役立つツールです。使いすぎないでください。@propertyメソッドを繰り返し拡張していることに気付いた場合は、コードの設計の悪さをさらに回避するのではなく、クラスをリファクタリングするときがきました。

@ @propertyを使用して、既存のインスタンス属性に新しい機能を提供します。

@ @propertyを使用して、より良いデータモデルに向けて段階的に進歩します。

@ @propertyの使用が多すぎる場合は、クラスとすべての呼び出しサイトをリファクタリングすることを検討してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.