晴耕雨読

working in the fields on fine days and reading books on rainy days

[Python] オブジェクト指向プログラミング

Python3におけるオブジェクト指向のまとめ

目次

クラスの定義

  • class というキーワードでクラスを宣言
  • クラス名() でインスタンスの生成

Python3

class Spam:
    pass

spam = Spam()

コンストラクタ、フィールド、メソッド

  • コンストラクタは __init__ メソッドに定義する
  • インスタンス変数の名前は self. から始まる
  • インスタンス変数はデフォルトで public
  • メソッドはデフォルトで public
  • メソッドは必ず引数に self をとる
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def add(self, other) -> 'Point':
        return Point(self.x + other.x, self.y + other.y)

    def __str__(self):
        return "({}, {})".format(self.x, self.y)

クラスメソッド、クラス変数

  • クラスメソッドを定義するには @classmethod の直下でメソッドを定義します
  • クラスメソッドは、クラスからだけではなく、インスタンスからも呼び出すことが可能です
  • クラス変数は、クラスのトップスコープで変数を宣言します
  • クラス変数を参照する時は、クラス名.クラス変数名
class User:
    # クラス変数
    user_count = 0

    # クラスメソッド
    @classmethod
    def get_user_count(klass):
        return klass.user_count

    def __init__(self):
        User.user_count += 1


User()
print(User.get_user_count())   # => 1
User()
User()
print(User().get_user_count()) # => 4

スタティックメソッド

クラスメソッドに良く似たものとして、スタティックメソッドがあります。 クラスメソッドとスタティックメソッドの違いは、「自身のクラスへの参照ができるかどうか」です。

  • クラスメソッドは、クラス変数などを参照するときに使う
  • スタティックメソッドは、クラス変数などを参照しないときに使う
# 引数の数の違い

class A:
    @classmethod
    def class_method(klass, arg1, arg2):
        # クラス変数などを参照する場合は、classmethod を使う
        pass

    @staticmethod
    def static_method(arg1, arg2):
        # クラス変数などに依存しないメソッドは、staticmethod を使う
        pass

そのクラスの全てのメソッドをスタティックメソッドにすることで、 クラスをただの名前空間として扱うこともできます。

class Math:
    @staticmethod
    def sqrt(x):
        """Returns the non-negative square root of x."""
        pass

    @staticmethod
    def pow(x, n):
        """Raises x to the power of n."""
        pass


Math.sqrt(4)
Math.pow(2, 10)

アクセス権

Pythonでは、変数の先頭などにアンダースコア「_」をつけてカプセル化に近いことはできますが、 本当の意味でカプセル化することはできません。

Pythonには ネームマングリング(name mangling)というものがあり、 アンダースコア2つで始まるメンバを、外からアクセスする場合は次のようにアクセスする必要があります。 これによって private に近いことを実現しています。

class Person:
    def __init__(self, name):
        self.__variable = name

    def __method(self):
        return "Person's private method"


alice = Person("Alice")
print(alice._Person__method())
print(alice._Person__variable)

また、いろんな人のコードを見るとアンダースコアの数が1つと2つの場合(_variable__variable) がありますが、これらの使い分けの基準は次のような感じです。

  • _variable は書き換えられたくない変数に用いる(読み取りはOK)
  • __variable は読み書きされたくない変数に用いる

オーバーロード

  • Pythonではオーバーロードはできないが、デフォルト引数を取ることはできる
class A
    def method(self, a, b=None):
        if b is None:
            # ...
            pass
        else
            # ...
            pass

演算子の定義

Pythonで定義できる演算子一覧

演算子に対応するメソッドは __***__ の形式になります。 累積代入文に対応するメソッドは __i***__ の形式になります。

演算子 対応メソッド 演算子 対応メソッド
+ __add__(self, other) += __iadd__(self, other)
- __sub__(self, other) -= __isub__(self, other)
* __mul__(self, other) *= __imul__(self, other)
/ __truediv__(self, other) /= __itruediv__(self, other)
// __floordiv__(self, other) //= __ifloordiv__(self, other)
% __mod__(self, other) %= __imod__(self, other)
** __pow__(self, other) **= __ipow__(self, other)
« __lshift__(self, other) «= __ilshift__(self, other)
» __rshift__(self, other) »= __irshift__(self, other)
& __and__(self, other) &= __iand__(self, other)
^ __xor__(self, other) ^= __ixor__(self, other)
__or__(self, other) |= __ior__(self, other)

演算子の左辺が演算子を定義していない場合は、演算子の右辺の __r***__ メソッドが呼び出されます。

__radd__, __rsub__, __rmul__, …(省略)

単項演算子に対応するメソッドは次の通りです。

演算子 対応メソッド
+(単項演算子) __pos__(self)
-(単項演算子) __neg__(self)
^(単項演算子) __invert__(self)

継承、オーバーライド

  • 継承は class Sub(Super):
  • 多重継承は class Sub(Super1, Super2, ...) とすることで可能(メソッド探索は必ずしも幅優先探索になるとは限らない)
  • 同名のメソッドを定義すれば、オーバーライドになる
  • 親クラスを呼び出すときは super() を使う
class Super:
    def foo(self):
        print("Super's method foo()")

class Sub(Super):
    def foo(self):
        print("Sub's method foo()")
        super().foo()


sub = Sub()
sub.foo()
# => "Sub's method foo()"
# => "Super's method foo()"
Python2で親クラスのメソッドのオーバーライドは、親クラスが object クラスを継承していないとできません。 Python3では全てのクラスが object クラスを継承しているため、この問題は起こりません。

関数デコレータ

関数の宣言の前に @ から始まる関数名を書くことができます。これを 関数デコレータ といいます。 @staticmethod@classmethod も関数デコレータの一つです。

class Spam:
    @staticmethod
    def method(arg):
        pass

上のコードは、下のコードと同じ意味を持ちます。

class Spam:
    def method(arg):
        pass

    method = staticmethod(method)

関数デコレータは複数設定することもできます。

@A
@B
@C
def f(arg):
    pass

上のコードは、下のコードと同じ意味を持ちます。

def f(arg):
    pass

f = A(B(C(f)))

関数デコレータの定義

関数デコレータはクラスとして定義します。

  • __init__ では受け取る関数をインスタンス変数にセットします
  • __call__ では呼び出されたときに行うデコレート処理を書きます
class html_p_tag:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args):
        return "<p>" + self.func(*args) + "</p>"


# 関数デコレータの適用
@html_p_tag
def content(text):
    return text

print(content("hello, world!"))
# => "<p>hello, world!</p>"

関数も呼び出し可能(Callable)なオブジェクトなので、関数を使って関数デコレータを作ることも可能です。

def html_p_tag(f):
    def new_func(*args):
        return "<p>" + f(*args) + "</p>"
    return new_func


# 関数デコレータの適用
@html_p_tag
def content(text):
    return text

print(content("hello, world!"))
# => "<p>hello, world!</p>"

部分適用 を行うことで、関数デコレータに引数を渡すこともできます。

  • __init__ では受け取る引数をインスタンス変数にセットします
  • __call__ では __init__ で受け取った引数を元に新しい関数を返します。その新しい関数の中にデコレート処理を書きます
class html_tag:
    def __init__(self, tag_name):
        self.tag_name = tag_name

    def __call__(self, func):
        def new_func(*args):
            return "<{0}>{1}</{0}>".format(self.tag_name, func(*args))
        return new_func


# 関数デコレータの適用
@html_tag("div")
@html_tag("p")
def content(text):
    return text

print(content("hello, world!"))
# => "<div><p>hello, world!</p></div>"