web-dev-qa-db-ja.com

@propertyデコレータはどのように機能しますか?

組み込み関数propertyがどのように機能するのかを理解したいのですが。私を混乱させているのは、propertyはデコレータとしても使えるということですが、組み込み関数として使われるときだけ引数をとり、デコレータとして使われるときではありません。

この例は ドキュメント :からのものです。

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

    def getx(self):
        return self._x
    def setx(self, value):
        self._x = value
    def delx(self):
        del self._x
    x = property(getx, setx, delx, "I'm the 'x' property.")

propertyの引数は、getxsetxdelx、およびdoc文字列です。

以下のコードではpropertyがデコレータとして使われています。その目的はx関数ですが、上記のコードでは引数に目的関数の場所はありません。

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

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

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

そして、x.setterx.deleterデコレータはどのように作成されますか?私は混乱しています。

783
ashim

property()関数は特別な 記述子オブジェクト を返します。

>>> property()
<property object at 0x10ff07940>

extra メソッドを持つのはこのオブジェクトです。

>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>

これらはデコレータとして機能します too 。それらは新しいプロパティオブジェクトを返します。

>>> property().getter(None)
<property object at 0x10ff079f0>

これは古いオブジェクトのコピーですが、関数の1つが置き換えられています。

@decorator構文は単なる構文上の糖であることを忘れないでください。構文は次のとおりです。

@property
def foo(self): return self._foo

本当に同じことを意味する

def foo(self): return self._foo
foo = property(foo)

それでfoo関数はproperty(foo)に置き換えられます。上で見たものは特別なオブジェクトです。それであなたが@foo.setter()を使うとき、あなたがしているのは私が上であなたに示したproperty().setterメソッドを呼ぶことです、そしてそれはプロパティの新しいコピーを返します、しかし今回はsetter関数で装飾されたメソッドで置き換えられます。

次のシーケンスでも、これらのデコレータメソッドを使用してフルオンプロパティを作成します。

まず、ゲッターを1つだけ使って関数とpropertyオブジェクトを作成します。

>>> def getter(self): print 'Get!'
... 
>>> def setter(self, value): print 'Set to {!r}!'.format(value)
... 
>>> def deleter(self): print 'Delete!'
... 
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True

次に.setter()メソッドを使ってセッターを追加します。

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

最後に.deleter()メソッドを使ってデリミタを追加します。

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

最後になりましたが、propertyオブジェクトは 記述子オブジェクト として機能するため、インスタンス属性の取得、設定、削除に使用する .__get__().__set__() 、および .__delete__() のメソッドがあります。

>>> class Foo(object): pass
... 
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

Descriptor Howtoは 純粋なpythonサンプル実装property()型のものを含みます。

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)
811
Martijn Pieters

ドキュメントによると これは読み取り専用のプロパティを作成するためのショートカットにすぎません。そう

@property
def x(self):
    return self._x

と同等です

def getx(self):
    return self._x
x = property(getx)
121
J0HN

最初の部分は簡単です:

@property
def x(self): ...

と同じです

def x(self): ...
x = property(x)
  • これは、getterだけでpropertyを作成するための単純化された構文です。

次のステップは、このプロパティをsetterとdeleterで拡張することです。そしてこれは適切な方法で起こります:

@x.setter
def x(self, value): ...

古いxと指定されたセッターのすべてを継承する新しいプロパティを返します。

x.deleterは同じように機能します。

71
glglgl

以下は@propertyを実装する方法の最小限の例です。

class Thing:
    def __init__(self, my_Word):
        self._Word = my_Word 
    @property
    def Word(self):
        return self._Word

>>> print( Thing('ok').Word )
'ok'

そうでなければWordはプロパティではなくメソッドのままです。

class Thing:
    def __init__(self, my_Word):
        self._Word = my_Word
    def Word(self):
        return self._Word

>>> print( Thing('ok').Word() )
'ok'
64
AlexG

これは次のとおりです。

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

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

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

と同じです:

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

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, _x_set, _x_del, 
                    "I'm the 'x' property.")

と同じです:

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

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, doc="I'm the 'x' property.")
    x = x.setter(_x_set)
    x = x.deleter(_x_del)

と同じです:

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

    def _x_get(self):
        return self._x
    x = property(_x_get, doc="I'm the 'x' property.")

    def _x_set(self, value):
        self._x = value
    x = x.setter(_x_set)

    def _x_del(self):
        del self._x
    x = x.deleter(_x_del)

これは以下と同じです。

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

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

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x
34
Bill Moore

以下は、 here から取られたコードをリファクタリングしなければならないときの@propertyの助けとなる別の例です(私はそれを以下に要約します)

このようなクラスMoneyを作成したと想像してください。

class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents

