ラベル クラス の投稿を表示しています。 すべての投稿を表示
ラベル クラス の投稿を表示しています。 すべての投稿を表示

2016年3月28日

クラスメソッド

Python にはクラスメソッドという概念があります。
こちらでスタティックメソッドを紹介しましたが、これをもう少し進めた概念になります。


クラスメソッドは、クラス内に定義したメソッドに @classmethodデコレータを付けることで定義します。
この時、第一引数にはそのクラスの型が渡されます(慣習的に引数名は cls にする)。

# クラスメソッドを持つクラスを定義
>>> class Sample(object):
...     def __init__(self, string):
...         self.string = string
... 
...     # クラスメソッドを定義
...     @classmethod
...     def create_from_file(cls, filename):
...         return cls(open(filename).read())
... 

# クラスメソッドの呼び出し
>>> s = Sample.create_from_file('text.txt')
>>> type(s)
<class '__main__.Sample'>

クラスメソッドとスタティックメソッドはかなり似ている機能です。唯一の違いは、クラスメソッドの「第一引数は必ず型である」という点です。この違いが重要となるのは、クラスメソッドを持つクラスを継承した場合です。

# クラスメソッドを持つクラスを継承
>>> class Derived(Sample):
...     pass
... 

# 継承先でクラスメソッドの呼び出し
>>> d = Derived.create_from_file('text.txt')
>>> type(d)
<class '__main__.Derived'>
上の例では、継承先で creat_from_file() を再定義しなくても、Derivedクラスのインスタンスを取得できています。
スタティックメソッドでこのような動作をさせるためには、継承先でスタティックメソッドをオーバーライドする必要があります。

2016年3月21日

クラスのスタティックメソッド

Python でクラスのスタティックメソッドを定義するには、専用の @staticmethodデコレータを使います。
ここでは @staticmethodデコレータの使い方を紹介します。


スタティックメソッドとして定義したいメソッドの前に @staticmethodデコレータを付けることで、スタティックメソッドとして定義できます。
通常のクラス内メソッドは第一引数を self にしますが、スタティックメソッドでは self は不要です。
定義したスタティックメソッドは、クラス名からでもインスタンスからでも呼び出し可能です。

# extension()をスタティックメソッド
# として持つクラスを定義。
>>> class Png(object):
...     @staticmethod
...     def extension():
...         return 'png'
...

# クラス名から呼ぶ
>>> Png.extension()
'png'

# インスタンスから呼ぶ
>>> png = Png()
>>> png.extension()
'png'

多くの場合、スタティックメソッドは通常の関数で置き換え可能です。スタティックメソッドを使うのは、以下のような場合になります。

  • スタティックメソッドの内容がクラスと密接に結びついており、クラス内に定義した方が見通しがよくなる。
  • インスタンス化にコストが掛かるので、インスタンス化しなくても呼び出し可能としたい。

2016年1月18日

selfのトリビア

クラスメソッドの self に関するちょっとしたトリビアを紹介します。

Python ではクラスメソッドの第一引数は self にします。
クラスメソッドを定義する度に当たり前のように self を書きますが、実はこの self、必須ではありません。
Python の文法的には、「クラスのインスタンスからメソッドを呼び出した時、メソッドの第一引数がインスタンス自身になる」と定められているだけで、第一引数を self とするのはあくまで慣習です。

# selfは予約語ですらない
>>> import keyword
>>> 'self' in keyword.kwlist
False

というわけで、self を別の単語に変えても問題なく動作します。
# selfの代わりにthis
>>> class Sample(object):
...     def __init__(this):
...         this.sample = 10
...     
...     def func(this):
...         print(this.sample)
... 

# 問題なく動作する
>>> sample = Sample()
>>> sample.func()
10

また、第一引数を self ではなく通常の引数として使うことも可能です。この場合、クラスの外から クラス名.メソッド名 と呼ぶことで、c++ や Java で言うところの static関数のような使い方ができます(このような使い方のための staticmethod デコレータもあります)。
# selfの無いクラスメソッド
>>> class Sample(object):
...     def func(x, y):
...         print(x + y)
...

# クラス名が名前空間のようになる
>>> Sample.func(2, 3)
5


ここまで普段はあまり意識しない self の性質について述べてきました。
しかし! クラスメソッドの第一引数を self にするというのは、Python での絶対的な慣習です。あえてこれを破る積極的な理由は見つかりません。
というわけで、本項で紹介したテクニックは、実際には使わない方が賢明です。

