LoginSignup
75

More than 5 years have passed since last update.

Pythonのクラス定義とインスタンスの扱い

Last updated at Posted at 2017-05-31

クラス定義とインスタンスの扱いついて

こちらで備忘録を兼ねたまとめを作成していた際に、改めて気付いたり、発見したことがあったので、ここで記載したい。

クラス変数とインスタンス変数

はじめに

"Hello World"と出力するサンプル。クラス変数c_varとインスタンス変数i_varをそれぞれ定義。

class Test(object):
    c_var = "Hello"

    def __init__(self):
        self.i_var = "World"

    def __str__(self):
        return Test.c_var + " " + self.i_var

print(Test())

クラス変数とインスタンス変数の検証(その1)

クラス変数と同じ名前のインスタンス変数を定義するパターン(examine1)とクラス変数を変更するパターン(examine2)で検証。

class Test(object):
    c_var = "Hello"

    def __init__(self):
        self.i_var = "World"

    def __str__(self):
        return Test.c_var + " " + self.i_var

    def examine1(self):
        self.c_var = "Hey"
        return self

    def examine2(self):
        Test.c_var = "Hey"
        return self

t = Test()
print(t.examine1())
print(t.examine2())
print(Test())

出力結果から、クラス変数とインスタンス変数は別もので、インスタンス変数は変更可能であることがわかる。

出力結果
Hello World
Hey World
Hey World

なお、vars(t)でインスタンス変数、vars(Test)でクラス変数が確認できる。

クラス変数とインスタンス変数の検証(その2)

今度はメソッドではなく、クラスメソッドとスタティックメソッドで、クラス変数を変更してみる。出力結果から、共に__init__が呼ばれ、またクラス変数が変更されている。

class Test(object):
    c_var = "Hello"

    def __init__(self):
        self.i_var = "World"

    def __str__(self):
        return Test.c_var + " " + self.i_var

    @classmethod
    def class_method(cls):
        cls.c_var = "Class"

    @staticmethod
    def static_method():
        Test.c_var = "Static"

Test.class_method()
print(Test())
Test.static_method()
print(Test())
出力結果
Class World
Static World

クラス変数とインスタンス変数の検証(その3)

今度は浅いコピーと深いコピーをしたときのクラス変数とインスタンス変数の変化について検証してみる。
深いコピーでもクラス変数の値が変更されている。

import copy


class Test(object):
    c_var = 2

    def __init__(self, obj):
        self.i_var = obj

    def __str__(self):
        return "{0} {1}".format(Test.c_var, self.i_var)

    def examine1(self):
        self.i_var += 2
        return self

    def examine2(self):
        Test.c_var += 2
        return self


a = int(3)
t1 = Test(a)
t1.examine1().examine2()
print(t1)
print(Test(a))

print("--- shallow copy ---")

t2 = copy.copy(t1)
t2.examine1().examine2()
print(t1)
print(t2)
print(Test(a))

print("--- deep copy ---")

t3 = copy.deepcopy(t1)
t3.examine1().examine2()
print(t1)
print(t2)
print(t3)
print(Test(a))
出力結果
4 5
4 3
--- shallow copy ---
6 5
6 7
6 3
--- deep copy ---
8 5
8 7
8 7
8 3

メソッドの第1引数self

2つのメソッドexamine1examine2を定義したクラス。

class Test(object):
    def __init__(self):
        self.i_var = "nothing"

    def __str__(self):
        return self.i_var

    def examine1(self):
        self.i_var = "examine1"
        return self

    def examine2(self):
        self.i_var = "examine2"
        return self

t1 = Test()
print(t1)
print(t1.examine1())
print(t1.examine2())

出力結果は以下の通り。

出力結果
nothing
examine1
examine2

ここで、クラスメソッドのようにprint(Test.examin1())を実行したところ、もちろんエラー。

エラー
examine1() missing 1 required positional argument: 'self'

ということは第1引数selfにインスタンスを指定することができるのだろうか。

t1 = Test()
print(Test.examine1(t1.examine2()))
print(Test.examine2(t1.examine1()))

上記を実行すると、下記結果が出力された。

出力結果
examine1
examine2

callableなクラス

callableなクラスの例

まとめでcallableなクラスをご提示いただいて、目から鱗と同時に、自分の勉強不足を痛感してしまった。

class Callable(int):
    def __call__(self, *arguments):
        print(arguments[0])

a = Callable(5)
print(a)
a(3)
出力結果
5
3

上記では、aが関数としても変数としても利用できるということを意味している。aCallableインスタンスの参照値が代入されているので、print(a)で、インスタンス生成時に引数として渡した値が表示される。また、aを関数のように呼び出すと、__call__が呼び出されるので、第一引数の値がprintされる。

callableなクラスの活用例

intをcallableに拡張し、関数呼び出しで第1引数を単位(名)として扱うような実装。int = Callableで標準intクラスを置き換えている。

class Callable(int):
    def __call__(self, unit):
        print("{0} {1}".format(self, unit))

int = Callable
print(int("3") * 4)
int("5")("meters")
int(2017 - 2010)("years old")
12
5 meters
7 years old

ミュータブルな数値オブジェクト

__add____sub__を実装したクラスを用意した。下記のように足しても引いても参照値は変わらない。こちらも標準intを置き換えた。置き換えた後idの値が変化しなくなった。

class MyInt(object):
    def __init__(self, v):
        self.__v = v

    def __add__(self, other):
        self.__v += other
        return self

    def __sub__(self, other):
        self.__v -= other
        return self

a = int(2)
print(id(a))
a = a + 3
print(id(a))
a = a - 5
print(id(a))

int = MyInt

a = int(3)
print(id(a))
a = a + 3
print(id(a))
a = a - 5
print(id(a))
1983644176
1983644272
1983644112
1736248227544
1736248227544
1736248227544

__mul____matmul____truediv____floordiv____mod____divmod____pow____lshift____rshift____and____xor____or__も必要に応じて実装する。

ミュータブルな数値オブジェクトをクラス変数とインスタンス変数で利用する

クラス変数とインスタンス変数の検証(その3)ではintを利用したが、ここで定義したミュータブルな数値オブジェクトを利用して検証してみた。
イミュータブルのときと同様にクラス変数は深いコピーでも値の変更が可能。

import copy


class MyInt(object):
    def __init__(self, v):
        self.__v = v

    def __str__(self):
        return str(self.__v)

    def __add__(self, other):
        self.__v += other
        return self

    def __sub__(self, other):
        self.__v -= other
        return self

int = MyInt


class Test(object):
    c_var = int(2)

    def __init__(self, obj):
        self.i_var = obj

    def __str__(self):
        return "{0} {1}".format(Test.c_var, self.i_var)

    def examine1(self):
        self.i_var += 2
        return self

    def examine2(self):
        Test.c_var += 2
        return self


a = int(3)
t1 = Test(a)
t1.examine1().examine2()
print(t1)
print(Test(a))

print("--- shallow copy ---")

t2 = copy.copy(t1)
t2.examine1().examine2()
print(t1)
print(t2)
print(Test(a))

print("--- deep copy ---")

t3 = copy.deepcopy(t1)
t3.examine1().examine2()
print(t1)
print(t2)
print(t3)
print(Test(a))
出力結果
4 5
4 5
--- shallow copy ---
6 7
6 7
6 7
--- deep copy ---
8 7
8 7
8 9
8 7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
75