そしてユーザは自分が使用するこのクラスに応じてライブラリを作成します。

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

Moneyクラスを変更してdollars属性とcents属性を取り除くことにしましたが、セントの合計金額のみを追跡することにしました。

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

上記のユーザが今までと同じように自分のライブラリを実行しようとした場合

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))

エラーになります

AttributeError: 'Money'オブジェクトには 'ドル'属性はありません

つまり、元々のMoneyクラスに頼っている人全員が、dollarscentsが使用されているすべてのコード行を変更する必要があるということです。 @propertyを使って! 

こうやって:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

    # Getter and setter for dollars...
    @property
    def dollars(self):
        return self.total_cents // 100

    @dollars.setter
    def dollars(self, new_dollars):
        self.total_cents = 100 * new_dollars + self.cents

    # And the getter and setter for cents.
    @property
    def cents(self):
        return self.total_cents % 100

    @cents.setter
    def cents(self, new_cents):
        self.total_cents = 100 * self.dollars + new_cents

私達が今私達の図書館から呼ぶとき

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

それは予想通りに動作するでしょう、そして私たちは私たちのライブラリのコードの一行を変更する必要はありませんでした!実際、依存しているライブラリが変更されたことを知る必要さえありません。

setterもうまく動作します。

money.dollars += 2
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 12 cents.

money.cents += 10
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 22 cents.
18
Cleb

ここですべての記事を読み、実際の例が必要な場合があることに気付きました。なぜ、実際には@propertyがあるのですか。したがって、認証システムを使用するFlaskアプリを検討してください。 モデルのユーザーをmodels.pyで宣言します。

class User(UserMixin, db.Model):
    __table= 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    ...

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

このコードでは、直接アクセスしようとするとpasswordアサーションをトリガーする@propertyを使用して属性AttributeErrorを「隠し」、実際のインスタンス変数password_hashを設定するために@ property.setterを使用しました。

これでauth/views.pyでUserをインスタンス化することができます。

...
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        db.session.commit()
...

ユーザーがフォームに記入するときに登録フォームから来る属性passwordに注意してください。パスワードの確認はフロントエンドでEqualTo('password', message='Passwords must match')で行われます(あなたが疑問に思っている場合は、それはFlaskフォームに関連する別のトピックです)。

この例が役に立つことを願っています 

12
Leo Skhrnkv

Pythonデコレータから始めましょう。

Pythonデコレータは、既に定義されている関数にいくつかの追加機能を追加するのに役立つ関数です。 

Pythonでは、すべてがオブジェクトです。Pythonでは、すべてがオブジェクトです。 Pythonの関数はファーストクラスのオブジェクトなので、変数で参照したり、リストに追加したり、他の関数に引数として渡したりすることができます。

次のコードスニペットを考えてください。

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

def say_bye():
    print("bye!!")

say_bye = decorator_func(say_bye)
say_bye()

# Output:
#  Wrapper function started
#  bye
#  Given function decorated

ここで、デコレータ関数がsay_hello関数を修正し、そこにいくつかのコード行を追加したと言えます。

デコレータのPython構文

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

@decorator_func
def say_bye():
    print("bye!!")

say_bye()

ケース・シナリオよりもすべてをまとめてみましょうが、その前におっとの原則について話しましょう。

ゲッターおよびセッターは、データのカプセル化の原則を保証するために、多くのオブジェクト指向プログラミング言語で使用されています(データをこれらのデータを操作するメソッドとバンドルするものと見なされています)。

これらのメソッドは、もちろんデータを取得するためのゲッターとデータを変更するためのセッターです。 

この原則によれば、クラスの属性は他のコードからそれらを隠し保護するために非公開にされます。

うん、 @property は基本的にゲッターとセッターを使うPythonicの方法です。

Pythonはpropertyと呼ばれる素晴らしい概念を持っています。これはオブジェクト指向プログラマーの生活をもっと簡単にします。

あなたが摂氏温度で温度を保存することができるクラスを作ることにあなたが決めると仮定しよう。 

class Celsius:
def __init__(self, temperature = 0):
    self.set_temperature(temperature)

def to_Fahrenheit(self):
    return (self.get_temperature() * 1.8) + 32

def get_temperature(self):
    return self._temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    self._temperature = value

リファクタリングされたコード、これがプロパティでそれを達成することができた方法です。

Pythonでは、property()はプロパティオブジェクトを作成して返す組み込み関数です。

プロパティオブジェクトには、getter()、setter()、およびdelete()の3つのメソッドがあります。

class Celsius:
def __init__(self, temperature = 0):
    self.temperature = temperature

def to_Fahrenheit(self):
    return (self.temperature * 1.8) + 32

