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__の使用例としては、

  1. そのクラスが何回呼び出されたのかを記録する

  2. tupleなどのimmutableなオブジェクトの初期化

  3. メタクラスを利用してクラスを切り替える

などが挙げられます。公式的には、2,3が想定用途っぽいです (参考: 3. データモデル)。

__new__() の主な目的は、変更不能な型 (int, str, tuple など) のサブクラスでインスタンス生成をカスタマイズすることにあります。また、クラス生成をカスタマイズするために、カスタムのメタクラスでよくオーバーライドされます。

動的なクラス定義

typeobjectの引数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のインスタンス化が行われていた、ということです。ここで重要なのは、typeclassです(参考: 組み込み関数)。この辺りの関係性はPythonのオブジェクトとクラスのビジュアルガイド – 全てがオブジェクトであるということなどが参考になります。

なので、typeを継承したクラスを使って__new__内でそのクラスを呼び出せば、クラスの生成を動的にカスタマイズできます。この概念がメタクラスです。

この記事に関するIssueをGithubで作成する

Read Next