人気ブログランキング | 話題のタグを見る

python2.7 プログラミング作法

python プログラミング作法
 リストなど変数に代入するのは、参照のコピーでしかない。
 これは、関数、メソッドの呼び出しで引数を渡すのも同じこと。
 渡したつもりで、参照がコピーされただけなので、メソッドの中で引数が変化すれば参照元も変化して見える。
 これを知らなければ、
>>> a=[1,2,3]
>>> b=a
>>> b
[1, 2, 3]
>>> b[1]=11
>>> a
[1, 11, 3]
>>> b
[1, 11, 3]
>>>

 という、不可解な現象に悩むことになる。
 これは、bには、aから[1,2,3]への参照と同じ参照が渡されるだけだからだ。
 これだけ見ると、どうしてこうゆう仕様になっているのか不思議だが、メソッドの引数渡しでもこの動きはあって、
def func_a(rec):

rec[1]=22

a=[1,2,3]
print a
func_a(a)
print a

ーーーー
[1, 2, 3]
[1, 22, 3]

 からわかるとおり、メソッドの受け取った引数recはローカル変数かと思いきや、参照しか渡されていないので、メソッドの中で引数を変更すると、メインルーチン側でも変数が変化していることがわかる。

 そうすると、メソッドと上位ルーチン間での引数、リターン値のやり取り方法にいくつか方法が考えられる。
import time

def func_a(rec):

rec[1]=rec[1]+1
return rec

def func_b(rec):
rec2 = rec[:]
rec2[1]=rec2[1]+1
return rec2

def func_c(rec):

rec[1]=rec[1]+1

def func_d():

rec[1]=rec[1]+1

# way 1
start = time.time()
a=[1,2,3]
for i in range (int(1e6)):
r=func_a(a)
r1=func_a(r)
r2=func_a(r1)
print r2
stop = time.time()
elapsed= (stop - start) * 1e3
print ("elapsed_time:{0}".format(elapsed) + "[ms]")

# way 2
start = time.time()
a=[1,2,3]
for i in range (int(1e6)):
r2=func_a(func_a(func_a(a)))
print r2
stop = time.time()
elapsed= (stop - start) * 1e3
print ("elapsed_time:{0}".format(elapsed) + "[ms]")

# way 2-1
start = time.time()
a=[1,2,3]
for i in range (int(1e6)):
r2=func_b(func_b(func_b(a)))
a=r2
print r2
stop = time.time()
elapsed= (stop - start) * 1e3
print ("elapsed_time:{0}".format(elapsed) + "[ms]")

# way 3
start = time.time()
a=[1,2,3]
for i in range (int(1e6)):
func_c(a)
func_c(a)
func_c(a)

print a
stop = time.time()
elapsed= (stop - start) * 1e3
print ("elapsed_time:{0}".format(elapsed) + "[ms]")

# way 4
start = time.time()
rec=[1,2,3]
for i in range (int(1e6)):
func_d()
func_d()
func_d()

print rec
stop = time.time()
elapsed= (stop - start) * 1e3
print ("elapsed_time:{0}".format(elapsed) + "[ms]")



結果
[1, 3000002, 3]
elapsed_time:1574.20301437[ms]
[1, 3000002, 3]
elapsed_time:1273.03004265[ms]
[1, 3000002, 3]
elapsed_time:2094.46501732[ms]
[1, 3000002, 3]
elapsed_time:1342.04602242[ms]
[1, 3000002, 3]
elapsed_time:1299.40485954[ms]

 一番早いのは、way4の、グローバル変数をメソッドの中でそのまま変更する方法。
 だがこれは何やってるのかソースの可読性が悪い。
 一番遅いのは、way2-1のループの中に代入がある方法。
 普通は、way1の、引数とリターンを使う方法。

 以上で分かるように、出来るだけ、値のコピーをしないほうが速度が向上する。
 
 pythonの解析サイトで、「代入が参照なので、気をつけるように」というのはよく解説されていることだが、どうしてそうなっているのかの理由まで言及しているところが見当たらない。
 現象としては、「代入文の右辺が左辺に影響される」という現象がよく言われるのだが、実はこれは副作用ではないかと思う。
 やりたかったのは、メソッドへの引数をコピーなしに渡したい、ということではなかったかと思う。
 代入文で右辺が左辺に影響されるというのは、バグを誘発する要因でしかないが、メソッドへの引数がコピーなしに渡せるというのは思想だと思う。
 それだけに、その思想は理解しないと、変数のスコープが曖昧だというデメリットばかりが目にについてしまう。
 
 先ほどの実験でも、なるべく値のコピーを発生させないようなコーディングのほうが早いというのが見て取れた。
 引数渡しは、変数がグローバルかローカルかというのより上位の概念のようで、変数名にかかわりなく、参照がコピーされるだけだ。
 
 プログラミング作法としては、メソッドの中の引数は、ローカルではなくグローバルのつもりでアクセスすることが必要だし、必要ない中間の変数をやたらと生成しないようにするほうが良い。
 
 

by studio_do | 2019-12-31 19:58 | 機械室から | Comments(0)
<< 【デジットオリジナル ギタープ... SAB JAM マンドリン編 >>