Programmer's Gate / Effective Python /プロパティとクラスメソッド

1   プロパティ

C#にプロパティというものがある。例えばリストLがあったときに リストの長さをL.length()やL.size()でなく、L.lengthやL.sizeで書けたら 便利である。しかし、メソッドでなく、変数で実現すると、例えばエラーチェック などの処理を入れることができない。構文は変数で、機能的にはメソッドの方式を 利用できるものを実現するのがプロパティである。

>>> class Foo(object):
...     def __init__(self):
...         self.__x = None
...     def getx(self):
...         print 'getx() is called.'
...         return self.__x
...     def setx(self, val):
...         print 'setx() is called.'
...         self.__x = val
...     x = property(getx, setx, None, 'property of x')
...
>>> f = Foo()
>>> f.x = 1
setx() is called.
>>> f.x
getx() is called.
1

また、propertyは新スタイルクラスのみで対応しているので、新スタイルクラスで定義する必要がある。

上記のサンプルは通常の方法だと思うが、Fooクラスに対してgetx、setxという名前を追加してしまう。 これを回避するには、以下のようにする。

>>> class Foo2(object):
...     def __init__(self):
...         self.__x = None
...     def x():
...         doc = 'property of x'
...         def fget(self):
...             return self.__x
...         def fset(self, value):
...             self.__x = value
...         return locals()
...     x = property(**x())
...
>>> set(dir(Foo2)) - set(dir(Foo))
set(['getx', 'setx'])

x = property(**x()) の部分は、以下のようにデコレータを用いても良い。

>>> class Foo2(object):
...     def __init__(self):
...         self.__x = None
...     def nested_property(c):
...         return property(**c())
...     @nested_property
...     def x():
...         doc = 'property of x'
...         def fget(self):
...             return self.__x
...         def fset(self, value):
...             self.__x = value
...         return locals()

但し、逆に Foo に nested_property という名前が追加されてしまう。

ここで、self.xでなくself.__xとしているのは、 データ・マングリング という 機能を使用しているためで、外側から直接self.__xでアクセスできないようにしている。

>>> f.__x
Traceback (most recent call last):
  
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__x'
>>> f._Foo__x
1

このようにアンダーバーを2つ重ねて変数を書くと、この場合では、__xは クラスFooの中で自動的に、_Foo__xに変換されてクラスの外側からは隠される (逆に言えば、f._Foo__xでアクセスできる)。

2   クラスメソッド

インスタンスメソッドでなく、クラスメソッドを定義するには以下の方法を用いる。

>>> class Foo:
...     @staticmethod
...     def foo1():
...         print 'staticmethod'
...     @classmethod
...     def foo2(cls):
...         print 'classmethod', cls
...
>>> Foo().foo1()
staticmethod
>>> Foo().foo2()
classmethod __main__.Foo

@は デコレータ と呼ばれているもので、 上記の場合、foo1 = staticmethod(foo1)、foo2 = classmethod(foo2)を意味する。 つまり、関数のラッパーとして機能する単なるシンタックスシュガーである。


参考文献:

  1. 『Python Cookbook 2nd Edition』; 20. Descriptor, Decorators, and Metaclasses; 20.2 Coding Properties as Nested Functions