def get_temperature(self):
    print("Getting value")
    return self.temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    print("Setting value")
    self.temperature = value

temperature = property(get_temperature,set_temperature)

ここに、

temperature = property(get_temperature,set_temperature)

に分解された可能性があります、

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

注意すべき点:

  • get_temperatureはメソッドではなくプロパティのままです。

今、あなたは書くことによって温度の値にアクセスすることができます。

C = Celsius()
C.temperature
# instead of writing C.get_temperature()

先に進むと、get_temperatureset_temperatureという名前は不要であり、クラスの名前空間を汚染するので定義しないでください。

上記の問題に対処するための Pythonicの方法 は、 @property を使用することです。

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_Fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Getting value")
        return self.temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self.temperature = value

注意点 -

  1. 値を取得するために使用されるメソッドは、 "@ property"で装飾されています。
  2. セッターとして機能しなければならないメソッドは、 "@ temperature.setter"で装飾されています。関数が "x"と呼ばれていた場合、 "@ x.setter"で装飾する必要があります。
  3. 同じ名前で、 "def temperature(self)"と "def temperature(self、x)"というパラメータの数が異なる2つのメソッドを作成しました。

お分かりのように、コードは間違いなくエレガントさを増しています。

それでは、実際の現実的な場面について話しましょう。

次のようにクラスを設計したとしましょう。

class OurClass:

    def __init__(self, a):
        self.x = a


y = OurClass(10)
print(y.x)

さて、私たちのクラスがクライアントの間で人気が出て、彼らが彼らのプログラムでそれを使い始めたとさらに仮定しよう。彼らはオブジェクトにあらゆる種類の割り当てをした。

そしてある運命の日、信頼できるクライアントが私たちのところにやってきて、 "x"は0から1000の間の値でなければならないと示唆しました、これは本当に恐ろしいシナリオです!

プロパティのせいで簡単です。プロパティバージョンの "x"を作成します。

class OurClass:

    def __init__(self,x):
        self.x = x

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        Elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

これは素晴らしいことです、そうではありません:あなたが想像できる最も簡単な実装から始めることができます、そしてあなたは後でインターフェースを変更する必要なしにプロパティバージョンに自由に移行することができます!だからプロパティは単にゲッターやセッターの代わりになるものではありません! 

あなたはこの実装をチェックすることができます ここ

5
Divyanshu Rawat

この点は多くの人々によって明らかにされていますが、ここで私が探していた直接的な点があります。

class UtilityMixin():
    @property
    def get_config(self):
        return "This is property"

関数 "get_config()"の呼び出しはこのように動作します。

util = UtilityMixin()
print(util.get_config)

あなたが気付くならば、私は関数を呼び出すために "()"括弧を使っていません。これは私が@propertyデコレータを探していた基本的なものです。関数を変数のように使うことができます。

5
Devendra Bhat

property@propertyデコレータの背後にあるクラスです。

あなたはいつでもこれをチェックすることができます:

print(property) #<class 'property'>

例をhelp(property)から書き直して、@property構文が正しいことを示します。

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

    @property 
    def x(self):
        return self._x

    @x.setter 
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

c = C()
c.x="a"
print(c.x)

機能的にはproperty()の構文と同じです。

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

    def g(self):
        return self._x

    def s(self, v):
        self._x = v

    def d(self):
        del self._x

    prop = property(g,s,d)

c = C()
c.x="a"
print(c.x)

ご覧のとおり、このプロパティの使用方法に違いはありません。

質問に答えるために@propertyデコレータはpropertyクラスを介して実装されています。


だから、問題はpropertyクラスを少し説明することです。この行

prop = property(g,s,d)

初期化でした。このように書き直すことができます。

prop = property(fget=g,fset=s,fdel=d)

fgetfsetおよびfdelの意味

 |    fget
 |      function to be used for getting an attribute value
 |    fset
 |      function to be used for setting an attribute value
 |    fdel
 |      function to be used for del'ing an attribute
 |    doc
 |      docstring

次の図は、クラスpropertyから取得したトリプレットを示しています。

enter image description here 

__get____set__、および__delete__は、 オーバーライドされる です。これはPythonの記述子パターンの実装です。

一般に、記述子は「バインディング動作」を持つオブジェクト属性であり、その属性アクセスは記述子プロトコルのメソッドによって上書きされています。

プロパティのsettergetterdeleterメソッドを使って関数をpropertyにバインドすることもできます。次の例を確認してください。クラスCのメソッドs2は、プロパティ doubled を設定します。

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

    def g(self):
        return self._x

    def s(self, x):
        self._x = x

    def d(self):
        del self._x

    def s2(self,x):
        self._x=x+x


    x=property(g)
    x=x.setter(s)
    x=x.deleter(d)      


