ゲッターとセッターを使用するpythonicの方法は何ですか?


340

私はそれを次のようにしています:

def set_property(property,value):  
def get_property(property):  

または

object.property = value  
value = object.property

私はPythonが初めてなので、まだ構文を調べています。これを行うためのアドバイスをお願いします。

回答:


684

これを試してください:Pythonプロパティ

サンプルコードは次のとおりです。

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        print("getter of x called")
        return self._x

    @x.setter
    def x(self, value):
        print("setter of x called")
        self._x = value

    @x.deleter
    def x(self):
        print("deleter of x called")
        del self._x


c = C()
c.x = 'foo'  # setter called
foo = c.x    # getter called
del c.x      # deleter called

2
_xをインスタンス化するときに、イニシャライザでxのセッターが呼び出されますか?
ケーシー

7
@Casey:いいえ。._x(プロパティではなく、単なる属性)への参照は、propertyラッピングをバイパスします。を.x通過する参照のみproperty
ShadowRanger

272

ゲッターとセッターを使用するpythonicの方法は何ですか?

「Pythonic」の方法は、「ゲッター」と「セッター」を使用するのではなく、質問が示すようにプレーンな属性を使用しdel、削除することです(ただし、名前は無害な...ビルトインを保護するために変更されます)。

value = 'something'

obj.attribute = value  
value = obj.attribute
del obj.attribute

後で設定を変更して取得したい場合は、propertyデコレータを使用することで、ユーザーコードを変更せずに行うことができます。

class Obj:
    """property demo"""
    #
    @property            # first decorate the getter method
    def attribute(self): # This getter method name is *the* name
        return self._attribute
    #
    @attribute.setter    # the property decorates with `.setter` now
    def attribute(self, value):   # name, e.g. "attribute", is the same
        self._attribute = value   # the "value" name isn't special
    #
    @attribute.deleter     # decorate with `.deleter`
    def attribute(self):   # again, the method name is the same
        del self._attribute

(各デコレータの使用法は、以前のプロパティオブジェクトをコピーおよび更新するため、各セット、取得、および削除の関数/メソッドには同じ名前を使用する必要があることに注意してください。

上記を定義した後、元の設定、取得、および削除コードは同じです。

obj = Obj()
obj.attribute = value  
the_value = obj.attribute
del obj.attribute

これは避けてください:

def set_property(property,value):  
def get_property(property):  

まず、プロパティが(通常はself)に設定されるインスタンスに次のような引数を指定しないため、上記は機能しません。

class Obj:

    def set_property(self, property, value): # don't do this
        ...
    def get_property(self, property):        # don't do this either
        ...

第二に、これは二つの特別なメソッドの目的を複製し、__setattr__そして__getattr__

3番目に、setattrおよびのgetattr組み込み関数もあります。

setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value)  # default is optional

@propertyデコレータはゲッターとセッターを作成するためです。

たとえば、設定動作を変更して、設定される値に制限を設けることができます。

class Protective(object):

    @property
    def protected_value(self):
        return self._protected_value

    @protected_value.setter
    def protected_value(self, value):
        if acceptable(value): # e.g. type or range check
            self._protected_value = value

一般に、property直接の属性は使用せず、そのまま使用します。

これはPythonのユーザーが期待することです。最小のサプライズのルールに従って、反対の非常に説得力のある理由がない限り、ユーザーが期待することをユーザーに提供するようにしてください。

デモンストレーション

たとえば、オブジェクトの保護された属性が0から100までの整数で、削除されないようにする必要があり、適切なメッセージを使用してユーザーに適切な使用法を通知するとします。

class Protective(object):
    """protected property demo"""
    #
    def __init__(self, start_protected_value=0):
        self.protected_value = start_protected_value
    # 
    @property
    def protected_value(self):
        return self._protected_value
    #
    @protected_value.setter
    def protected_value(self, value):
        if value != int(value):
            raise TypeError("protected_value must be an integer")
        if 0 <= value <= 100:
            self._protected_value = int(value)
        else:
            raise ValueError("protected_value must be " +
                             "between 0 and 100 inclusive")
    #
    @protected_value.deleter
    def protected_value(self):
        raise AttributeError("do not delete, protected_value can be set to 0")