2015年12月24日

ベンチマーク10 __dict__

クラスのアトリビュートへのアクセス速度について、__dict__ を使う方法と、setattr() / getattr() を使う方法の、ベンチマークを計測します。


まず、アトリビュートに値を代入する場合について、ベンチマークを計測します。
ベンチマークのソースコードです。

from benchmarker import Benchmarker

with Benchmarker(10000000, cycle=3, extra=1) as bench:
    class Sample(object):
        def __init__(self):
            self.sample = 0
    s = Sample()

    @bench("__dict__")
    def _(bm):
        for _ in bm:
            s.__dict__['sample'] = 1

    @bench("setattr")
    def _(bm):
        for _ in bm:
            setattr(s, 'sample', 1)

計測結果です。
## benchmarker:         release 4.0.1 (for python)
## python version:      3.4.0
## python compiler:     MSC v.1600 32 bit (Intel)
## python platform:     Windows-8-6.2.9200
...

## Ranking    real
__dict__    5.6670  (100.0) ********************
setattr     8.3321  ( 68.0) **************
__dict__ を用いた方が高速です。


続いて、アトリビュートの値を取得する場合について、ベンチマークを計測します。
ベンチマークのソースコードです。

from benchmarker import Benchmarker

with Benchmarker(10000000, cycle=3, extra=1) as bench:
    class Sample(object):
        def __init__(self):
            self.sample = 0
    s = Sample()

    @bench("__dict__")
    def _(bm):
        for _ in bm:
            s.__dict__['sample']

    @bench("getattr")
    def _(bm):
        for _ in bm:
            getattr(s, 'sample')

計測結果です。
## benchmarker:         release 4.0.1 (for python)
## python version:      3.4.0
## python compiler:     MSC v.1600 32 bit (Intel)
## python platform:     Windows-8-6.2.9200
...

## Ranking                real
__dict__                5.4146  (100.0) ********************
getattr                 7.1281  ( 76.0) ***************
こちらも、__dict__ を用いた方が高速となりました。


結果より、__dict__ を用いる方が setattr() / getattr() が高速でした。この結果だけを見ると、__dict__ を使いたくなってしまいます。
しかし! こちらで述べたように、__dict__ はクラスのインターフェースを考慮せずに内部情報に直接アクセスします。所構わず使っていると、良くない結果に繋がりかねません。
__dict__ を使うのは速度が必要になった時の最終手段、ということを忘れないようにしましょう。

2015年12月23日

ベンチマーク9 __slots__

クラスに __slots__ を定義する場合としない場合におけるアトリビュートへのアクセス速度について、ベンチマークを計測します。
__slots__ についてはこちらを参照下さい。


ベンチマークのソースコードです。__slots__ を定義する場合と定義しない場合のそれぞれについて計測します。

from benchmarker import Benchmarker

with Benchmarker(10000000, cycle=3, extra=1) as bench:
    @bench("no slots")
    def _(bm):
        class Sample(object):
            def __init__(self):
                self.sample = 0

        s = Sample()
        for _ in bm:
            s.sample = 1

    @bench("use slots")
    def _(bm):
        class Sample(object):
            __slots__ = ['sample']
            def __init__(self):
                self.sample = 0

        s = Sample()
        for _ in bm:
            s.sample = 1

計測結果です。
## benchmarker:         release 4.0.1 (for python)
## python version:      3.4.0
## python compiler:     MSC v.1600 32 bit (Intel)
## python platform:     Windows-8-6.2.9200
...

## Ranking    real
use slots   3.8496  (100.0) ********************
no slots    4.3632  ( 88.2) ******************
__slots__ を定義する方が高速になりました。
__slots__ を定義しない通常のクラスでは、アトリビュートの追加・削除に備えるため dict を使ってアトリビュートを管理します。dict の処理が必要な分、アトリビュートの追加のないクラスよりも動作が遅くなったと考えられます。

結論としては、__slots__ を定義することで、クラスのサイズを小さくしつつ、さらにアトリビュートへのアクセスも速くすることができる、ということになります。
もちろん、アトリビュートの追加ができなくなるという副作用は忘れてはなりません。

2015年12月22日

__dict__は原則使わないこと

クラスの __dict__ に関する注意点を述べます。


クラスのアトリビュートには、通常の class.attribute というアクセス方法以外にも、class.__dict__['attribute'] というアクセス方法もあります。

