Pythonのclassについて

PythonのclassはC++などのOOP(オブジェクト指向プログラミング)とは少し異なります。それはPythonが動的型付け言語だからでもあります。Pythonにはclassに関して、とても簡単な仕組みしか提供されていないので、本当でこれだけでいいのかと思うかも知れませんが、わりとなんとかなるものです。

private members,methods,…

PythonのclassにはC++で言うところのprivateやprotectedという機能はありません。慣習的には変数名や関数名の前に”__”(アンダースコア2つ)をつけてprivateであることを表します。これは、言語機能とは言い難いので、ここではprivateのことは忘れてしまいましょう。

class member , instance member

次に、クラスに属するメンバー変数と、インスタンスに属するメンバー変数について見ていきましょう。

クラスに属するメンバー変数は、そのクラスに1つしか存在しないものです。これはクラスメンバー変数、クラス変数などと呼ばれます。変数であることが明らかな場合は、単にクラスメンバーと呼ぶこともあります。

インスタンスに属するメンバー変数は、各インスタンスそれぞれに存在できます。これはインスタンスメンバー変数、インスタンス変数と呼ばれます。文脈から、インスタンスに属することが明らかである場合は、単にメンバー変数、あるいは単にメンバーと呼ばれることもあります。

class X:
    # class member
    m = 123

    # コンストラクタ
    def __init__(self):
        # instance member
        self.m = 234

クラス変数、インスタンス変数の書くべき場所

classの直下に書いたものがクラス変数であるというのは、Pythonの初心者が必ずハマる罠だと言えるでしょう。

インスタンス変数は、コンストラクタのなかでself.xxx = yyy のように書いてやる必要があります。

コンストラクタのなかで初期化したくない場合はどうすれば良いのでしょうか?いまのところ(Python 3.8)、その方法はPythonには存在しないように思います。

インスタンスからクラス変数にアクセス

C++などのOOP言語をやっていた人は驚くかも知れませんが、インスタンスからクラス変数にアクセスできます。

class X:
    m = 123

x = X()
print(x.m)
# 123

これは、どのような仕組みで可能なのでしょうか?

実は、Pythonのインスタンスには、”__class__”という隠しメンバがあり、これが型名のオブジェクト(?)を持っています。つまり、次のような関係があります。

class X:
    m = 123

x = X()

print(type(x) is X)
# True
print(x.__class__ is X)
# True
print(x.__class__ is type(x))
# True
// このことから X と x.__class__ と type(x)が等価であるとわかる。

“x.m”のように書いたとき、xのインスタンスメンバーのなかに”m”という変数があるか探すのですが、それが見つからない場合、”x.__class__.m”を探しに行くという仕組みになっています。

このような仕組みになっていることで、インスタンスからクラスメンバーにアクセスできます。

class X:
    # public class member
    m = 123

    # コンストラクタ
    def __init__(self):
        # public member
        self.m = 234

x = X()

print(x.m)
# 234

print(X.m)
# 123

print(type(x).m)
# 123

print(x.__class__.m)
# 123

// さきほど見たように、 X と x.__class__ と type(x)が等価なので
// このような書き方ができる。

重複したメンバー変数

クラス変数は、後出し優先です。何も考えずに上から実行されているようにも見えます。

class X:
    m = 1
    m = 2


print(X.m)
# 2

クラスも同様で、後出し優先です。

class X:
    m = 123

print(X.m)
# 123

class X:
    m = 234

print(X.m)
# 234

クラスの宣言も実は、X = type(…)のようなtype型のインスタンスの代入にすぎないので、後から代入したほうが優先(先に宣言したものは隠されてしまう)になります。

typeインスタンスによるclassの宣言

classがtypeインスタンスによって実現されていることを確認しましょう。

class Foo:
    def print_double(self,x):
        print(x*2)

foo = Foo()
foo.print_double(10)
# 20

Foo = type("Foo",(object,),{ "print_double" : lambda self , x : print(x*2)}) 
foo = Foo()
foo.print_double(10)
# 20
// type()の第二引数は基底クラスの羅列をtupleで指定します。基底クラスが特に無いなら、上例のように"(object,)"と書いておきます。

重複するメソッドの宣言

同様にメソッド名が重複している場合、あとから宣言したものが優先されます。このような書き方は、pylintなどでは警告をだしますが、実行は問題なくされます。

class Foo:
    def bar(self):
        print(123)
    def bar(self):
        print(234)

foo = Foo()
foo.bar()
# 234

引数の型や引数の数でメソッドの呼び分け

また、Pythonでは引数の型や引数の数で同名の関数を呼び分けすることはできないので、後から宣言しているほうが呼び出されます。

class Foo:
    def bar(self,x:int):
        print(123)
    def bar(self):
        print(234)

foo = Foo()
foo.bar(100)
#  TypeError : bar() takes 1 positional argument but 2 were given
// あとから宣言されているほうのbar()を呼び出そうとした。
// 呼び出す側と呼び出される側とで引数の数が異なるので実行時のエラーとなった。

(Visited 10 times, 1 visits today)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です