Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Pythonのselfはなぜ必要なのかをじっくり考察してみた

※5/24に、より良い記載方法を思いついたので追記しています。

クラスメソッドの第一引数であるselfとは

クラスの勉強をする際に、初めのとてつもなく大きな壁になるのがなんでメソッドの引数に"selfを使わなければいけないのか"というところだと思う。

ルールだから入れましょうということで納得させる場合も多いと思うが、しっかりと構造を理解して先に進めたい初学者にとってはとうてい納得できるものではないと思う。

私自身もPythonの勉強を始めたばかりではあるが、seltの使い方でついに学習が止まってしまった。
「なぜ使うのかちゃんと教えてくれ!!」と思ってしまい、モヤモヤしたままで進めたくないからである。

恐らくは、ルールですということで先に進めてもそれほど苦労することは無い気がするが、やはりちゃんと理解してすすめたいので、学習内容をまとめてみた。
私も初学者のひとりなので、できる限り初学者の方が理解できるように記載内容を努力してみた。

今回の学習にあたり、このサイトの説明が一番納得できるものであったので、参考にしてまとめてみた。

selfを入れないと本当にダメかを確認する

まず、selfなんていらないんじゃないかと思ったので、selfを利用せずにソースを書いてみる。
※initの使い方は省略

class Person:
    def __init__(name):
        p_name = name

    def introduce():
        print("私は" + p_name + "です。")

player = Person("太郎")
player.introduce()

①インスタンス「player」を作る際に、引数"太郎"をクラスに渡す
②コンストラクタ(初期化/initでの処理)により、p_name変数に"太郎"が代入される
③introduceメソッドによって、「私は太郎です。」と出力される(はず)

これを実行してみると結果はこうなる。

Traceback (most recent call last):
  File "Main.py", line 25, in <module>
    player = Person("太郎")
TypeError: __init__() takes 1 positional argument but 2 were given
(Exit status: 1)

処理は間違っていないように思えるが、なぜかエラーが出ている。。。
player = Person("太郎")
の処理においてエラーが発生しているようである。ちゃんとクラスに引数を渡しているはずだが。

ただし、ここでエラーメッセージを見てみると面白いことに気づく。

TypeError: __init__() takes 1 positional argument but 2 were given

これは何を言っているのかと言うと、
「initメソッドは1つしか変数取れないのに、なんで2つも指定しているんだい?」
とのことである。

いやいや、変数は1つしかしていませんよね?そちらこそ何言っているんだい?
Pythonのバグだろうか?
※お気づきかもしれないが、もちろんバグではなく、"self"という引数がカギを握っていると予想できる。

一方で、"ルール"に沿ってコードを書きなおして実行してみる。

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

    def introduce(self):
        print("私は" + self.name + "です。")

player = Person("太郎")
player.introduce()

これを実行してみると。

私は太郎です。

正しく出力されているようだ。
2つのコードの違いとしては、selfを入れたか入れなかったかであるが、selfを入れると正しく動く。
やはりまだよく分からないので、このサイトを参考にして、なぜこのように処理の違いが出てしまうのかを考えている。

どうやら隠された引数があるらしい

先ほどselfを指定しないコードの処理において、
TypeError: __init__() takes 1 positional argument but 2 were given
というエラーが出ているが、この意味は何だったのかというと、

「initメソッドは1つしか引数取れないのに、なんで2つも指定しているんだい?」

ということであった。つまり、これの逆を返せば、

「initメソッドの引数が2つあれば、問題なく処理できるよ」

つまり、player = Person("太郎")という指定方法では、実は2つの引数を指定していることになっているが、init関数においては、nameという一つの引数しか指定していないので、エラーが発生していることになる。

selfを理解するカギは、player = Person("太郎")を指定する際に、隠れたもう一つの変数を渡しているということが分かる。

ということは、init関数の記載において、def __init__(nazo , name)と指定すれば良さそうだ。
良く分からない謎の引数があるので、「nazo」という引数を指定してみる。

class Person:
    def __init__(nazo,name):
        nazo.name = name

    def introduce(nazo):
        print("私は" + nazo.name + "です。")

player = Person("太郎")
player.introduce()

これを実行してみると。

私は太郎です。

あれ?

selfじゃないけど通過してしまった。
そう、どうやらselfでなくても良いみたいですね。

ここまでで分かったのは、謎引数があるということと、その謎引数を入れれば処理ができるということ。
そして、引数はselfである必要はないということだ。

