2017年9月18日月曜日

Pythonのプロパティについて

Pythonのプロパティについて調べてみた

Pythonでは、クラス作成時にプロパティを作れる。
まず、プロパティを作らずに、普通にクラスを作ると、



>>>class Position(object):
    def __init__(self, x):
        self.x = x
   

>>> p = Position(2)
>>> p
<__main__ .position="" 0x02cfa870="" at="" object="">
>>> p.x
2

となる。
これに、@propertyをつけてみる、

class Position(object):
    def __init__(self, x):
        self.x = x
    @property
    def x(self):
        return self.x 

とすると、

>>> p = Position(2)
Traceback (most recent call last):
  File "", line 1, in 
    p = Position(2)
  File "", line 3, in __init__
    self.x = x
AttributeError: can't set attribute


となり、インスタンスを作成しようとするとアトリビュートエラーになる。
これは、@propertyデコレータをつけたメソッド(この場合はx)は、ゲッター、セッター、ディレーターがフックされるからである。
メソッドxに@propertyデコレータをつけたことで、インスタンス作成時に__init__が呼ばれ、その中でself.xへの代入を行われる。
この時、フックされたセッターを呼び出そうとするが、セッターが存在しないので、アトリビュートエラーが発生する。
そこで、次のようにセッターを作ってみると、

>>> class Position(object):
 def __init__(self, x):
  self.x = x
 @property
 def x(self):
  return self.x
 @x.setter
 def x(self, x):
  self.x = x
  
>>> p = Position(2)
Traceback (most recent call last):
  File "", line 1, in 
    p = Position(2)
  File "", line 3, in __init__
    self.x = x
  File "", line 10, in x
    self.x = x
  File "", line 10, in x
    self.x = x
  File "", line 10, in x
    self.x = x
  [Previous line repeated 491 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object


となり、今度は再帰呼び出しの上限でエラーになる。
これは、Positionインスタンスに値をセットする際に、__initi__中のself.xへの代入時に、xへのsetterが呼び出される。
そのsetter中で、さらにx.setterが呼び出されて・・・と、再帰呼び出しになってしまうためである。
なので、代入先の名前をself.xからself._xへと変更してやるとうまくいく。

>>> class Position(object):
 def __init__(self, x):
  self._x = x
 @property
 def x(self):
  return self._x
 @x.setter
 def x(self, x):
  self._x = x

  
>>> p = Position(2)
>>> p
<__main__ .position="" 0x02cfa650="" at="" object="">
>>> p.x
2
>>> p._x
2
>>> p.x = 10
>>> p.x
10

こうしてやると、うまくいく。
なお、上記のように、インスタンス中のプロパティを直接呼び出したり代入すことも出来る。
@propertyを使うメリットは、インスタンスのプロパティの参照時、代入時、削除時の挙動を変更することである。
また、次に示すが、プロパティへのアクセスは、p.getx()のようなメソッド呼び出しだと括弧が必要だが、@propertyを使うと、p.xとなり見やすくなる。
次のようにゲッター、セッター、 ディレーターを普通のメソッドとして実装すると、


>>> class Position(object):
        def __init__(self, x):
            self._x = x
        def setx(self, x):
            self._x = x
        def getx(self):
            return self._x
        def delx(self):
            del self._x

>>> p = Position(2)
>>> p.getx()
2
>>> p._x
2
>>> p.getx()
2
>>> p.setx(10)
>>> p.getx()
10
>>> p.delx()
>>> p.getx()
Traceback (most recent call last):
  File "<pyshell#252>", line 1, in <module>
    p.getx()
  File "<pyshell#244>", line 7, in getx
    return self._x
AttributeError: 'Position' object has no attribute '_x'

また、プロパティに変更があったとき、保持する値や他の値も自動的に更新したい時などにも使える。

>>> class Square(object):
 def __init__(self, x):
  self._x = x * x
 @property
 def x(self):
  return self._x
 @x.setter
 def x(self, x):
  self._x = x * x

  
>>> s = Square(4)
>>> s
<__main__ .square="" 0x02d6d390="" at="" object="">
>>> s.x
16
>>> s.x = 5
>>> s.x
25

0 件のコメント:

コメントを投稿

【和訳】Django Rest Framework 目次

目次 【和訳】Django Rest Framework クイックスタート 【和訳】Django Rest Framework チュートリアル1:シリアル化 【和訳】Django Rest Framework チュートリアル2:リクエストとレスポンス 【和訳】Django Res...