PythonのClassの生成に関してまとめてみた
TL;DR
Pythonのクラス、色々ありすぎて難しいのでまとめます。まずはClassの生成についてです。
Classの生成
- まず、
__new__
メソッドが呼ばれ、その後__init__
メソッドが呼ばれる。 __new__
メソッド内では、クラス変数の定義などが行われる。__init__
メソッド内では、インスタンス変数が定義できる。- クラス変数は、クラスから作成されたインスタンス全てが利用できる変数で、インスタンス変数はそれぞれのインスタンスからのみ利用できる変数。
- クラス変数はインスタンスが生成されていなくても使えて、クラス変数を利用するメソッドには
@classmethod
のデコレータを付けて明示する。 - 生成されたインスタンスは
self
、クラスそのものはcls
と慣用的に命名される。
__new__
と__init__
の挙動確認
# -*- coding: utf-8 -*-
class Name:
class_name = None # クラス変数
def __new__(cls, name):
print("new:", str(id(cls)))
return super().__new__(cls)
def __init__(self, name):
print("==init==")
print("id:", str(id(self)), "name:", name)
self.name = name # インスタンス変数
@classmethod
def set_class_name(cls, class_name):
cls.class_name = class_name
@classmethod
def print_class_name(cls):
print(cls.class_name)
@staticmethod
def print_hi():
print("hi!")
のようにクラスを定義します。@staticmethod
はクラス変数にも生成されたインスタンス(cls
, self
を引数に取らない)メソッドのデコレータで、このメソッドもインスタンスが生成されてなくても使えます。
以下の様に、@classmethod
や@staticmethod
で定義されている部分については、インスタンスを生成しなくても使えていることがわかります。
Name.print_class_name()
Name.set_class_name("A")
Name.print_class_name()
Name.print_hi()
# None
# A
# hi!
次にインスタンスの生成過程を見てみます。
john = Name("John")
mike = Name("mike")
# new: 25652888
# ==init==
# id: 140347266309760 name: John
# new: 25652888
# ==init==
# id: 140347266309816 name: mike
インスタンスが生成された後のIDを見ると、__new__
で生成されているIDは同じでクラス自身が生成されています。また、__init__
で生成されているIDは異なっていて、違うインスタンスが生成されていることがわかります。
さらに、クラス変数を変更した場合の挙動を確認します。
print("classname:", john.class_name, "name:", john.name)
print("classname:", mike.class_name, "name:", mike.name)
print("-" * 10)
mike.set_class_name("B")
print("classname:", john.class_name, "name:", john.name)
print("classname:", mike.class_name, "name:", mike.name)
# classname: A name: John
# classname: A name: mike
# ----------
# classname: B name: John
# classname: B name: mike
どちらかの側でクラス変数を変更すると、両方のインスタンスでクラス変数が変更されていることがわかります。
__new__
は何に使うのか
__new__
の使用例としては、
-
そのクラスが何回呼び出されたのかを記録する
-
tupleなどのimmutableなオブジェクトの初期化
-
メタクラスを利用してクラスを切り替える
などが挙げられます。公式的には、2,3が想定用途っぽいです (参考: 3. データモデル)。
__new__()
の主な目的は、変更不能な型 (int, str, tuple など) のサブクラスでインスタンス生成をカスタマイズすることにあります。また、クラス生成をカスタマイズするために、カスタムのメタクラスでよくオーバーライドされます。
動的なクラス定義
type
がobject
の引数1つを取った場合、object.__class__
を返します。これによって、以下のようにクラスを判別することができます。
>>> n = 1
>>> type(n)
<class 'int'>
>>> n.__class__
<class 'int'>
クラスの生成のためには、name, bases, dict
の3つの引数を取ります。これらの値は__name__, __bases__, __dict__
に対応しています。__bases__
は継承で、()
or (object,)
が最も基本的なクラスです。通常のクラス生成との対応は以下です。
def __init__(self, name):
self.name = name
A = type('A', (), dict(__init__=__init__, a='1'))
# 上と同義
class A:
a = '1'
def __init__(self, name):
self.name = name
print(A)
# <class '__main__.A'>
print(A("NAME").name)
# "NAME"
type(A)
# <class 'type'>
つまり、通常のクラス生成は__new__
内でtype
のインスタンス化が行われていた、ということです。ここで重要なのは、type
はclass
です(参考: 組み込み関数)。この辺りの関係性はPythonのオブジェクトとクラスのビジュアルガイド – 全てがオブジェクトであるということなどが参考になります。
なので、type
を継承したクラスを使って__new__
内でそのクラスを呼び出せば、クラスの生成を動的にカスタマイズできます。この概念がメタクラスです。