Hatena::Groupbugrammer

蟲!虫!蟲!

Esehara Profile Site (by Heroku) / Github / bookable.jp (My Service)
過去の記事一覧はこちら

なにかあったら「えせはら あっと Gmail」まで送って頂ければ幸いです。
株式会社マリーチでは、Pythonやdjango、また自然言語処理を使ったお仕事を探しています

 | 

2011-12-10Python Advent Calender(全部俺)::十日目

[]参照渡し地獄変 -- Pythonにおける参照 00:27

 Pythonを勉強している人が躓くポイントとしては、「Pythonの代入は、基本的に参照である」という点でしょう。Pythonにおける代入とは、「あるオブジェクトが格納されている場所を表すために作られるモノ」だということとして考えたらいいのかなーと思ったり。詳しくは下のサイトが図によって書かれてあるのでわかりやすいのかな、という気がします。

 no title

 原理的な話は上を見て頂くとして、ここでは基本的な参照渡しについての話を考えていきたいなと思います。

参照渡しになるのか、値渡しになるのかの違い

 原理的な話としては、「変更不可能なオブジェクト」か「変更可能なオブジェクトか」という違いによって、その挙動が変わってきます。変更不可能なオブジェクトとは、具体的には「文字列、数字、タプル」がわかりやすいかと思います。そして、変更可能なオブジェクトとは、具体的には「配列」になると思います。

配列引数に渡すと、元の配列が変更される

 次のような関数を考えてみましょう。配列引数に渡されると、その先頭の要素を変えて変更するという関数ですね。

def test(list):
     result = list
     result[0] = "FooBar"
     return result

 これで正しそうに見えたとしても、実際は下のようになってしまいます。

>>> a = [1,2,3]
>>> print test(a)
['FooBar', 2, 3]
>>> a
['FooBar', 2, 3]
>>> 

 なぜかというと、配列は、元のオブジェクトへの参照が渡されています。わかりやすい例えは思いつきませんが、例えば本棚を配列として考えると、「○○番目の棚」という情報が変数に入っているのであって、「棚の順番そのもの」が入っているわけではないようです。なので、こういうオバケみたいなことが出来ます。

>>> a = []
>>> a.append(a)
>>> a[0][0][0][0].... #以下無限

 要するに、aの最初に「aの棚」を追加してしまったために、aの一番目を開くと、aという配列を開くという結果になってしまいます。ですので、懸命なPythonistaの人は、copyというモジュールを使います。またはスライスを使うのもいいかもしれませんね:]。

>>> a = []
>>> b = a[:]

404 Not Found

ジェネレーターの罠

 さて、この参照渡しなのですが、これはジェネレーターでも同じことが言えます。例えば、ある値までジェネレーターを回して、そのジェネレーターをコピーしたいなと思ったとき、こういう風に渡してやると、途端に失敗したりします。

>>> def test_yield():
...     x = 0
...     while 1:
...             x += 1
...             yield x
... 
>>> x = test_yield()
>>> x.next()
1
>>> y = x
>>> y.next()
2
>>> x.next()
3

 さすがに、オブジェクトを理解している人であるならば、この挙動は理解できるとは思うのですが、解説をしておくと、xとはあくまでも任意のオブジェクトについている名前に過ぎません。例えば、俺が「似非原」と共に、本名の「ほにゃらら」と結びついているように、単に「xと名がついたオブジェクトは、同時にyという名前もありますよ」という形にしているにすぎません。なので、例えば複数のオブジェクトに分岐させたい時点で、itertoolsのteeを使うという方法があります。

>>> import itertools
>>> a,b = itertools.tee(x)
>>> a.next()
12
>>> b.next()
12

 ただ、注意しなければならないのは、元のxの扱いかたで、xはaかb、大きなほうのどちらかの値を操作してしまう挙動を確認しました。さらに悪いことに、小さいほうは、その値をすっ飛ばして、次の値を使おうとします。なので、xは放置しておき、できるだけa,bを使うほうがいいんじゃないのかなという気はします。この辺はもうちょっと闇Python的に深く潜った方が面白いことが出てきそうですが、今回はさわりだけにしておきます。(自分もそれほどいじってはいないので)

関数の参照ができると何がうれしいか

 例えば、関数で「()」を使わないと、そのインスタンスへの参照自体が渡されます。下のような状態ですね。

>>> def print_hello():
...     print "Hello!"
... 
>>> print_hello
<function print_hello at 0xb77d9b54>

 これに対して、次のように書くことが可能になります。

>>> def pre_monad(f,flug):
...     if flug:
...             f()
...     else:
...             print "Nothing"
... 

 これは、どういうことかというと、fに、引数として渡された関数オブジェクトを束縛し、実行することが可能になります。

>>> pre_monad(print_hello,True)
Hello!
>>> pre_monad(print_hello,False)
Nothing

 あれ、Monad……?

 To be Continued.

kamimura7kamimura72011/12/10 00:49"変更不可能なオブジェクトとは、具体的には「配列」"
前後から考えると
"変更可能なオブジェクトとは、具体的には「配列」"

nisemono_sannisemono_san2011/12/10 00:50報告Thanks!! Fix しました :)

 |