参照渡しと値渡し
go言語を勉強し始めてから久しぶりにポインタを利用することになったので改めてポインタやらアドレスやらのややこしいとこを自分なりに見直しました.
値渡し
go言語の場合
package main
import "fmt"
type Person struct {
Name string
age int
}
func growUp(person Person) {
person.age++
fmt.Println("innerFunc", person.age)
}
func main() {
var person Person
person.Name = "とくまる"
person.age = 20
fmt.Println("before", person.age)
growUp(person) // 1コ歳をとらせる
fmt.Println("before", person.age)
}
このプログラムを実行してみます
% go run person.go
before 20 // 関数実行前
innerFunc 21 // 関数内での出力
after 20 // 関数実行後のmain()内での出力
出力結果は上記のようになり,関数内では年齢が1コ増えてますが,afterの出力を見てわかるように関数に渡したperson
の値は変わっていません!これが値渡しで,person
のコピーを作って関数に渡しているような感じです
参照渡し
次に参照渡し
package main
import (
"fmt"
)
type Person struct {
Name string
age int
}
func growUp(person Person) {
person.age++
fmt.Println("innerFunc:", person.age)
}
//変更点/////////////////////////////////
func fixGrowUp(person *Person) {
person.age++
fmt.Println("innerFunc:", person.age)
}
/////////////////////////////////////////
func main() {
//var person Person
person := &Person{}
person.Name = "とくまる"
person.age = 20
fmt.Println("before:", person.age)
//growUp(person)
fixGrowUp(person)
fmt.Println("after:", person.age)
}
新しくfixGrowUp
という関数を作りました.出力結果は以下のようになります.
% go run person.go
before: 20 // 関数実行前
innerFunc: 21 // 関数内での出力
after: 21 // 関数実行後のmain()内での出力
今度は関数実行後の出力も歳をとっています.これが参照渡しです.アドレスのポインタ渡して,関数に渡したperson
を書き換えてます.
いや
知ってたでって感じの人が多いと思いますが,C言語に挫折し,pythonやらjavascriptやらでポインタとおさらばしてなんとなくプログラミングしてたらすっかり気にしなくなってました。(笑)
Pythonでは?
ここからが本題です!
前置きが長くなりましたが,伝えたかったのはここからです.参照渡しとかほとんど意識せずプログラム書いてたけどpythonだとどうなってるの?って思って調べてみました.
pythonは全て参照渡し
らしいです.そして,書き換わる型と書き変わらない型があるらしいです.以下を見てください.
def change_int(age):
age = 21
def main():
age = 20
data = {"name": "とくまる", "age": 20}
print("type", type(age))
print("before: ", age)
change_int(age)
print("after: ", age)
main()
% python person.py
type <class 'int'>
before: 20
innerFunc 20
after: 21
これは,int型のage
を書き換えたものですがpythonは全て参照渡ししてるのに書き換わってないですね.参照渡しするけど「書き換えるならコピーして元のと別のやつ書き換えるよ」みたいな?次にdict型
def change_int(age):
age = 21
print("innerFunc", age)
################変更点###########################
def change_dict(person):
person["age"] = 21
print("innerFunc", person["age"])
##################################################
def main():
age = 20
person = {"name": "とくまる", "age": 20}
print("type", type(person))
print("before: ", person["age"])
# change_int(age)
change_dict(person)
print("after: ", person["age"])
main()
今度はdict型の中の変数を書き換える関数を書きました.出力結果は以下のようになります.
% python person.py
type <class 'dict'>
before: 20
innerFunc 21
after: 21
書き換わってますね.dict型は書き換えちゃうみたいですね.書き換わるやつ(mutable)と書き変わらんやつ(immutable)は以下のようになっています.
- 書き換えできない型
- int, float, str, tuple, bytes, frozenset ..
- 書き換え可能な型
- list, dict, set, bytearray ..
なんとなくpythonが簡単だからと思ってプログラミングしてる方は以上のことを意識してプログラミングしていきましょう!
おまけ
listがmutableだから
def implement(awesome_list):
for elem in awesome_list:
elem += 1
def main():
awesome_list = [1, 2, 3] # intのリスト
implement(awesome_list)
print(awesome_list)
上記のようにして書き換えたくなりますが,ここで生成されるelem
はawesome_list
の要素のコピーなので,
elem += 1
では要素のコピーを書き換えてることになります.したがって,main()での出力結果は1, 2, 3
のままです.解決方法としては
- 関数内で新しい空のリストを定義して,for文の中でappendする
- リストの要素に直接アクセスする
for i in range(len(awesome_list)):
awesome_list[i] += 1
まあざっくりと,(1)「新しいリスト作って書き換えた要素追加する」か,(2)「リストの要素の番号指定してちまちまアクセスして書き換えてく」かって感じですかね個人的に(2)だとpythonのよさみたいなものが消えてるような気がするので(1)をおすすめします
* ここではリストの要素がint型(immutable)なので書き換えれませんでしたが,リストの要素がmutableの場合は書き換え可能です!
新しい言語を始める際のチェックポイントとして「値渡しと参照渡し」のルールについてその言語ではどうなってるのかっていうのはチェックしておくとスムーズに正確にプログラミングできると思います!