c = C()
c.x="a"
print(c.x) # outputs "a"

C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x) # outputs "aa"
1
prosti

プロパティは2つの方法で宣言できます。

  • 属性の取得メソッド、設定メソッドを作成し、これらを property functionに引数として渡します
  • @property デコレータを使用する。

pythonのプロパティ について書いたいくつかの例を見ることができます。 

1
nvd

これは別の例です。

##
## Python Properties Example
##
class GetterSetterExample( object ):
    ## Set the default value for x ( we reference it using self.x, set a value using self.x = value )
    __x = None


##
## On Class Initialization - do something... if we want..
##
def __init__( self ):
    ## Set a value to __x through the getter / setter... Since __x is defined above, this doesn't need to be set...
    self.x = 1234

    return None


##
## Define x as a property, ie a getter - All getters should have a default value arg, so I added it - it will not be passed in when setting a value, so you need to set the default here so it will be used..
##
@property
def x( self, _default = None ):
    ## I added an optional default value argument as all getters should have this - set it to the default value you want to return...
    _value = ( self.__x, _default )[ self.__x == None ]

    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Get x = ' + str( _value ) )

    ## Return the value - we are a getter afterall...
    return _value


##
## Define the setter function for x...
##
@x.setter
def x( self, _value = None ):
    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Set x = ' + str( _value ) )

    ## This is to show the setter function works.... If the value is above 0, set it to a negative value... otherwise keep it as is ( 0 is the only non-negative number, it can't be negative or positive anyway )
    if ( _value > 0 ):
        self.__x = -_value
    else:
        self.__x = _value


##
## Define the deleter function for x...
##
@x.deleter
def x( self ):
    ## Unload the assignment / data for x
    if ( self.__x != None ):
        del self.__x


##
## To String / Output Function for the class - this will show the property value for each property we add...
##
def __str__( self ):
    ## Output the x property data...
    print( '[ x ] ' + str( self.x ) )


    ## Return a new line - technically we should return a string so it can be printed where we want it, instead of printed early if _data = str( C( ) ) is used....
    return '\n'

##
##
##
_test = GetterSetterExample( )
print( _test )

## For some reason the deleter isn't being called...
del _test.x

基本的に、C(オブジェクト)の例と同じですが、代わりに xを使用しています。__init - で初期化していません。はクラスの一部として定義されています....

出力は以下のとおりです。

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

init でself.x = 1234をコメントアウトすると、出力は次のようになります。

[ Test Class ] Get x = None
[ x ] None

また、getter関数で_default = Noneを_default = 0に設定した場合(すべてのgetterはデフォルト値を持つはずですが、ここで定義したプロパティ値では渡されないため、ここで定義できます)。デフォルトを一度定義してどこでも使うことができるので、実際にはそれほど悪くありません。例:def x(self、_default = 0):

[ Test Class ] Get x = 0
[ x ] 0

注:ゲッターロジックは、値が操作されることを保証するために値によって操作されるようにするためのものです - printステートメントでも同じです。

注:私はLuaに慣れていて、1つの関数を呼び出すときに動的に10+ヘルパーを作成することができ、プロパティを使用せずにPython用に似たようなものを作成しました。それがそのようにコード化されていないので変わっている...それらが作成される前に呼び出されることに関して時々問題がまだ時々あります。基本的に直接変数にアクセスするのではなく... Pythonを使っていくつかのことをどれだけ早く構築できるかが好きです - 例えばguiプログラム。私が設計しているものは、追加のライブラリがたくさんなければ実現できないかもしれません - それをAutoHotkeyでコーディングすれば、必要なdll呼び出しに直接アクセスでき、Java、C#、C++などでも同じことができます。正しいことがまだ見つかっていませんが、そのプロジェクトではPythonから変更することができます。

注意:このフォーラムのコード出力は壊れています - それを機能させるためにコードの最初の部分にスペースを追加しなければなりませんでした - コピー/ペーストで確実にすべてのスペースをタブに変換してください。ファイルサイズが10,000行のファイルは、スペースで512KBから1MB、タブで100から200KBであり、ファイルサイズの大幅な違いと処理時間の短縮に相当します。

タブはユーザーごとに調整することもできます - したがって、2スペース、4、8など、できることなら何でも好きな人には、視力障害のある開発者にとっては思慮深いことです。

注:フォーラムソフトウェアのバグのため、クラスで定義されているすべての関数が正しくインデントされていません。コピーして貼り付ける場合は必ずインデントしてください。

1
Acecool

私にとっては、Python 2.xでは@propertyがフォームオブジェクトを継承していないときの宣伝どおりに機能しませんでした。

class A():
    pass

しかし、うまくいったとき:

class A(object):
    pass

pyhton 3のために、いつも働いて

0