>>> class Test(object):
...     def __init__(self):
...         self.test1 = 1
...
>>> t = Test()

# 通常のアクセス
>>> t.test1
1

# __dict__を介してのアクセス
>>> t.__dict__['test1']
1

# アトリビュートの追加も可能
>>> t.__dict__['test2'] = 2
>>> t.test2
2
__dict__ にはクラスの全アトリビュートが dict 形式で格納されています。dict のインターフェースでアトリビュートへアクセスできるというのは、かなり便利な機能です。特に、アトリビュートの追加・削除が多いクラスでは、getattr() / setattr() を使うよりも記述が簡単になることが多いです。


しかし! この __dict__ は原則として使うべきではない機能です。理由は2つあります。

  1. __slots__ を定義しているクラスでは使えない
  2. __dict__ は内部情報に直接アクセスし、クラスのインターフェースを無視する

1.の __slots__ についてはこちらを参照下さい。
外部定義のクラスを使う場合には、たとえ今は __slots__ が定義されていなくても、次のバージョンアップで __slots__ が定義されるかもしれません。このリスクを考えると、外部定義のクラスに対しては __dict__ を使えなくなるはずです。


2.についてですが、例えば以下のようなクラスがあったとします。

class FixInt(object):
    # 固定のint値を保持
    def __init__(self, fixed):
        self.__fixed = int(fixed)

    # getter
    @property
    def fixed(self):
        return self.__fixed
この FixIntクラスは、Read Only 属性のアトリビュート __fixed を持っています。このクラスの fixed へのアクセスについて、getattr() と __dict__ を比較します。
>>> f = FixInt(10)

# getattr()でfixedにアクセス
>>> getattr(f, 'fixed')
10

# __dict__でfixedにアクセス
>>> f.__dict__['fixed']
Traceback (most recent call last):
  File "<stdin>", line 1, in 
KeyError: 'fixed'
getattr() では 'fixed' にアクセスできたのに、__dict__ では 'fixed' にアクセスできませんでした。
これは、getattr() は定義されているクラスのインターフェース(即ち、クラス設計者の意図)に従っているのに対し、__dict__ は内部情報に直接アクセスしようとしている、ということを意味します。当然、getattr() の方が望ましいアクセス方法です。



そんな __dict__にも、一点大きな長所があります。それは、getattr() / setattr() よりも動作速度が速いということです。
よって、満足のいく動作速度が得られない場合には、__dict__ を使うのはありだと思います。
しかし、これは最終手段であり、上で述べた使うべきでない2つの理由を頭に入れた上で、それでも使いたいという場合にのみ使うべきと思います。

2015年12月21日

__slots__で軽量化

クラスの先頭で __slots__ を定義することで、クラスを軽量・高速にすることができます。


以下は、一般的なクラス定義です。

>>> class Point(object):
...     def __init__(self):
...         self.x = 0
...         self.y = 0
...
# クラスのサイズを取得
>>> import sys
>>> sys.getsizeof(Point)
492
このクラスのサイズは 492バイトとなりました。

続いて、クラスの先頭で __slots__ を定義します。__slots__ はリスト(or タプル)で、各要素は使用するアトリビュート名とします。

>>> class Point(object):
...     __slots__ = ['x', 'y']  # __slots__を定義
...
...     def __init__(self):
...         self.x = 0
...         self.y = 0
...
# クラスのサイズを取得
>>> sys.getsizeof(Point)
428
クラスのサイズは 428バイトになりました。
この例では __slots__ を定義することで 64バイト減ったことになります。インスタンスが多数ある場合には(100万個とか)、決して小さくはない減少量と言えます。


ただし、__slots__ には副作用もあります。
通常のクラスではアトリビュートを後から追加することができますが、__slots__ を定義した場合には、__slots__ の要素のアトリビュートしか持てないようになります。

# __slot__を定義したクラスのインスタンス
>>> p = Point()

# zを追加できない
>>> p.z = 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Point' object has no attribute 'z'

# setattrでもできない
>>> setattr(p, 'z', 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in 
AttributeError: 'Point' object has no attribute 'z'

一方、__slots__ を定義することで、アトリビュートの追加が出来ない代わりに、アトリビュートへのアクセスが高速化されるという利点もあります。



結局、__slots__ を使うべき場面とは、以下の2つを共に満たす場合になります。

  • アトリビュートの追加がないクラス
  • 多数のインスタンスが予想されるクラス、又は、動作速度が非常に重要なクラス