Python の関数とメソッドの違いってなに?
自分を呼び出したオブジェクトを
代入するか、しないか
とりあえず、次のコードをコピペして実行してみてください。
class GirlFriend(object): def change_name(self, name): self.name = name girl_friend = GirlFriend() # 1) メソッドでの呼び出し方 girl_friend.change_name('岩倉玲音') print(girl_friend.name) # 岩倉玲音 # 2) 関数での呼び出し方 GirlFriend.change_name(girl_friend, 'サーバルちゃん') print(girl_friend.name) # サーバルちゃん
まったく同じ動作をしています。
>>> # 1) メソッドでの呼び出し方 ... girl_friend.change_name('岩倉玲音') >>> print(girl_friend.name) # 岩倉玲音 岩倉玲音 >>> >>> # 2) 関数での呼び出し方 ... GirlFriend.change_name(girl_friend, 'サーバルちゃん') >>> print(girl_friend.name) # サーバルちゃん サーバルちゃん >>>
◯ メソッドの動作
メソッド 1, 関数 2 を実行すると同じ結果が得られます。
# 1) メソッドでの呼び出し方 girl_friend.change_name('岩倉玲音') # 2) 関数での呼び出し方 GirlFriend.change_name(girl_friend, '岩倉玲音')
x.f() という呼び出しは、 MyClass.f(x) と厳密に等価なものです。
9.3.4. メソッドオブジェクト
実は、メソッドが呼び出されると、単純に関数が呼び出されてるだけです。
# 1) メソッドが呼び出されると girl_friend.change_name('岩倉玲音') # 2) 関数に変換されて実行される。 GirlFriend.change_name(girl_friend, '岩倉玲音')
関数オブジェクトからインスタンスメソッドオブジェクトへの変換は、
インスタンスから属性が取り出されるたびに行われます。
呼び出し可能型 (callable type) > インスタンスメソッド
疑問: self ってなに?
答え: 関数のいちばん最初の引数です。
たくさん self を書かないといけないので、 self は、特別な何かなのかなと混乱してしまいます。
これまで見た通りメソッドは、単純に関数を呼び出しているだけです。 self は、たんなる関数の第一引数、いちばん最初の引数でしかありません。
実際に self は関数の引数なので、別に self と書かなくてもメソッドは問題なく動きます。
class GirlFriend(object): def change_name(girl_friend, name): # self じゃなくても動く。 girl_friend.name = name person = GirlFriend() person.change_name('岩倉玲音') person.name # 岩倉玲音
理解するために意識しておいて欲しいことは、
クラスの中で書いてるのは関数ですよ、ということです。
class GirlFriend(object): def change_name(girl_friend, name): # <- 関数を書いてます。 girl_friend.name = name
ちなみに self と書かないといけないのは PEP 8 という Python のコーディング規約で決められています。
self は機能ではなく、そうやって書いてくださいね、という決まりごとでしかありません。
インスタンスメソッドの第一引数には、self を使ってください。
Always use self for the first argument to instance methods.
PEP 8 - Style Guide for Python Code
とは言え、言葉で説明されても分からないので、
実際に触って比較して理解を試みたいと思います。
2. 実際に触って、メソッドを関数と比較してみる。
Step 1: 属性を参照してみる。
Step 2: 第一引数を省略して呼び出してみる。
Step 3: 第一引数を省略しないで呼び出してみる。
◯ 関数
クラスオブジェクトの属性に代入されるのは "関数" です。"メソッド" ではありません。クラス定義文の中で定義された関数は、クラスオブジェクトの属性に代入されます。
class GirlFriend(object): def change_name(self, name): self.name = name GirlFriend # <- クラスオブジェクト
>>> GirlFriend # <- クラスオブジェクト <class '__main__.GirlFriend'> >>>
このクラスオブジェクトの属性に代入された "関数" を使って、"関数" の動作を確認してみます。実際にクラスオブジェクトの属性に代入された関数を参照して見ましょう。
# Step 1: 属性を参照してみる。 GirlFriend.change_name # Step 2: 第一引数を省略して呼び出してみる。 GirlFriend.change_name('岩倉玲音') girl_friend.name # Step 3: 第一引数を省略しないで呼び出してみる。 GirlFriend.change_name(girl_friend, '岩倉玲音') girl_friend.name
>>> # Step 1: 属性を参照してみる。 >>> # function という文字が見えます。 >>> # 属性を参照するとメソッドではなく >>> # 関数が代入されていることがわかります。 >>> GirlFriend.change_name <function GirlFriend.change_name at 0x10b59f1e0> >>>
>>> # Step 2: 第一引数を省略して呼び出してみる。 >>> # 関数 change_name は2つの引数 self, name が必要です >>> # 引数を1つだけ与えるとエラーが返されます。 >>> GirlFriend.change_name('岩倉玲音') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: change_name() missing 1 required positional argument: 'name' >>>
>>> # Step 3: 第一引数を省略しないで呼び出してみる。 >>> GirlFriend.change_name(girl_friend, '岩倉玲音') >>> girl_friend.name '岩倉玲音' >>>
◯ メソッド
クラスオブジェクトからインスタンスオブジェクトが生成されると、 "関数" は "メソッド" として代入されます。"メソッド" は第一引数に、呼び出し元のオブジェクトが与えられます。
# Step 1: 属性を参照してみる。 girl_friend.change_name # Step 2: 第一引数を省略して呼び出してみる。 girl_friend.change_name('サーバルちゃん') girl_friend.name # Step 3: 第一引数を省略しないで呼び出してみる。 GirlFriend.change_name(girl_friend, 'サーバルちゃん')
>>> # Step 1: 属性を参照してみる。 >>> # method と書かれた文字が見えます。 >>> # 属性を参照すると関数ではなく >>> # メソッドが代入されていることがわかります。 >>> girl_friend.change_name <bound method GirlFriend.change_name of <__main__.GirlFriend object at 0x10b5af240>> >>>
>>> # Step 2: 第一引数を省略して呼び出す。 >>> girl_friend.change_name('サーバルちゃん') >>> girl_friend.name 'サーバルちゃん' >>>
>>> # Step 3: 第一引数を省略しないで呼び出す。 >>> girl_friend.change_name(girl_friend, 'サーバルちゃん') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: change_name() takes 2 positional arguments but 3 were given >>>
疑問: なんでメソッドは、第一引数 self にインスタンスを自動的に代入してくれるの?
答え: 読みやすくなるから
もし、第一引数 self にインスタンスを自動的に代入してくれなかった場合、こういう書き方になります。2回も girl_friend を書いていて冗長です。
# (偽物)メソッド呼び出し girl_friend.change_name(girl_friend, 岩倉玲音')
そこで第一引数 self に自分を呼び出したインスタンスを、自動的に代入しておいてもらった方が読みやすいコードになります。
# (本物)メソッド呼び出し girl_friend.change_name('岩倉玲音')
self って、なんだ?という混乱を招いてはしまいますが。デメリットよりも、メリットの方が大きいかなと思います。こうやって便利な機能を実装すると、便利になる反面、理解しづらくなる感覚を、なんとなくおさえておいてください。
理解しにくくなるけど便利にしようかな、それとも理解しやすくしておいて面倒だけどガリガリ書き込んでもらうようにしようかなと、決断を迫られる場面が出てくるのではないかなと思います。
◯ まとめ
メソッドは、単純にクラスで定義された関数を呼び出しているだけです。self は、その呼び出された関数の第一引数でしかありません。
ここまでは、関数とメソッドの動作の違いを確認しました。
もし、Python を習いたてで、すでに、お腹いっぱいだったり、まだ興味がわかなければ、ここまでで十分だと思います。 何故なら、ここから先のことを知らなくても、十分にアプリは作れるからです。
確かに、知っていれば、もう少し複雑なことができるようになりますが、 いろんなアプリを作ってみて、メソッドの動作の中身がどうなってるのか気になったら、また来ていただけると嬉しいです。
頑張って書いては見ましたが、オブジェクトについて具体的なイメージがない時にここから先の文章を読んでも、 抽象的すぎて役に立たないどころか、苦痛になってしまうと思います。
いまやらないで、学ぶタイミングを見計らうことは、きっと効率的に学習する上で大切だと思っています。
ここから先は、メソッドの設計背景と仕組みについて説明します。
具体的には次の2つのことを説明します。
1つ目は、なぜ self を書いてまでメソッドを関数で定義するのか。 なぜなら self を書いてまでメソッドを関数で定義した方が、一貫性が高いから。
2つ目は、メソッドへの変換は誰が行なっているのか。 関数からメソッドへの変換は、ディスクリプタが行なっています。
1 章 | ... | 疑問: メソッドを関数の違いって何? |
2 章 | ... | メソッドを関数と比較してみる。 |
3 章 | ... | 疑問: メソッドと関数は、どう使い分けるの? |
4 章 | ... | メソッドを変更してみる。 |
5 章 | ... | 疑問: なんで self を書くの? |
6 章 | ... | 疑問: なんで全てのメソッドが変更されたの? |
7 章 | ... | メソッドの呼び出しを高速化してみる。 |
8 章 | ... | 疑問: 関数からメソッドへの変換って何をしているの? |
9 章 | ... | 疑問: 誰が関数からメソッドに変換しているの? |
3. 疑問: どんな時に関数で書いて
どんな時にメソッドで書くべきなんだろう?
Step 1. 特定のクラスに限定されているかでクラスを設計する。
答え: まずは、次のような理解でいいかなと思います。
メソッド
特定のクラスに限定された操作関数
特定のクラスに限定されない操作
例えば list.sort メソッドと sorted 関数があります。
なぜ list.sort はメソッドで sorted は関数なのでしょうか?
実は sorted 関数は、for 文の in の中に書けるオブジェクならなんでも引数に取れます。例えば dict, list, str などです。 反対に list.sort メソッドは list しか引数に取れません。
lst = [3, 1, 2] dct = {'a': 0, 'b': 1, 'c': 2} txt = 'Hello, world!' # sorted は何でも sorted(dct) sorted(txt) sorted(lst) # sort は list だけ lst.sort() lst
>>> # sorted は何でも ... sorted(dct) ['a', 'b', 'c'] >>> sorted(txt) [' ', '!', ',', 'H', 'd', 'e', 'l', 'l', 'l', 'o', 'o', 'r', 'w'] >>> sorted(lst) [1, 2, 3] >>> >>> # sort は list だけ ... lst.sort() >>> lst [1, 2, 3] >>>
list.sort() メソッドはリストにのみ定義されています。 一方 sorted() 関数は任意のイテラブルを受け付けます。
Python HOWTO - ソート HOW TO
結果をリストに代入しなければなりませんが、list の場合は最初からあるリストに、結果を代入すればいいだけです。
一方で dict, str は、結果を保存するための新しいリストを生成しなければなりません。
その分だけメモリを消費してしまいます。
import sys lst1 = [3, 1, 2] lst2= sorted(lst1) # sorted は新しいオブジェクトを生成している。 lst1 # [3, 1, 2] lst2 # [1, 2, 3] # その分、メモリを消費する sys.getsizeof(lst1) # 88 sys.getsizeof(lst2) # 112 # なぜ要素数が同じなのに # メモリは 88, 112 と結果が異なるのかは、わかりません。
パフォーマンスが問題となる状況では、ソートするためだけにリストのコピーを作るのは無駄が多いです。 そこで、 list.sort() は新しいリストを生成しないで、リストをソートします。
なぜ list.sort() はソートされたリストを返さないのですか? - Python よくある質問
# NG # エラーになります。 lst = [0, 3, 1, 2] for element in lst.sort(): element
# OK lst = [0, 3, 1, 2] lst.sort() for element in lst: element
このことを忘れないため、この関数はソートされたリストを返しません (ワイの注釈: list.sort が新たにリストを生成しないということを忘れないために、list.sort は None を返します。)。 こうすることで、ソートされたコピーが必要で、ソートされていないものも残しておきたいときに、 うっかり上書きしてしまうようなことがなくなります。
なぜ list.sort() はソートされたリストを返さないのですか? - Python よくある質問
Step 2. 現実世界のモデリングとしてクラス設計をする。
「メッセージ」という言葉があります。ちょっと難しいですが、 頭の片隅にいれて置くと、オブジェクト指向でコーディングをする上で、 関数とメソッドを書き分ける上で、いつか理解の助けになるかもしれません。
この記事は、古くかつ Java を題材にしていますが、とてもいい記事だと思います。
オブジェクト指向は、基本的に「現実世界をモデリングして、 そのモデルのコードを書いたらプログラムができる」ということを目指して作られたものです。 (実際にシステムを作るとモデルと現実世界は必ずしも一緒じゃありませんが)
ですので、オブジェクト指向を考えたお偉いさんが、現実の世界でおこっているある状態をモデル化するときに 「仕事をお願いする」というようなことを「メッセージ」という名前をつけました。
メッセージ脳の作り方 - オブジェクト脳オンライン
この記事も Java を題材にしていて、かつメッセージという言葉はでてきませんが
オブジェクト指向を理解する上において、すごくいい記事だと思います。
もう一度言う。 オブジェクトはデータとそれを操作する関数をセットにしたものではない。 オブジェクトはデータエンティティではない。では何か? ... 真のオブジェクト指向の世界では、オブジェクトを生きた有機体のように扱い、 オブジェクトには生まれた日と死ぬ瞬間がある。
また、君が望むなら、アイデンティティや性質を持たせてもいい。 犬にはデータの一部(例えば体重)をくれるよう頼むことができるし、犬はその情報を返してもよい。 ただ、この犬は能動的なコンポーネントであることを忘れてはいけない。 こちらの要求に対し、何をするかは犬が決めるのだ。
Getter/Setterは悪だ。以上。 - To Be Decided
最初にオブジェクト指向について習うときは、ほとんど聞きませんが、メッセージングという言葉は、とても大事な言葉なようです。
Eclipseを開発したDave Thomasや、オブジェクト指向という言葉の生みの親であるAlan Kay博士は、 オブジェクト指向という言葉は失敗だったと語っている。[1] これは、本来オブジェクト指向が重視すべきは「オブジェクト」ではなく「メッセージング」であるにもかかわらず「メッセージング」がおろそかにされているためである。
オブジェクト指向 - Wikipedia
Step 3. コードの整理術としてクラス設計をする。
ただ、以下の本では、オブジェクト指向は、必ずしも現実世界のものを表現しているという訳ではなくて、 単純にコードを整理するための方法論、「整理術」として捉えるといいですよね的な話をしてくれています。
結局「オブジェクト指向」という難しい言葉については考えずに、関数で書いた方が読みやすければ関数で メソッドで書いた方が読みやすければメソッドで書けば、いいかなと思ったりもします。
この章では従来からしばしば行われてきた「オブジェクト指向が現実世界をそのままソフトウエアに表現する技術である」という説明について 考え直します。 結論から言うと、オブジェクト指向プログラミングの仕組みと現実世界の様子は、それなりに似ている点はあるものの、実際はかなり違うものです。
... かなり中略
筆者も長い間、コンセプトが中心だと考えていましたが、 業務分析や要求定義でオブジェクト指向のコンセプトを現実世界に当てはめ、 それをC++やJavaで実装しようと試みるうちに、 このコンセプトが当初期待していたほど現実世界のモデル化やソフトウエアの設計にうまく適合しないと感じるようになりました。 このコンセプトを当てはめることにこだわりすぎて、 かえって保守しづらいアプリケーション構造にしてしまうような事例もいくつか見てきました。
上記の書籍では、"唯一の正しい" クラス設計とは何か?について突き詰めて考えて、ゲシュタルト崩壊していく感覚を示してくれています。
オブジェクト指向の限界を論理的に説明するのは、おそらく無理だと思うのですが、自分にはなんとなく腑に落ちる説明でした。
クラス設計は、視点や立場によって変わるので、全てのシステムに適用できる "唯一の正しい" クラス設計がある訳ではないということかなと思っています。 だから「オブジェクト指向」は "唯一の正しい" というよりは、読みやすい、理解しやすいくらいの「整理術」として抑えておくのが良いということを、 上記の書籍は伝えようとしてくれるのかなと思いました。
オブジェクト指向だけでなく、立場(視点)が曖昧な議論はえてして混乱を招くと思う。 余談だがデザインパターン全盛の時代に、あるべきクラス設計の議論でモメて時間をつぶすという事態は結構な割合で経験者がいると思う。
icoxfog417 - Twitter
すべての現実世界のものはクラスにモデリングできると考えて設計された Java では、
全ての関数は必ずクラスに所属していなければなりませんでした。
しかし、それでは極端すぎるので Java の後継言語たる Kotlin では、クラスに所属する必要はなくなりました。
全てのものを正確にモデリングすることはおろか、全てのものをモデリングすることそのものが困難だったという訳です。
と個人的に思い込んでいます。
// Java public class Sample{ public static void main(String args[]){ printSum(3, 4); } public static void printSum(int a, int b){ System.out.println(a + b); } }
// Kotlin fun main(args: Array<String>) { printSum(3, 4) } fun printSum(a: Int, b: Int) { println(a + b) }
つまりオブジェクト指向とは、ごくごくざっくり言えばクラスを使って「名前空間」を適切に分割することです。
その1つのやり方として「現実のものを元にクラス設計をする」こともあるけど、
必ずしもそうではないということかなと個人的に思い込んでいます。
◯ 混乱したこと
Python で書いていて2つの書き方を見て混乱しました。 1つは len, str などの関数、もう1つは str.join メソッドです。
メソッドと関数の書き分けをどうすればいいのかわからなくなりました。 当時の経緯を振り返ることによって、疑問が解けるわけではないのですが、のぞいて見たいと思います。
1. メソッドではなく関数が採用された例
ただ、もし、汎用的な処理でかつ、オブジェクト指向である必要がないならば、メソッドよりも、 モジュールの中で関数として定義してしまった方が、望ましいという雰囲気、感覚を感じたりもします。
# (本物) len(obj)
# (偽物)オブジェクト指向なら、メソッドで統一してほしいのになんで? obj.len()
2. 関数ではなくメソッドが採用された例
ただ、もし、汎用性の低い処理であるならば、たとえ関数として書けたとしても、 メソッドとしてクラスの中に格納してモジュールを整理してしまった方が良さそうにも感じます。
# (本物) ', '.join(['Hello', 'wolrd!'])
# (偽物)上は読み辛い。join こそ、組込関数にしてほしいのになんで? join(['Hello', 'world'], ', ')
4. メソッドを変更してみる。
メソッドを変更したい時は、
クラスオブジェクトの属性に関数を代入します。
class GirlFriend(object): def change_name(self, name): self.name = name # Step 1: # new_change_name 関数を定義しました。 def new_change_name(self): self.name = 'バラライカ' # Step 2: # 正 クラスオブジェクトの属性に代入する # GirlFriend.change_name = new_change_name # 誤 インスタンスオブジェクトの属性ではない。 # girl_friend1.change_name = new_change_name GirlFriend.change_name = new_change_name # Step 3: # インスタンスオブジェクトのメソッドは変更される。 girl_friend = GirlFriend() girl_friend.change_name() print(girl_friend.name) # バラライカ
クラスオブジェクトの属性(関数)を変更すると
そのクラスオブジェクトから生成された
全てのインスタンスオブジェクトの属性(メソッド)も変更されました。
◯ ポイント
Python の関数は、再利用性が高いです。
このようにして、関数をクラスオブジェクトの属性に代入するだけで、メソッドとして利用できます。 関数は、関数として利用したり、あるいは複数の異なるクラスのメソッドとして共有して利用したりすることができます。
def change_name(person, name): person.name = name class BoyFriend(object): change_name = change_name boy_friend = BoyFriend() # メソッド boy_friend.change_name('やる夫') # 関数 change_name(boy_friend, 'やらない夫')
疑問: クラスオブジェクトって何?
さっきからクラスオブジェクト、クラスオブジェクトと連呼していますが、これは何者でしょうか? 実は、この記事は以下の記事からの続きになります。 クラス、クラスオブジェクトとは、何かについて説明しています。 この先を理解するには、ざっくりとでも押さえておきたい知識です。
5. 疑問: なんで明示的に self を書くの?
答え: 関数として再利用できなくなるから
Explicit is better than implicit.
明示的であることは暗黙的であるより良い。
PEP 20 -- The Zen of Python
「暗黙的に self に関数を呼び出してきたインスタンスオブジェクトを代入するような設計」も考えることができます。
# 「暗黙的に self に関数を呼び出してきた # インスタンスオブジェクトを代入するような設計」がされているなら。 # self を書かなくても # 使えるようにすればいいじゃん! class GirlFriend(self): max_intimacy = 100 min_intimacy = 0 def __init__(name=''): self.name = name self.intimacy = 0 def change_name(name): self.name = name
しかし、このように設計すると関数を再利用したい時に、困ることが3つあります。この後、それを見ていきたいと思います。
例 1. 親クラスの関数を呼び出す時
親クラスのメソッドを呼び出したい時があります。 もし「self に関数を呼び出してきたインスタンスオブジェクトを暗黙的に代入するような設計」だと、self にクラスモジュールが代入されてしまい、再利用できなくなります。
# 「暗黙的に self に関数を呼び出してきた # インスタンスオブジェクトを代入するような設計」がされているなら。 class A: message = "Hello, world!" def f(): print(self.message) class B(A): message = "Nihao, shijie!" # 你好,世界! def g(): # 親クラスの関数 f を再利用して # "Nihao, shijie!" を表示したいけど... # self にはクラスオブジェクトの A が暗黙的に代入されて # "Hello, world!" が表示されてしまう。 A.g()
第二に、特定のクラスからメソッドを明示的に参照または呼び出ししたい時に、特別な構文が必要なくなります。C++ では、派生クラスでオーバーライドされた基底クラスからメソッドを使うには、 :: 演算子を使わなければなりません。 Python では、 baseclass.methodname(self,
) と書けます。これは特に、 __init__() メソッドに便利ですし、派生クラスのメソッドが、基底クラスにある同じ名前のメソッドを拡張するために、基底クラスのメソッドをどうにかして呼び出したい時にも便利です。
なぜメソッドの定義や呼び出しにおいて ‘self’ を明示しなければならないのですか? | デザインと歴史 FAQ
super クラスから呼び出せばいいじゃんという話があるかもしれません。
できない訳ではないと思いますが、
ただ、親クラスの関数 A.f を参照できなくなるため super クラスを実装することそのものが、複雑になってしまいます。
# self を省略しないなら class B(A): def g(self): # super().f() と同じ動作をする # Python のコードは簡単に書ける。 super().f() A.f(self)
# 「暗黙的に self に関数を呼び出してきた # インスタンスオブジェクトを代入するような設計」がされているなら。 class B(A): def f(): # super().f() と同じ動作をする # Python のコードは簡単に書けない。 super().f() super(A, self).f() ??? # -> super クラスを実装するために # 特別な機能を新しく作らないといけない
super クラスは、親クラスを参照するための機能ではありません。
親クラスを参照するには、A.f(self) とすればいいだけです。
では super クラスは何をしているのでしょうか?
ひし形継承した時に2回メソッドを呼び出すのを避けるための機能です。
super については、この記事が一番わかりやすいです。
super を使うメリットは、コンストラクタのような クラス階層全体にまたがるメソッドチェーンを、 ダイヤモンド型の多重継承を行った場合でも実現できることである。
Python の super のメリットとデメリット - methaneのブログ
下記の記事は Pythonの生みの親 Guido によって書かれたブログを和訳したものです。Bruce という人が「第一引数にわざわざ self を与えなくたって、暗黙的に呼び出し元のインスタンスオブジェクトを self に代入すればいいじゃん」と提案したのに対して、Guido が、それがなぜダメなのかの理由を述べています。
ここにとても明快な論拠がある。"self"を明示的に引数として記述することによって, 以下の2つの関数呼び出しが論理的に同じであることを補強できる,というのである。 つまり,メソッドfooはクラスCのインスタンスであることを論理的に示せるわけだ。
foo.meth(arg) == C.meth(foo, arg)
このようにして Python では親クラスを、とても簡単に参照することができます。
しかし、言語の中身の実装なんか、どうだっていいじゃない?という意見もあるかもしれませんが、ここで Flask の開発者である Armin Ronacher(イケメン) が、Python は、文法はいいんだけど実装がやばい。と怒っている記事を紹介します。ちょっと難しいかもしれません。
私が見たい Python - The Python I Would Like To See
例 2. ある処理をメソッドとしても関数としても使いたい時
汎用的な関数を定義した時、この手の関数はメソッドとして再利用したい時があります。もし「self に関数を呼び出してきたインスタンスオブジェクトを暗黙的に代入するような設計」だと、 関数をメソッドとして再利用できなくなります。あるいは、その逆も、できなくなります。
# 「暗黙的に self に関数を呼び出してきた # インスタンスオブジェクトを代入するような設計」がされているなら。 # 1. 関数はメソッドとして再利用できない。 def change_name(person, name): person.name = name class BoyFriend(object): change_name = change_name boy_friend = BoyFriend() # 関数 change_name(boy_friend, 'やる夫') # メソッド にはインスタンスを与えないといけない。 boy_friend.change_name(boy_friend, 'やる夫')
# 「暗黙的に self に関数を呼び出してきた # インスタンスオブジェクトを代入するような設計」がされているなら。 # 2. メソッドは関数として再利用できない。 def change_name(name): self.name = name class BoyFriend(object): change_name = change_name boy_friend = BoyFriend() # 関数 としては使えない(self に何も代入されないから) # change_name('やる夫') # メソッド boy_friend.change_name('やる夫')
例 3. 第一引数 self を操作するデコレータを定義したいとき
関数やメソッドに共通の前処理、後処理を定義する際には、デコレータを使います。
Python の デコレータとクロージャ - いっきに Python に詳しくなるサイト
第一引数 self を操作するデコレータを定義したいとき、第一引数 self が明示されていれば簡単にそのようなデコレータを実装できます。
例えば classmethod デコレータも, staticmethod デコレータは、第一引数 self を操作するデコレータです。
Python の classmethod と staticmethod ってなに? - いっきに Python に詳しくなるサイト
classmethod デコレータも, staticmethod デコレータもクロージャを使えば簡単に実装できます。
def classmethod(function): def decorated_method(self, *args, **kwargs): return function(type(self), *args, **kwargs) return decorated_method
def staticmethod(function): def decorated_method(self, *args, **kwargs): return function(*args, **kwargs) return decorated_method
ところが、もし「self に関数を呼び出してきたインスタンスオブジェクトあるいはモジュールを暗黙的に代入するような設計」をされてしまうと、第一引数 self を扱えなくなります。
classmethod デコレータや staticmethod デコレータと同じ動作をどうやって実装しましょう?
そのような動作は、クロージャだけでは実装できなくなります。そうなってくると処理系のレベルで、classmethod, staticmetod デコレータのための特別な実装が必要になります。 Guido はそのような実装をすることを却下しています。
"@classmethod"や"@staticmethod"を特別な記法で代替するハックを私は却下する。また,メソッドの定義だけを検査して,自動的にクラスメソッドかインスタンスメソッドかを決めるような手法が良いアイデアだとも思わない(実際だれかがBruceのブログにコメントする形で提案していたが)。そのような仕様を採用すると,メソッドの前にはdefという宣言があるだけなのに,メソッドは複数の異なった呼ばれ方をすることになる。どのように呼ばれるかを判断しづらくなる。
和訳 なぜ Python のメソッド引数に明示的に self と書くのか
疑問: なんで self って書くことが不自然に感じるんだろう?
答え: メソッドを定義していると思っているから
自分も self を頭に書くと可読性という実用性がひどく損なわれる気がしたので Bruce さんと同じこと考えてました。しかも、これさえなければ Python は素晴らしいのに、とさえ思っていました。なので Bruce さんがコテンパンにされてるのを見て凹みました。
では、なぜ self を第一引数に書くと不自然だと感じるのでしょうか?それは「メソッドを定義していると思っているから」です。厳密に言えばクラス定義文の中で書いてるのは、関数定義文です。メソッドではなく関数を定義しています。
実際に Python 言語リファレンスを覗いてみても funcdef 関数定義文 と言うのはあったのですが methoddef のようなメソッド定義文に該当しそうなものはありませんでした。
compound_stmt ::= if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef <--------- 関数定義文 | classdef | async_with_stmt | async_for_stmt | async_funcdef
Pyhton は基本的には(広い意味での)関数型言語で、オブジェクト指向はハッシュ表を使って後付したものです。 この点、もともとオブジェクト指向言語として設計された C++, Java, Ruby などとは違います。
Python は手続きの定義を、関数定義とメソッド定義に分ける代わりに、 全てを関数定義にし、メソッド定義として使う場合は第一引数をインスタンスに割り当てるという 約束事を導入しました。
このようにしたのは、関数型言語は、オブジェクト指向言語より抽象性が高いという信念でしょう。 Python は関数型言語 Haskell から多大な影響を受けているいわれています。 実際、[code 2] で示したように関数型言語を使えばオブジェクト指向は簡単に実装できます。
Python のクラスシステム
上の文章は、とても、とても勉強になったのですが、若干の修正が必要で Python はメソッドに比べて関数をとても重視していますが、Python は命令型言語ではなく関数型言語に重きをおいているという訳では、どうやらなさそうです。
私 (Guido van Rossum) は Python が関数型言語から強く影響を受けたことがあるとは一度も考えたことはなかったが、 そのように言ったり思ったりする人はいるようである。私がそれまでに慣れ親しんできた言語は、C言語やAlgol 68のような命令型言語である。
Python の"関数型"の機能の起源 - The history of Python
関数型プログラミングとは Referential Transparency を徹底的に追求するプログラミングです ... 後略
megumin1 氏のブクマ
◯ 関数と辞書でブジェクト指向
上で引用した記事 Python のクラスシステム では、関数(クロージャ)と辞書の2つを組み合わせれば、オブジェクト指向と同じようなことを表現できることを紹介しています。インスタンスメソッドオブジェクトを関数(クロージャ)で、クラスオブジェクトやインスタンスオブジェクトを辞書で、それぞれ代用してオブジェクト指向を表現しています。
emulating_oop.py - GitHubGist(自分にもわかるように書き直しました。)
クロージャとメソッドに、なんの関係があるんや?と思われた方は、Effective Python の 「3 章 クラスと継承 - 項目23: 単純なインターフェイスにはクラスの代わりに関数を使う」を読んでみてください。メソッド、クロージャの関係が近しいことを理解できるだけでなく、メソッド、クロージャの実践的な使い方を紹介してくれています。
属性と辞書に、なんの関係があるんや?と思われた方は、以下のコードを見てください。実際に Python の属性、さらには変数までもが(即ちスコープそのものが)、辞書で実装されていたりします。
# var = 0 locals().update({'var': 0}) print(var) # 0 # obj.attr = 1 class Cls(object): pass obj = Cls() obj.__dict__.update({'obj_attr': 1}) print(obj.obj_attr) # 1
スコープ (scope) とは、ある 名前空間 が直接アクセスできるような、 Python プログラムのテキスト上の領域です。 "直接アクセス可能" とは、修飾なしに (訳注: spam.egg ではなく単に egg のように) 名前を参照した際に、 その名前空間から名前を見つけようと試みることを意味します。
9.2. Python のスコープと名前空間 - Python チュートリアル
名前空間 (namespace) とは、名前からオブジェクトへの対応付け (mapping) です。 ほとんどの名前空間は、現状では Python の 辞書 として実装されています。
9.2. Python のスコープと名前空間 - Python チュートリアル
◯ 関数に傾きがちな Python
どうやら関数というのは、とても汎用的な表現のようです。Python は、できるのであれば、関数という枠の中に収めようとしている気配を感じます。メソッドの定義以外にも、そのようなものが2つあります。
メソッド定義文を導入せず、都度 self 書いてでも、関数定義文で書くメソッド定義
ジェネレータ関数定義文を導入せず、関数とは動作が違うにもかかわらず、関数定義文で書くジェネレータ関数定義
慈悲深き終身独裁官による判決 - PEP 255 Simple Generatorsメソッドを定義してから、それを関数で呼び出すという若干二度手間を踏む感のある len, str, repr, abs といた処理
Python の len はなんでメソッドではなく関数なの?
◯ まとめ
「なぜ self を書くのか?」という疑問は、「なぜ関数定義文でメソッドを定義するのか?」に言い換えることができますし、おそらくそっちの方が考えやすいかなと思います。さっそく、質問の文章を変えて、もう一度、考え直してみたいと思います。
5. 疑問: なんで関数定義文でメソッドを定義するの?
答え: 関数定義文は、メソッド定義文よりも汎用的な表現だから
もう一度、最初の疑問に戻ります。メソッッドを定義する度に self をわざわざ書くのは可読性という実用性を損ねている気がします。関数という一貫性を重視する必要性は、あったのでしょうか?
Special cases aren't special enough to break the rules.
特別なケースは、一貫性を破るほど特別ではない。
Although practicality beats purity.
けれども実用性は、一貫性に勝る。
PEP 20 -- The Zen of Python
Python の開発者たちが出した答えは "はい" です。そして Ruby の開発者たちが出した答えは "いいえ" です。ここで Ruby を例にとって見てみたいと思います。自分は Ruby を書いたことが無いので、間違っていたらご容赦ください。
◯ 実用性と一貫性, Ruby と Python
関数をメソッドとして共有したり、親クラスのメソッドを読んだり、クラスメソッド, スタティックメソッドするときでも、 Python は "新しい機能を追加することなく" 一貫性の高いコードでこれを実装することができます。
Ruby でやってもらおうとするには、新しい機能が必要になってきます。サンプルコードを比較しながら見ていきたいと思います。実際に読む前に簡単に 3 点だけお伝えしておきます。
0. Ruby のコードを読む前に...
1. Ruby のシェル, IRB(Interactive Ruby) を起動するには irb を実行します。下記のサンプルコードも、シェルにコピペで実行できるようになっています。
$ irb irb(main):001:0>
2. 全然、よくわかってないのですが、面白いのでサンプルコードでは return は、全部省略しました。
return 文を省略すると最後に評価された式の値が返されます。
Ruby ではメソッドの最後の return は使っても使わなくても全く同じ 意味に書ける。... 理由は、まず簡潔さ。それから「Ruby ではすべての式が値を持つ」と いう基本思想を適切に表現するからである。 if も while もイテレータ もメソッドも同様に値を持つ。ならば書式も全部同じにするのが適切で ある。 あらゆる式の記述の中にオブジェクトの流れを見るのだ。
return - Ruby のコーディングスタイル
3. インデントはスペース 2 文字です。
Ruby の正式なコーディング規約はありませんが、 2 文字であることが多いそうです。
Rubyの正式なコーディング規約はありません。
コーディング規約
ちなみに Python では PEP 8 でインデントが 4 文字に定められている理由は、ネストを深くして欲しくないからだと個人的に思っています。
Flat is better than nested.
ネストは浅い方が良い
PEP 20 - The Zen of Python
例 1. 親クラスのメソッドを指定して呼ぶ
Ruby では alias を追加して対応しています。
Python では、機能を追加しなくても、属性参照で対応することができます。
class A def f 'hello' end end class B < A # alias という機能が必要 alias :super_f :f def g super_f end end o = B.new o.g
class A(object): def f(self): return 'hello' class B(A): def g(self): # 属性参照だけ return A.f(self) o = B() o.g()
もし super_method を使うなら class C は次のような具合になります。
class B < A def g # super_method という機能が必要 method(:f).super_method.call end end
class B(A): def f(self): # 属性参照だけ return A.f(self) # Python にも super がありますが # これは、ひし形継承されていた時に # 祖先クラスのメソッドが # 2度呼び出されるのを避けるためのものです # Ruby の super_method と # Python の super は目的としている機能が少し違います。 return super().f()
例 2. ある処理をメソッドとしても関数としても使いたい時
Ruby のやり方が見つからない.. orz
例 3. スタティックメソッド、クラスメソッド
スタティックメソッド
Ruby では、スタティックメソッドがありません。
Python には、スタティックメソッドがあります。
クラスメソッド
Ruby では、クラスメソッドを特異メソッドで対応しています。
Python では、デコレータで対応しています。
特異メソッドとは?単一のオブジェクトに特化したメソッドのことらしいです。
特異メソッドについて -Qiita
Ruby では、クラスメソッドはクラスオブジェクトからアクセスしなければなりません。
Python では、クラスメソッドにインスタンスオブジェクトから直接参照できます。
class Hoge # def.self.メソッド名 という機能が必要 def self.class_name self.name end end hoge = Hoge.new # クラスオブジェクトから呼び出し Hoge.class_name # インスタンスオブジェクトから呼び出し → できない hoge.class_name
class Hoge(object): @classmethod def class_name(cls): return cls.__name__ hoge = Hoge() # クラスオブジェクトから呼び出し Hoge.class_name() # インスタンスオブジェクトから直接呼び出し → できる hoge.class_name()
Ruby のクラスメソッド定義まとめ - Qiita
Rubyのクラスメソッドがリファクタリングに抵抗する理由
ただ Python では、デコレータと言う機能がなくても、実装できます。
class Hoge(object): def class_name(cls): return cls.__name__ # 代入だけ(クロージャ) class_name = classmethod(class_name)
◯ Python は Ruby よりも優れているのか?
答え: そういうわけではないかなと思ったりします。
Ruby に比べて Python の方が一貫性のある書き方ができそうです。ここで言う一貫性とは、新しい機能を追加しなくても3つの機能を実装できました、と言うことです。
だからと言って、Python の方が優れているとは言えないかなと思ったりします。実際 Python 以外で関数定義文でメソッドを定義する言語をあまり見たことがないので、必要性が薄かったりするのかなと思ったりもします。
第1の理由は、頻度の多寡です。上で見た3つのことをしようとする機会がそんなに多くありません。反対に self を書く機会は、ゲシュタルト崩壊を起こしかねないくらい、とても多いです。
第2の理由は、影響の大小です。一貫性のある書き方ではできませんが、それでもその機能が実装できない訳ではないからです。あるいはなかったとしても、そこまで困りません。影響について、3つの例を振り返って見ます。
1. 親クラスのメソッドを指定して呼ぶ
この点において Python は Ruby よりも簡単に操作もできますし、 きっと実際に中身の実装もシンプルなものだと思います。
ただ、本来 実装による継承は避けられるべき と言うことを考えると、 実装による継承をしているということ自体、あまりよくないのかもしれません。
2. ある処理をメソッドとしても関数としても使いたい時
そんなことがしたい時は、恐らくそんなに無いかなと思います。
3. スタティックメソッド、クラスメソッド
あった方が可読性は良くなりますが無くても困らないかなと思います。 クラスメソッドについてはクラスオブジェクトを取得して self を使わなければ、それでことが済みます。 スタティックメソッドは self を使わなければ、それでことが済みます。 インスタンスを参照できてしまうので、正確には違いますが。
class Hoge def class_method # cls を取得して # self を参照しなければいい cls = self.class cls end def static_method # self を参照しなければいい 'Hello, world!' end end hoge = Hoge.new hoge.class_method hoge.static_method
Ruby は Python に比べて、一貫性よりも実用性を大事にしているのかもしれません。例えば Python の join は、なんで関数ではなくメソッドなの? では str.join メソッドについて見てきました。Python では実用性(可読性)よりも、実装の一貫性が優先されていました。Ruby では実用性(可読性)が、一貫性よりも優先されて実装されています。
# Python 読みにくい ", ".join(["hello", "world"])
# Ruby 読みやすい ["hello", "world"].join(", ")
一貫性 purity を大事にした Python がデータサイエンス系の分野で好まれて、
実用性 practicality を大事にした Ruby がウェブ系の分野で発展を遂げたのは、
ある意味そういうところにあるのかなと思ったりもします。
なぜ機械学習に Python が選ばれるのか - Qiita
Python の一貫性 purity は美しさであり、 Ruby の実用性 practicality は楽しさかなと思ったりもします。自由な楽しさと言うのは新しいサービスを生み出すこととマッチしているような気もします。
Rubyの価値は「楽しいプログラミング」
Ruby誕生から25年、そしてこれからのRubyは––まつもとゆきひろ氏が描く未来
Rails is the most popular web framework in the bay area by job openings.
2018/06/17 Demand for Ruby on Rails is Still Huge
メソッド定義を関数で定義することによって最も利益が得られるのは、親クラスの関数を参照する機能を実装する時です。
しかし、どうやら実装による継承は避けた方が望ましいということを考えると
(継承よりも合成)
、self を書くメリットは、すなわち関数定義文でメソッドを定義するメリットは、もしかしたら薄れてしまっているかもしれません。
◯ まとめ
Python では「なぜ、第一引数に明示的に self を書くのか?」は、実は「なぜ、メソッドの定義を関数定義文で行なっているのか?」に言い換えることができます。
そして、「なぜ、メソッドの定義を関数定義文で行なっているのか?」に対する答えは「関数定義文はメソッド定義文よりも汎用的な表現なので、より複雑なことをしようと思った時でも、一貫性を崩すことなく(Python 本体に新しい機能を追加しなくても)実装することができるからです。これは最終的に可読性の良さをもたらすから。」です。
Python は可読性、実用性を重視するが故に、私たちが思っている以上に、一貫性のある実装を優先しています。このことが str.join メソッドや self を明示したりすることに対して感じた違和感の原因でした。
また、何でも一貫性を重視すればいいというものではなく、その辺りのバランスは物によりけりかなとも思います。Ruby はウェブ系で、 Python はデータサイエンス系で発展しました。
6. 疑問: なんでクラスオブジェクトの属性に関数を代入したら配下のインスタンスオブジェクトのメソッドも全て変更されるの?
答え: メソッドを呼び出されるとその度に、クラスオブジェクトの関数にインスタンスオブジェクトを代入して実行しているだけから。
# これが呼び出されると girl_friend.change_name('岩倉玲音') # 自動的にこれが呼びされている。 GirlFriend.change_name(girl_friend, '岩倉玲音')
関数オブジェクトからインスタンスメソッドオブジェクトへの変換は、
インスタンスから属性が取り出されるたびに行われます。
呼び出し可能型 (callable type) > インスタンスメソッド
◯ 復習
メソッドを変更したときの挙動を、ちょっと復習してみます。
# # こんなクラスを作りました。 class GirlFriend(object): def change_name(self, name): self.name = name girl_friend_a = GirlFriend() girl_friend_a.change_name('サーバルちゃん') print(girl_friend_a.name) # サーバルちゃん girl_friend_b = GirlFriend() girl_friend_b.change_name('岩倉玲音') print(girl_friend_b.name) # 岩倉玲音 # # メソッドを変更します。 def new_change_name(self): self.name = 'バラライカ' GirlFriend.change_name = new_change_name # メソッドを変更した後にインスタンス化したオブジェクトの # メソッドは変更できています。 girl_friend_c = GirlFriend() girl_friend_c.change_name() print(girl_friend_c.name) # 'バラライカ' # メソッドを変更する前にインスタンス化したオブジェクトの # メソッドも変更できています。 girl_friend_a.change_name() girl_friend_b.change_name() print(girl_friend_a.name) # 'バラライカ' print(girl_friend_b.name) # 'バラライカ'
疑問: インスタンスメソッドオブジェクトって何?
答え: メソッドです。
メソッドもインスタンスオブジェクトです。このことは、メソッドが変数に代入できることからもわかります。メソッドもインスタンスオブジェクトであることを強調するためにこのように表現しているものと思われます。
# Step1. インスタンスオブジェクトの生成 change_name_method = girl_friend.change_name # Step2. メソッドの呼び出し change_name_method('岩倉玲音') # Step3. 実際には自動的にこれが呼びされている。 change_name_method.__func__( change_name_method.__self__, '岩倉玲音') # Step4. # __func__ は GirlFriend.change_name を # __self__ は girl_friend オブジェクトを # それぞれ参照している。 change_name_method.__func__ is GirlFriend.change_name change_name_method.__self__ is girl_friend
インスタンスメソッドオブジェクトが呼び出される際、
根底にある関数 (__func__) が呼び出されます。
このとき、クラスインスタンス (__self__) が
引数リストの先頭に挿入されます。
呼び出し可能型 (callable type) > インスタンスメソッド
インスタンスを通してメソッド (クラスの名前空間内で定義された関数) にアクセスすると、 特殊なオブジェクトが得られます。それは束縛メソッド (bound method) オブジェクトで、 インスタンスメソッド (instance method) とも呼ばれます。 呼び出された時、引数リストに self 引数が追加されます。 束縛メソッドには 2 つの特殊読み出し専用属性があります。 m.__self__ はそのメソッドが操作するオブジェクトで、 m.__func__ はそのメソッドを実装している関数です。 m(arg-1, arg-2, ..., arg-n) の呼び出しは、 m.__func__(m.__self__, arg-1, arg-2, ..., arg-n) の呼び出しと完全に等価です。
4.12.4. メソッド - Python 標準ライブラリ
ちなみに Python 2 の頃はクラスオブジェクトの関数は unbound method と呼ばれていました。Python 3 では単純に関数 function になりました。bound 束縛しているのは self を指しています。
>>> # Python 3 >>> girl_friend.change_name <bound method GirlFriend.change_name ...> >>> >>> GirlFriend.change_name <function GirlFriend.change_name
>>> # Python 2 >>> girl_friend.change_name <bound method GirlFriend.change_name ...> >>> >>> GirlFriend.change_name <unbound method GirlFriend.change_name> >>>
Python 2 の unbound method は、そのクラスのインスタンスだけが、第一引数に代入できるように限定された method (関数)です。しかし、特定のクラスのインスタンスに限定しても、コーディングをする上であまり意味がないことがわかり、Python 3 では削除されました。
Python3000 では、非束縛メソッドの考え方が削除され、"A.spam" という式は、通常の関数オブジェクトを返すようになった。結局、最初の引数をAのインスタンスの限定しても、問題の分析の役に立つことはあまりない、ということになったのである。逆に高度な使い方を使用とした場合に邪魔になることも多かったからである。この高度な使い方というのは「duck typing self」という適切な名前で呼ばれている。
すべてをファーストクラスに - The History of Python.jp
まとめ
オブジェクトからメソッドを呼び出すと、実は関数からメソッドへの変換が行われます。生成されたメソッドの属性には、呼び出したオブジェクトと関数が代入されています。
では、誰がインスタンスメソッドオブジェクトを生成しているのでしょうか?答えは、ディスクリプタです。いきなりディスクリプタという抽象的な機能について、触れてもちょっと厳しいかなと思うので、もう少し実際にメソッドを操作して、メソッドが生成される動作をなんとなく把握しておきたいと思います。
# 参照するだけでインスタンスメソッドオブジェクトが生成される。
change_name_method = girl_friend.change_name
7. メソッドの呼び出しを高速化してみる。
最初からメソッドを変数に代入しておくと少しだけ処理を速くできます。
なぜ、速くなるかと言うと、属性を参照するたびに実行される関数からメソッドへの変換という処理を、省略できるからです。
$ # 変数の参照 $ python -m timeit -s "pop=[i for i in range(10000000)].pop" "pop()" 10000000 loops, best of 3: 0.0792 usec per loop $ $ # メソッドの参照 $ python -m timeit -s "lst=[i for i in range(10000000)]" "lst.pop()" 10000000 loops, best of 3: 0.115 usec per loop $
なお、関数オブジェクトからインスタンスメソッドオブジェクトへの変換は、インスタンスから属性が取り出されるたびに行われます。場合によっては、属性をローカル変数に代入しておき、そのローカル変数を呼び出すようにするのが効果的な最適化になります。
呼び出し可能型 (callable type) > インスタンスメソッド
可読性が落ちるので使っていいのか疑問だったのですが copy モジュールの中で、このような実装をしているところを見かけました。このコードは list を deepcopy するコードです。
def _deepcopy_list(x, memo, deepcopy=deepcopy): y = [] memo[id(x)] = y append = y.append for a in x: append(deepcopy(a, memo)) return y
ただ、同じ copy モジュールの中でもこのような書き方がなされていないところもあります。実行時間を計測して、どのくらい効果が見込めるのかと、可読性を天秤にかける必要があるかなと思います。
deepcopy は遅いんやって stackoverflow で文句を言われてるのを見かけました。自分も昔使っていて、なんか重いなって印象がありました。履歴を見たら割と最近 2016/03/16 に、このような書き方に変更されていたので、天秤をかけた結果の苦肉の策かなと思ったりもします。
deepcopy() is extremely slow
Ask Question - stackoverflow
◯ リスト内包表記が速い理由
また、一般に for 文よりもリスト内包表記が速いです。これは append メソッドを呼び出す必要がなくなるからです。
"""乱数のリストを生成する""" import random random_list1 = [random.randint(0, 999) for _ in range(1000)] random_list2 = [] for i in range(1000): random_list2.append(random.randomint(0, 999))
8. 疑問: 関数からメソッドに変換って何をしているの?
「生成」それとも「切替」?
答え: 新しいインスタンスメソッドオブジェクトが、その都度「生成」されています(インスタンス化されています)。
キャッシュを作って、インスタンスメソッドオブジェクトに参照先を「切替」るだけというようなことはしていません。メソッドの参照は、どうやら見た目より重たい処理のようです。
なぜ、調べたかというと、マニュアルで「変換」という言葉が使われていたので、「切替」なのか「生成」なのか気になったからです。
関数オブジェクトからインスタンスメソッドオブジェクトへは 変換 は、 インスタンスから属性が取り出されるたびに行われます。
呼び出し可能型 (callable type) > インスタンスメソッド
◯ identity
これを調べるためにインスタンスメソッドオブジェクトを生成した時に identity が、どのように変化するかを通して確認しました。
identity というのは、オブジェクトが持っている番号です。id 関数で調べることができます。
以下の記事は identity について説明せいて頂いております。
全部読んでいただく必要は全くないのですが、
全てのオブジェクトは identity という数字を持っていることだけを、
なーんとなく押さえておいていただければと思います。
Python の id 関数, identity ってなに? - いっきに Python に詳しくなるサイト
1. 参照するだけ
identity は変化しませんでした。
id(girl_friend.change_name) id(girl_friend.change_name)
>>> id(girl_friend.change_name) 4533991496 >>> >>> # identity は変化しない >>> id(girl_friend.change_name) 4533991496 >>>
このことから、インスタンスメソッドオブジェクトは呼び出されているたびに、生成されてないと言えるでしょうか?
identity が同じだったということは2つの可能性があります。 1つ目は、同じインスタンスメソッドオブジェクトを使いまわしている可能性。 2つ目は、インスタンスメソッドオブジェクトを生成して破棄して、 同じ identity を使ってインスタンスメソッドオブジェクトを生成している可能性。 もう少し追いかけてみたいと思います。
2. 属性をローカル変数に代入しておき、そのローカル変数を呼び出すようにする
identity が変化しました。
新しいインスタンスオブジェクトが生成さたことが、わかります。 どうやら呼び出される度にインスタンスオブジェクトが生成されていそうです。 また、オブジェクトの生成、破棄は、変数への代入と何らかの関係がありあそうです。
id(girl_friend.change_name) # 変数にインスタンスメソッドオブジェクトを代入すると... new_change_name_method = girl_friend.change_name id(girl_friend.change_name) id(new_change_name_method)
>>> id(girl_friend.change_name) 4438100040 >>> >>> # 変数にインスタンスメソッドオブジェクトを代入しておくと... ... new_change_name_method = girl_friend.change_name >>> >>> # 次に呼び出した時には >>> # が変化する。 >>> id(girl_friend.change_name) 4438100104 >>> >>> id(new_change_name_method) 4438100040 >>>
3. 別のインスタンスメソッドオブジェクトを変数に保存する。
identity が変化しました。
どうやら呼び出される度にインスタンスオブジェクトが生成されているのは、ほぼ確実そうです。
id(girl_friend.change_name) class C(object): def f(self): pass o = C() m = o.f id(m) id(girl_friend.change_name)
>>> id(girl_friend.change_name) 4350253192 >>> >>> class C(object): ... def f(self): ... pass ... >>> o = C() >>> m = o.f >>> id(m) 4350253192 >>> >>> id(girl_friend.change_name) 4351437192 >>>
◯ ガベレージコレクション
なぜ、1 では、インスタンスメソッドオブジェクトが、インスタンス化されたあとにすぐに削除されたのでしょうか? それは、どの変数にも代入されていなかったので、もう使われることはないオブジェクトだと判断されたからです。
使わなくなったオブジェクトが保存されたメモリから解放することは、とても重要です。 なぜならオブジェクトはメモリを占有してしまうからです。 使わなくなったオブジェクトを回収する機能をガベレージコレクションと言います。
1. 参照カウント
CPython では、参照カウントを使ってガベレージコレクションを実装しています。オブジェクトが、変数あるいは属性に代入されている数を保存しておきます。例えば、オブジェクトが変数 a に代入されれば、参照カウントを1つ増やします。また、変数 a を参照できなくなると、参照カウントを -1 減らします。参照カウントが 0 になると、オブジェクトが参照できなくなった、今後そのオブジェクトを利用することは無くなった、と判断して、オブジェクトを破棄します。
参照カウント法 - Python/C API リファレンスマニュアル
今日の計算機は有限の (しばしば非常に限られた) メモリサイズしか持たないので、参照カウントは重要な概念です; 参照カウントは、あるオブジェクトに対して参照を行っている場所が何箇所あるかを数える値です。... 中略 ... あるオブジェクトの参照カウントがゼロになると、そのオブジェクトは解放されます。... 後略
1, 2 では同じ identity を使ってインスタンス化→削除→インスタンス化→削除を繰り返していました。
なんでインスタンスメソッドオブジェクトの identity が変化しなかったのか?というのは、ひとつ疑問です。
おそらくそういうメモリの確保の実装をしているからだと思います。
ヒープとスタック - ユークエスト株式会社
malloc - Wikipedia
ほとんどの Python は C 言語で実装されています。C 言語で実装された Python を CPython と言います。 CPython の実装では は、メモリのアドレスです。 データを保存する場所であるメモリには、住所のような番号が割り振られています。 その住所のような番号をアドレスと言います。 この説明では、全くピンと来ないと思うので C言語, ポインタ などで検索するといいかもしれません。
1, 2, では、アドレスが 4533991496 のメモリを使って、インスタンス化してはガベレージコレクションで 4533991496 のメモリを解放して、また解放されたアドレスが 4533991496 のメモリを使ってインスタンス化してはガベレージコレクションでアドレスが 4533991496 のメモリを解放するという動作を繰り返していました。
2. 循環参照ガベージコレクション
ちなみに参照カウント法では、相互に参照し合うようなケースではうまく動作しません。 そのような循環参照を検知するための別の GC が必要になるようです。
参照カウント法 - Wikipedia
オブジェクト同士が互いに参照しあうなど、参照が循環している場合、 参照カウントが0にならないためにオブジェクトが破棄されないという問題がある。(詳しくは後述)
参照カウント法 - Python/C API リファレンスマニュアル
言うまでもなく、互いを参照しあうオブジェクトについて問題があります; 現状では、解決策は "何もしない" です。(ワイの注記: Python の参照カウント法が、循環参照に対して何もしないのであって、Python には循環参照を捉える機構はあるようです。)
現在の CPython 実装では参照カウント(reference-counting) 方式を使っており、(オプションとして) 循環参照を行っているごみオブジェクトを遅延検出します。この実装ではほとんどのオブジェクトを到達不能になると同時に処理することができますが、循環参照を含むごみオブジェクトの収集が確実に行われるよう保証しているわけではありません。
3.1. オブジェクト、値、および型 - Python 言語リファレンス
循環参照 GC の話をしてくれています。
死ぬほどわかりやすく書いてくれていると思うのですが、自分はまだ 1 mm も理解できていません。
Compact PyGC_HEAD
◯ まとめ
メソッドは呼び出されるたびにインスタンス化していました。これによって動的にクラスオブジェクトの関数が変更されると、すぐにメソッドも変更されるようになっています。
9. 疑問: 誰が関数からメソッドに変換しているの?
答え: ディスクリプタ
# これが呼び出された時に girl_friend.change_name('岩倉玲音') # これが呼び出されています girl_friend.__class__.__dict__['change_name'].__get__(girl_friend, type(girl_friend))('サーバルちゃん')
◯ ディスクリプタ
コードを書く人が、関数を定義する。そして、関数が代入されたクラスオブジェクトの属性を参照されると、誰かが、関数の第一引数に関数を呼び出してきたインスタンスオブジェクトを代入して関数を実行する。メソッドを呼び出した時に、一体誰が、このような動作を実装しているのでしょうか?
ディスクリプタを理解すると、公式マニュアルの説明が理解できて、その動作がなんとなく把握することができます。
Python のオブジェクト指向機能は、関数に基づく環境の上に構築されています。非データデスクリプタを使って、この 2 つは(関数とメソッドは)シームレスに組み合わされています。
デスクリプタ HowTo ガイド
ディスクリプタについて、ごく手短に説明すると、クラス変数が参照された時に、もしそのクラス変数に束縛されたオブジェクトが __get__ メソッドを持っていると __get__ メソッドが呼び出されています。
# インタラクティブシェルに # コピペして実行して見てください。 class C(object): def __get__(self, obj, objtype): print(self) print(obj) print(objtype) return 'Hello, world!' class D(object): c = C() d = D() # インスタンス束縛 # d.c 属性を参照しただけで # C.__get__ メソッドが実行されます。 print(d.c) # クラス束縛 # D.c 属性を参照した時にも # C.__get__ メソッドが実行されます。 print(D.c)
>>> # インスタンス束縛 ... # d.c 属性を参照しただけで ... # C.__get__ メソッドが実行されます。 ... print(d.c) <__main__.C object at 0x105293198> <__main__.D object at 0x105293160> <class '__main__.D'> Hello, world! >>> >>> # クラス束縛 ... # D.c 属性を参照した時にも ... # C.__get__ メソッドが実行されます。 ... print(D.c) <__main__.C object at 0x105293198> None <class '__main__.D'> Hello, world! >>>
インスタンス束縛 (Instance Binding)
オブジェクトインスタンスへ束縛すると、a.x は呼び出し type(a).__dict__['x'].__get__(a, type(a)) に変換されます。クラス束縛 (Class Binding)
クラスへ束縛すると、A.x は呼び出し A.__dict__['x'].__get__(None, A) に変換されます。
◯ 疑似コード
関数オブジェクト function の __get__ メソッドを次のように定義することで、メソッドを簡単に実装できます。 このコードは、CPython の C 言語のコードを Python で書き直した疑似コードだそうです。
純粋な Python では、メソッドはこのように動作します。
In pure python, it works like this:
class Function(object): ... def __get__(self, obj, objtype=None): "Simulate func_descr_get() in Objects/funcobject.c" if obj is None: return self return types.MethodType(self, obj)
本当に疑似コードと同じような動作をするのでしょうか?
ディスクリプタとクロージャを組み合わせて、ごく簡単ではありますが、同じことができることを示してみたいと思います。
◯ 疑似コードを再現してみる。
__get__ メソッドの動作を、なんとなく理解するためにサンプルコードを用意しました。 インタラクティブシェルにペチペチ コピペできるようにしてあります。 各 Step のコードをコピペして例外がでる箇所をなんとなく眺めて見ていただければと思います。
Step 0. メソッドは動的に追加できます。
class GirlFriend(object): def __init__(self, name): self.name = name def change_name(person, name): person.name = name GirlFriend.change_name = change_name girl_friend = GirlFriend('岩倉玲音') # 0.1. 関数呼び出し GirlFriend.change_name(girl_friend, 'サーバルちゃん') print(girl_friend.name) # 0.2. メソッド呼び出し girl_friend.change_name('バラライカ') print(girl_friend.name)
>>> # 0.1. 関数呼び出し ... GirlFriend.change_name(girl_friend, 'サーバルちゃん') >>> print(girl_friend.name) サーバルちゃん >>> >>> # 0.2. メソッド呼び出し ... girl_friend.change_name('バラライカ') >>> print(girl_friend.name) バラライカ >>>
Step 1. 問題
ここで問題です。 関数をラップした Function クラスでを、関数、そしてメソッドとして使えるようにするにはどうすればよいでしょうか?
class GirlFriend(object): def __init__(self, name): self.name = name def change_name(person, name): person.name = name class Function(object): def __init__(self, function): self.function = function GirlFriend.change_name = Function(change_name) girl_friend = GirlFriend('岩倉玲音') # 1.1. 関数呼び出し <--- そもそも、関数呼び出しができない。 GirlFriend.change_name(girl_friend, 'サーバルちゃん') print(girl_friend.name) # 1.2. メソッド呼び出し <--- メソッド呼び出しも当然できない。 girl_friend.change_name('バラライカ') print(girl_friend.name)
>>> # 1.1. 関数呼び出し <--- そもそも、関数呼び出しができない。 ... GirlFriend.change_name(girl_friend, 'サーバルちゃん') Traceback (most recent call last): File "<stdin>", line 2, in <module> TypeError: 'Function' object is not callable >>> print(girl_friend.name) 岩倉玲音 >>> >>> # 1.2. メソッド呼び出し <--- メソッド呼び出しも当然できない。 ... girl_friend.change_name('バラライカ') Traceback (most recent call last): File "<stdin>", line 2, in <module> TypeError: 'Function' object is not callable >>> print(girl_friend.name) 岩倉玲音 >>>
Step 2. まずは関数として使えるようにする。
Function クラスのインスタンスオブジェクトは、関数として呼び出せません。 インスタンスオブジェクトを関数として呼び出せるようにするには __call__ メソッドをクラスで定義するとできるようになります。
class GirlFriend(object): def __init__(self, name): self.name = name def change_name(person, name): person.name = name class Function(object): def __init__(self, function): self.function = function def __call__(self, *args, **kwargs): return self.function(*args, **kwargs) GirlFriend.change_name = Function(change_name) girl_friend = GirlFriend('岩倉玲音') # 2.1. 関数呼び出し GirlFriend.change_name(girl_friend, 'サーバルちゃん') print(girl_friend.name) # 2.2. メソッド呼び出し <--- メソッド呼び出しができない。 girl_friend.change_name('バラライカ') print(girl_friend.name)
>>> # 2.1. 関数呼び出し ... GirlFriend.change_name(girl_friend, 'サーバルちゃん') >>> print(girl_friend.name) サーバルちゃん >>> >>> # 2.2. メソッド呼び出し <--- メソッド呼び出しができない。 ... girl_friend.change_name('バラライカ') Traceback (most recent call last): File "<stdin>", line 2, in <module> File "<stdin>", line 6, in __call__ TypeError: change_name() missing 1 required positional argument: 'name' >>> print(girl_friend.name) サーバルちゃん >>>
__call__ の実践的な使い方についても Effective Python の「3 章クラスと継承 項目23: 単純なインタフェースにはクラスの代わりに関数を使う」に書かれていました。
Step 3. メソッドとしても使えるように
__get__ メソッドをクラスで定義するとできるようになります。
class GirlFriend(object): def __init__(self, name): self.name = name def change_name(person, name): person.name = name class Function(object): def __init__(self, function): self.function = function def __call__(self, *args, **kwargs): return self.function(*args, **kwargs) def __get__(self, obj, objtype): def method(*args, **kwargs): return self(obj, *args, **kwargs) return method GirlFriend.change_name = Function(change_name) girl_friend = GirlFriend('岩倉玲音') # 3.1. 関数呼び出し <--- 今度は関数呼び出しができなくなった。 GirlFriend.change_name(girl_friend, 'サーバルちゃん') print(girl_friend.name) # 3.2. メソッド呼び出し girl_friend.change_name('バラライカ') print(girl_friend.name)
>>> # 3.1. 関数呼び出し <--- 今度は関数呼び出しができなくなった。 ... GirlFriend.change_name(girl_friend, 'サーバルちゃん') Traceback (most recent call last): File "<stdin>", line 2, in <module> File "<stdin>", line 10, in method File "<stdin>", line 6, in __call__ TypeError: change_name() takes 2 positional arguments but 3 were given >>> print(girl_friend.name) 岩倉玲音 >>> >>> # 3.2. メソッド呼び出し ... girl_friend.change_name('バラライカ') >>> print(girl_friend.name) バラライカ >>>
Step 4. メソッドとしても関数としても使えるように
なぜ関数呼び出しができなくなったかというと関数呼び出しの時にも __get__ メソッドが呼ばれてしまっているからです。 そのため少し細工をしてあげる必要があります。
class GirlFriend(object): def __init__(self, name): self.name = name def change_name(person, name): person.name = name class Function(object): def __init__(self, function): self.function = function def __call__(self, *args, **kwargs): return self.function(*args, **kwargs) def __get__(self, obj, objtype): if obj is None: return self def method(*args, **kwargs): return self(obj, *args, **kwargs) return method GirlFriend.change_name = Function(change_name) girl_friend = GirlFriend('岩倉玲音') # 4.1. 関数呼び出し GirlFriend.change_name(girl_friend, 'サーバルちゃん') print(girl_friend.name) # 4.2. メソッド呼び出し girl_friend.change_name('バラライカ') print(girl_friend.name)
>>> # 4.1. 関数呼び出し ... GirlFriend.change_name(girl_friend, 'サーバルちゃん') >>> print(girl_friend.name) サーバルちゃん >>> >>> # 4.2. メソッド呼び出し ... girl_friend.change_name('バラライカ') >>> print(girl_friend.name) バラライカ >>>
Step 5. 疑似コードと同じコードに
これでやっと疑似コードと同じようなコードが出てきました。 staticmethod と classmethod を同じようにディスクリプタで書いたら、いい練習になるかもしれません。
import types class GirlFriend(object): def __init__(self, name): self.name = name def change_name(person, name): person.name = name class Function(object): def __init__(self, function): self.function = function def __call__(self, *args, **kwargs): return self.function(*args, **kwargs) def __get__(self, obj, objtype): "Simulate func_descr_get() in Objects/funcobject.c" if obj is None: return self return types.MethodType(self, obj) GirlFriend.change_name = Function(change_name) girl_friend = GirlFriend('岩倉玲音') # 4.1. 関数呼び出し GirlFriend.change_name(girl_friend, 'サーバルちゃん') print(girl_friend.name) # 4.2. メソッド呼び出し girl_friend.change_name('バラライカ') print(girl_friend.name)
>>> # 4.1. 関数呼び出し ... GirlFriend.change_name(girl_friend, 'サーバルちゃん') >>> print(girl_friend.name) サーバルちゃん >>> >>> # 4.2. メソッド呼び出し ... girl_friend.change_name('バラライカ') >>> print(girl_friend.name) バラライカ >>>
MethodType は、ユーザ定義クラスのインスタンスのメソッドの型です。
import types class GirlFriend(object): def change_name(self, name): self.name = name girl_friend = GirlFriend() isinstance(girl_friend.change_name, types.MethodType) # True
types.MethodType
ユーザー定義のクラスのインスタンスのメソッドの型です。
◯ PEP 20 - The Zen of Python
Python は、関数とディスクリプタを組み合わせて、メソッドを実装しました。Python は、ごっちゃにひとまとめにしたメソッド定義文を定めて、メソッドを実装はしませんでした。
Complex is better than complicated.
ごっちゃにひとまとめした実装するよりも、複数のパーツを組み合わて実装する方が良い。
Simple is better than complex.
複数のパーツを組み合わて実装するよりも、各パーツをバラバラに実装する方が良い。(複数のパーツを組み合わせて1つのものを作って1つのものに同時に複数のことをさせるよりも、複数のパーツをバラバラして1つのパーツには1つのことをさせる方が良い。)
PEP 20 -- The Zen of Python
プログラマが持つべき心構え (The Zen of Python) - Qiita
メソッド呼び出し以外のディスクリプタの用途としては、Java で言う所の getter とか setter を書かずに、直接、属性を参照したり属性を代入するだけで済ませることができるようになります。身近な具体例としては Django の model で使われている気配があります。
Getter/Setterは悪だ。以上。 - To Be Decided
ディスクリプタの用途だけを手短に述べました。こんな説明では "何言ってんだ、お前?" みたいな気分かもしれません。 ディスクリプタを理解するために、自分は Effective Python の 「4 章 メタクラスと属性」が、いいかなと思います。
特に使用例を示してくれているので、かなりありがたかったです。 1, 2, 3 章と 4 章は関係ないので、 4 章だけ読むことができます。この 30 ページの 4 章のためだけに 3,000 円以上を払う価値は十分にあると思います(理解したとは言ってない)。
まとめ
後編で言いたかったことは、次の2つのことだけです。
- メソッドの定義は、関数定義文で書いた方がいろんなことができます。
- 関数からメソッドへの変換はディスクリプタが使われています。
たった2つのことだけですが、かなりの労力を費やしてしまいました。
こんなに長い文章に付き合っていただいた方が、もしいらっしゃったなら幸いです。
ありがとうございました。