(を__init__参照してself.protected_valueいますが、プロパティメソッドはを参照していself._protected_valueます。これは__init__、がパブリックAPIを介してプロパティを使用し、「保護」されていることを確認するためです)。

そして使い方:

>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0

名前は重要ですか?

はい、そうです.setterそして.deleter元のプロパティのコピーを作成します。これにより、サブクラスは親の動作を変更せずに動作を適切に変更できます。

class Obj:
    """property demo"""
    #
    @property
    def get_only(self):
        return self._attribute
    #
    @get_only.setter
    def get_or_set(self, value):
        self._attribute = value
    #
    @get_or_set.deleter
    def get_set_or_delete(self):
        del self._attribute

これが機能するためには、それぞれの名前を使用する必要があります。

obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'  
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error

これがどこで役立つかわかりませんが、使用、取得、設定、削除のみのプロパティが必要な場合です。おそらく、同じ名前の意味的に同じプロパティに固執するのが最善です。

結論

単純な属性から始めます。

設定、取得、削除に関する機能が後で必要になった場合は、プロパティデコレータを使用して追加できます。

set_...and という名前の関数は避けてくださいget_...。それがプロパティの目的です。


利用可能な機能を複製することに加えて、なぜ独自のセッターとゲッターを書く必要がないのですか?私はそれがPythonicの方法ではないかもしれないことを理解していますが、そうでなければ遭遇するかもしれない本当に深刻な問題がありますか?
user1350191

4
デモでは、__init__メソッドはをself.protected_value参照していself._protected_valueますが、ゲッターとセッターはを参照しています。これがどのように機能するか説明していただけますか?私はあなたのコードをテストしました、そしてそれは現状のまま動作します-したがってこれはタイプミスではありません。
codeforester

2
@codeforester私は以前に私の回答で応答することを望んでいましたが、できるまで、このコメントで十分です。パブリックAPIを介してプロパティが使用され、「保護」されていることを確認してください。プロパティで「保護」し、代わりに非公開APIを使用しても意味がないの__init__でしょうか。
アーロンホール

2
はい、@ AaronHallが取得しました。self.protected_value = start_protected_value実際にはセッター関数を呼び出していることに気付きませんでした。宿題だと思いました。
codeforester 2018

1
私が正しく理解していれば、Pythonはjavaなどとは正反対の点をとります。代わりに、デフォルトでは、民間のすべてを作り、それはPythonで公に必要とされるとき、いくつかの余分なコードを書くのあなたはすべてを公開すると、上、後にプライバシーを追加することができます
idclev 463035818

27
In [1]: class test(object):
    def __init__(self):
        self.pants = 'pants'
    @property
    def p(self):
        return self.pants
    @p.setter
    def p(self, value):
        self.pants = value * 2
   ....: 
In [2]: t = test()
In [3]: t.p
Out[3]: 'pants'
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20

17

使用する@property@attribute.setter、あなたは「神託」の方法を使用するだけでなく、オブジェクトを作成し、それを変更するときながら、両方の属性の有効性を確認するのに役立ちます。

class Person(object):
    def __init__(self, p_name=None):
        self.name = p_name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_name):
        if type(new_name) == str: #type checking for name property
            self._name = new_name
        else:
            raise Exception("Invalid value for name")

これにより、実際に_nameクライアント開発者から属性を「非表示」にし、名前プロパティタイプのチェックも実行します。開始時でもこのアプローチに従うと、セッターが呼び出されます。そう:

p = Person(12)

につながる:

Exception: Invalid value for name

だが:

>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception

16

@propertyデコレータを確認してください。


33
これはほとんどリンクのみの回答です。
アーロンホール


7
これは完全な答えですか?リンクは答えではありません。
Andy_A̷n̷d̷y̷

そこのドキュメントには使用方法が明確に記載されており(Pythonの実装が変更された場合でも最新の状態を保ち、回答者が提案しているメソッドにOPを誘導するため)、それは良い答えだと思います。@Jean-FrançoisCorbettは明確に述べています「どのように」それは完全な答えである。
HunnyBear

いずれにせよ、この回答は他の回答に役立つものを追加するものではないので、理想的には削除する必要があります。
Georgy

5