もう少しでたどり着けそうな匂いがしてきた。

いったい何が引数として渡されているのか

例として、"python"という文字をすべて大文字にする関数を考えてみる。
大文字にする関数は、strクラス(もともとPythonの中で準備されているクラス)の中にあり、upper関数を用いることで処理できる。

upper関数は、def upper(object):とクラスの中で定義されているので、str.upper(object)として呼び出すことができる。

string = "python"
print(str.upper(string))

これを実行すれば、PYTHONという出力が得られる。

ただし、このような記載で大文字変換をする人はいないだろう。
恐らく多くの人は、大文字にする処理についてはこのようにコードを書くはずである。

string = "python"
print(string.upper())

これを実行しても、PYTHONという出力が得られる。

こちらの説明によると、どうやらpythonでは、string.upper(string)という記載は見にくいからやめましょう。引数のstringは省略させてもいいけど、裏ではちゃんと引数を取っていることにしましょうねというルールにしたとのことである。

さて、省略された引数は何かというと、string自身、つまり「俺 = self」ということである。

要は、オブジェクトからメソッドを利用する際に、オブジェクトの引数を省略しても良いっすよ。ただし、省略しても良いというだけであって、裏ではちゃんと引数としてこっそりもらっておくからよろしく!というルールがpythonにはあるらしい。

何が言いたいのかというと、先ほど作ったplayerインスタンスがintroduceというメソッドを利用する際には、本来であれば player.introduce(player)と書かなければいけないが、引数のplayerを省略して、player.introduce()と書いてもいいよ。その代わり、あくまでも記載上は省略するということであって、引数自体が消えたわけじゃないから、クラスのほうでは"self"として勝手に引数をもらっておきますねということだ。

ということは、initメソッドも同じように、利用する際(コンストラクタなので勝手に実行される)には便宜的に記載をすると、本来であれば、player.init(player,"太郎")としなければいけないが、同じように省略することができるので、インスタンス側では、player = Person("太郎")という書き方でよい。その代わり、クラスのほうでは省略された引数を貰わなければいけないので、クラス側では"self"を引数で設定する必要があるということになる。

これをもとに、もう一度Personクラスを見てみる。

class Person:
    def __init__(nazo,name):
        nazo.name = name

    def introduce(nazo):
        print("私は" + nazo.name + "です。")

player = Person("太郎")
player.introduce()

このソースコードから言えることは、Personクラスのinitメソッドを使いたいのかい?じゃあ"お前(self)"という引数は省略してもいいけど、あくまでも省略を許しているだけだから、クラスのほうではちゃんと引数(self)をもらっておくぞということになる。

分かりやすい例は、『Pythonのクラス屋さんは、インスタンスの顔パスを許している』ということかなと思う。

playerというインスタンスがクラス屋さんへ入店する際に、本来であれば名簿に名前を書かなければいけないけど、「良く知っている人だから顔パスでいいよ!」ってことになっている。
ただし、クラス屋さんとしては、売上管理のために、playerさんが来たことはいちおう書いておく。

ようやく答えが分かった、selfとはインスタンスそのものであると。
本来であれば、利用させていただくのに名前(player)をちゃんと教えなければいけないけど、顔パスを許可しているので、いちいち書く必要はない。でも、本当のルールとしては書いて欲しいわけだから、クラスのほうでは引数としてselfを設定しているのだ。

最後に、もう一度ソースコードを見て確認したい。
先ほどは「nazo」と書いたが、インスタンス自身が引数になるので、「self」が望ましいだろう。

class Person:
    #init関数使いたいなら顔パスでいいけど、名前はこっちで書いておくからな。
    #そうしないと、誰なのか分からなくなっちゃうからよ。
    def __init__(self,name):
    #"お前(self)の"名前はここに書いておくからな。
        self.name = name

    #顔パスだから勝手に使ってもいいけど、誰が使ったか分かんねえからこっちで名前書いておくぞ。
    def introduce(self):
        print("私は" + self.name + "です。")

player = Person("太郎")
player.introduce()

あとがき

初学者ゆえに分かりづらい記載もあったかもしれないが、こんなイメージで覚えておけばとりあえずはいいと思う。
もう少し詳しく知りたい場合は、このサイトが良いと思うが、初学者には難しいので、中級以上になってから改めて読んだほうが良さそうである。
私も半分理解できたが、半分はまだ分からなかった。

1人でもPythonを学ぶ人が、この記事を読んでselfでつまづかないようになればと思う。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away