PHPプログラマでしたがPython初めました。
最近長年連れ添ってきたPHPを捨て(てはないですが)Pythonを
触り始めました。
そこで一つ長年PHPを書いていた僕が
Pythonで書き始めて はまった所なんかをまとめます。
まず初めに
ぱっとPythonのソースを見て思ったこと
- 行末の「;」がないことへの不安感が半端ないです。
⇒ 慣れましょう。 -
if
,for
等にカッコが無いので閉じる部分がどこなの?という不安。
⇒ 慣れましょう。
Pythonでも末尾「;」とか書いても問題はないようですが基本書かないようです。
カッコについては、PHPでは
メインの処理
{
処理の途中でまとまりの良いもの
あわばよく後で関数化するかもしれないもの
}
のように、まとまったもの(まとまりが良いもの)にカッコをつけるという
使い方をしていたので少し不便に感じます。
本編(処理について)
いくつかPHPと違う(PHPの方が異端というお話はおいておいて。。。)ために
はまった点を記載します。
関数の引数
PHPは基本値渡し
pythonは基本参照渡し・・・
と書くと非常に語弊がありますね。
PHPはオブジェクト以外は指定しなければ値渡し
Pythonは基本は参照渡しだけどイミュータブルなオブジェクトについては書き換えできない。
なまじstringやintみたいな単純なものの挙動が同じで
『PHPと同じじゃん!』とか思ってしまったのが
そもそもの間違いでした。
それに加え
Pythonの何がイミュータブルなのかがわからず
なんとなく
dict、listはPHPの hash、arrayと同じ挙動だよね~とか思ってしまったのが大間違い。
PHPの場合
<?php
function echo_str_append_world($pram_str) {
$pram_str .= " world!!";
echo $pram_str . "\n";
}
$str_hello = 'hello';
echo $str_hello . "\n";
echo_str_append_world($str_hello);
echo $str_hello . "\n";
function echo_array_append_world($pram_ary) {
$pram_ary[] = " world!!";
var_dump($pram_ary);
}
$ary_hello = ['hello'];
var_dump($ary_hello);
echo_array_append_world($ary_hello);
var_dump($ary_hello);
> hello
> hello world!!
> hello # 関数抜けると元の値に戻ってる。
> array(1) {
> [0]=>
> string(5) "hello"
> }
> array(2) {
> [0]=>
> string(5) "hello"
> [1]=>
> string(8) " world!!"
> }
> array(1) { # 関数抜けると元の値に戻ってる。
> [0]=>
> string(5) "hello"
> }
Pythonの場合
def echo_str_append_wold(param_str :str) -> None:
param_str = param_str + " world!"
print (param_str)
str_hello = "hello"
print(str_hello)
echo_str_append_wold(str_hello)
print(str_hello)
def echo_array_apped_world(param_ary : list) -> None:
param_ary.append(" world!")
print(param_ary)
ary_hello = ['hello']
print(ary_hello)
echo_array_apped_world(ary_hello)
print(ary_hello)
> hello
> hello world!
> hello # (← 書き換わらない! なーんだPHPと同じか。)
> ['hello']
> ['hello', ' world!']
> ['hello', ' world!'] # (← (゚ω゚)ノノ ナンデ 書き換わってる!!)
イミュータブル: int, str, tupleなど
ミュータブル: list, dictなど
などってあたりが、まだ勉強不足なのですが
とりあえずよく利用するものは こんなとこではないかと。
Pythonを触る場合は基本参照渡し!
イミュータブルなオブジェクトだけ 値渡しと覚えておく。
イテレータの再利用
そもそもPHPでイテレータをあまり使っていなかったのですが。。。
PHPのイテレータだと先頭に巻き戻すというのもあり
再利用が可能だったのです。。。
Pythonの場合進んだものを戻すことはできません。
なので
『デバッグ用に処理に利用したイテレータを
再度ループして中身を確認しよう。。。』
という気持ちで↓のようなコードを書いた僕は
すっかりはまってしまったのでした。
hoge_list = [1,2,3,4,5]
for _number in hoge_list:
print(f"number:{_number}")
for _number in hoge_list:
print(f"Debug number:{_number}")
moge_ite = iter(hoge_list)
for _number in moge_ite: # 本処理のループ
print(f"number:{_number}")
for _number in moge_ite: # デバッグ用のループ
print(f"Debug number:{_number}")
number:1
number:2
number:3
number:4
number:5
Debug number:1
Debug number:2
Debug number:3
Debug number:4
Debug number:5 # ちゃんと処理されてる。確認できた~。
number:1
number:2
number:3
number:4
number:5
# (゚v゚*) あれ?空になってる?値セットできてなかった?
実際にはテーブルのデータ取得だったり、
他の関数からの戻り値を確認する際に
『実際に変数にどんな値がはいっているのかな?』
と思って上記のようなコードを書いて はまりました。
「出力する場所がまずい?」と思って本処理用のループの前で
出力するように変更するとデバッグ出力されるので
(この場合、本処理が動かないわけですが。。。)
さらに気づくのに時間がかかってしまいました。。
わかっていれば全くはまる要素ではないのですが
はまるとなかなか気づけない点でした。
PHPでイテレータを使ってこなかったのも原因ですかね。
内包表記
はまったというか、PHPには全くなかったので
はじめ何のことかさっぱりわからなかったのが
『内包表記!!』
すごく便利なのですが
(短くかけるだけでなく実行速度も速くなるという優れもの)
とにかく所見だと「?なんとなくリストを作ってる?よくわからん!」ってなります。
さらにlambda(無名関数) も一緒に扱うことがあり
より「なんだこれ?」となりましたが。これはぜひ覚えていきたいものです。
# PHP ではこんなイメージ
list_num = []
for _i in range(10):
list_num.append(_i**2)
print(list_num)
# 内包表記!!
list_num = [_i ** 2 for _i in range(10)]
print(list_num)
# 内包表記と lambda(無名関数) !!
list_num = list(map(lambda _i:_i**2, range(10)))
print(list_num)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
その他
Pythonの line_profiler が非常に便利です。
pip install line_profiler
と導入も簡単
例えば上記の内包表記の確認コードをループ数を増やして
プロファイルにかけるならこんな感じ
import line_profiler
def profiling()->None:
range_num = 1000
list_num = []
for _i in range(range_num):
list_num.append(_i**2)
print(list_num)
# 内包表記!!
list_num = [_i ** 2 for _i in range(range_num)]
print(list_num)
# 内包表記と lambda(無名関数) !!
list_num = list(map(lambda _i:_i**2, range(range_num))) #
print(list_num)
pr = line_profiler.LineProfiler()
pr.add_function(profiling)
pr.enable()
profiling()
pr.disable()
pr.print_stats()
Timer unit: 1e-06 s
Total time: 0.009735 s
File: naihou.py
Function: profiling at line 3
Line # Hits Time Per Hit % Time Line Contents
==============================================================
3 def profiling()->None:
4 1 2.0 2.0 0.0 range_num = 1000
5 1 2.0 2.0 0.0 list_num = []
6 1001 1961.0 2.0 20.1 for _i in range(range_num):
7 1000 2533.0 2.5 26.0 list_num.append(_i**2)
8 1 995.0 995.0 10.2 print(list_num)
9
10 # 内包表記!!
11 1 978.0 978.0 10.0 list_num = [_i ** 2 for _i in range(range_num)]
12 1 1036.0 1036.0 10.6 print(list_num)
13
14 # 内包表記と lambda(無名関数) !!
15 1 1226.0 1226.0 12.6 list_num = list(map(lambda _i:_i**2, range(range_num))) #
16 1 1002.0 1002.0 10.3 print(list_num)
リストをfor文で回して作成 ⇒ 4494.0
内包表記 ⇒ 978.0
内包表記+lambda ⇒ 1226.0
と **内包表記すごい!**って実感することうけあいです。
まとめ
PHPの違いというよりも自分の無知をさらしただけみたいになりましたが。。。
ずっと同じ場所で同じ言語で開発を続けていると
別文化に触れたときにいろいろと衝撃を受けますね。
とりあえず、PHPerな人が Pythonを読むという点では
まずは内包表記になれる とずいぶんと読みやすくなる気がします。