アクセサ/ミューテータ(つまり@attr.setterand @property)を使用してもしなくてもかまいませんが、最も重要なことは一貫性を保つことです

@property単に属性にアクセスするために使用している場合、例えば

class myClass:
    def __init__(a):
        self._a = a

    @property
    def a(self):
        return self._a

すべての*属性にアクセスするためにそれを使用してください!アクセサなしで@property他のいくつかのプロパティを使用してパブリック(つまり、アンダースコアなしの名前)のままにすることは、悪い例です。

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b

    @property
    def a(self):
        return self.a

self.bパブリックであっても、ここには明示的なアクセサがないことに注意してください。

同様に、セッター(またはミューテーター)でも、自由に使用できます@attribute.setter、一貫性があります。あなたがするとき

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b 

    @a.setter
    def a(self, value):
        return self.a = value

私が君の意図を推測するのは難しい。一方で、abは両方ともパブリック(名前の先頭にアンダースコアがない)であるため、理論的には両方にアクセス/変更(取得/設定)できるようにする必要があります。しかし、それから明示的なミューテーターのみを指定すると、aおそらく設定できないはずbです。明示的なミューテーターを提供したので、明示的なアクセサー(@property)がないということは、これらの変数のいずれにもアクセスできないはずか、単にを使用することに質素だったのかわかりません@property

*例外は、一部の変数を明示的にアクセス可能または変更可能にしたいが両方を変更したくない場合、または属性にアクセスまたは変更するときに追加のロジックを実行したい場合です。これは私が個人的に@propertyand を使用している場合です@attribute.setter(そうでない場合、公開属性の明示的なアクセス権/ミューテーターはありません)。

最後に、PEP8とGoogleスタイルガイドの提案:

PEP8、Designing for Inheritanceは言う:

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

一方、GoogleスタイルガイドのPython言語のルール/プロパティによると、次のことをお勧めします

新しいコードのプロパティを使用して、通常はシンプルで軽量なアクセサーまたはセッターメソッドを使用していたデータにアクセスまたは設定します。プロパティは、@propertyデコレータを使用して作成する必要があります。

このアプローチの長所:

単純な属性アクセスのための明示的なgetおよびsetメソッド呼び出しを排除することにより、可読性が向上します。計算を遅延させることができます。クラスのインターフェースを維持するためのPython的な方法を検討しました。パフォーマンスの面では、直接変数アクセスが妥当な場合、プロパティを許可すると、簡単なアクセサーメソッドが必要なくなります。これにより、インターフェイスを壊すことなく、将来的にアクセサメソッドを追加することもできます。

と短所:

objectPython 2 から継承する必要があります。演算子のオーバーロードのように副作用を隠すことができます。サブクラスでは混乱する可能性があります。


1
私は強く反対します。オブジェクトに15個の属性があり、それをで計算したい場合、@property残りの属性も使用するの@propertyは適切ではないようです。
ケルクレフ

同意する必要があるのは、この特定の属性について何か特定の必要がある場合のみです@property(たとえば、属性を返す前に特別なロジックを実行するなど)。それ以外の場合、なぜ1つの属性を@propery他の属性ではなく装飾するのですか?
Tomasz Bartkowiak

@Quelklefは、投稿の副注(アスタリスク付き)を参照してください。
Tomasz Bartkowiak

ええと...傍注で言及されていることの1つを実行していない場合は、そもそも使用@propertyしてはいけませんよね?getterがreturn this._xで、setterがthis._x = new_xである場合、使用@propertyするのはちょっとばかげています。
Quelklef

1
うーん、たぶん。私は個人的には、それはうまくいかないと言います---それは完全に不必要です。しかし、私はあなたがどこから来ているのかを見ることができます。私はあなたの投稿を「使用するときに最も重要なこと@propertyは一貫している」と言ったと思います。
Quelklef

-1

魔法のメソッド__getattribute__とを使用できます__setattr__

class MyClass:
    def __init__(self, attrvalue):
        self.myattr = attrvalue
    def __getattribute__(self, attr):
        if attr == "myattr":
            #Getter for myattr
    def __setattr__(self, attr):
        if attr == "myattr":
            #Setter for myattr

あることに注意してください__getattr__とは__getattribute__同じではありません。__getattr__属性が見つからない場合にのみ呼び出されます。

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