#PHPとPython3の違いについての備忘録
PHPとPythonの違いについて、PHPよりの視点で備忘録としてまとめた。
##短くかける
とにかく、短くかきやすいのがPython。下記のように変換しても同じ結果を出力する。この文化には習うより慣れるしかない。
def test(v):
r = []
if v > 0:
for i in range(v):
r += [v - i]
return r
test = lambda v: [v - i for i in range(v)] if v > 0 else []
コードゴルフなるものもあり、コードを短く書く競技がある(詳細はこちら)。
##対話モード
Python3では対話モードという機能が利用でき、1行コードを入力すれば、その内容が評価され、実行される。エラーがある場合、その場で指摘されるため、簡易的な動作確認や計算に便利。
##配列
###リスト(list)、タプル(tuple)、辞書(dict)、集合(set)
PHPで当たり前のように利用していた配列。Python3でもリスト、タプル、辞書、集合といった類似機能があるが利用目的によって、使い分けが必要。
集合を除き、PHPと同様に要素にオブジェクトだけではなく、リスト、タプル、辞書、集合を指定することもできる。
####リスト(list)
PHPで$a = [1, 2, 3];
のような単純な配列であれば、Python3でもa = [1, 2, 3]
で表現できる。多次元も可能。i, j, k = a
とするとそれぞれの要素が取得できる。
ただし、PHPのような参照配列のような機能を実現するには辞書を利用する必要がある。
#####リストのスライス
PHPではarray_slice
があるが、Python3にはそれに以上に便利な機能がある。a = b[m:n]
とすることで、m
番目からn-1
番目の要素を切り出すことができる。m
を省略した場合は先頭から、n
を省略した場合、最後までとなる。m
とn
が負の場合、逆から指定することができる。また、a = b[::m]
拡張スライスなるものがあり、m
個おきに取得できる。
a = [0, 1, 2, 3, 4, 5, 6, 7]
print(a[2:5])
print(a[4:])
print(a[:4])
print(a[-1:-4])
print(a[-1:])
print(a[:-4])
print(a[::3])
print(a[::-1])
[2, 3, 4]
[4, 5, 6, 7]
[0, 1, 2, 3]
[4, 5, 6]
[4, 5, 6, 7]
[0, 1, 2, 3, 4, 5, 6]
[0, 3, 6]
[7, 6, 5, 4, 3, 2, 1, 0]
なお、a[1:6:3]
とすると1番目から5番目まで切り出して、さらに3個おきに取り出せる。
#####文字列からリストへ
PHPに慣れてしまうと気になるのが文字列の扱い。Python3では下記のように配列にすることができるので、文字列も簡単に扱える。
a = "abcd"
b1 = list(a)
b2 = [a[i:i+2] for i in range(0, len(a), 2)]
print(b1, b2)
['a', 'b', 'c', 'd'] ['ab', 'cd']
####タプル(tuple)
大雑把な説明をすると値を変更できないリスト。a = (1, 2, 3)
と定義ができ、i, j, k = a
とすることでリストと同様に要素が取得できる。PHPの場合、複数の関数の戻り値がある場合、わざわざ配列でreturnする必要があったが、Python3の場合、この性質を利用して、return r1, r2, r3
のようにタプルで指定することができる。
####辞書(dict)
PHPの参照配列のようなもの。JSONをパースするときなど利用できる。$a = ['a' => 1, 'b' => 2];
といったPHPの記述を例にすると、Python3ではa = {'a':1, 'b':2}
と表現できる。また、i, j = a
とすると値ではなく、キーが取得できる。b = list(a)
としても、a
のキーで構成された要素でのリストがb
に代入される。
####集合(set)
PHPで要素が重複しない配列を作成するときは、$a = array_unique([1, 1, 2, 3, 3, 3]);
のように毎回array_unique
を施す必要があったが、Python3ではa = {1, 2, 3}
と定義しておけば、以降a.add(1)
としても重複なしが保証される。
####Python3でのリスト、タプル、辞書、集合の使用例
l1 = [1, 2, 3]
print(l1)
l2 = [4, 5, 6, l1]
print(l2)
print(11 + l2)
t1 = ("a", "b", "c")
print(t1)
t2 = ("d", l1, l2)
print(t2)
print(t1 + t2)
d1 = {'a':l1, 1:l2, "c":t1}
s1 = set(l1)
print(s1)
print({'d1':d1, 's1':s1})
[1, 2, 3]
[4, 5, 6, [1, 2, 3]]
[1, 2, 3, [4, 5, 6, [...]]]
('a', 'b', 'c')
('d', [1, 2, 3, [4, 5, 6, [...]]], [4, 5, 6, [1, 2, 3, [...]]])
('a', 'b', 'c', 'd', [1, 2, 3, [4, 5, 6, [...]]], [4, 5, 6, [1, 2, 3, [...]]])
{1, 3, 5}
{1, 3, 5}
{'d1': {1: [4, 5, 6, [1, 2, 3, [...]]], 'c': ('a', 'b', 'c'), 'a': [1, 2, 3, [4, 5, 6, [...]]]}, 's1': {1, 3, 5}}
出力結果では...
と省略されているので、省略しないで表示する方法はこちらが参考になる。
##代入
###イミュータブル・ミュータブル
Python3には、イミュータブル・ミュータブルという概念がある。イミュータブルは変更不可であること、ミュータブルは変更可であるという意味。上述したタプルはイミュータブルで、リスト、辞書、集合はミュータブルとなる。これが「代入」の振る舞いでPHPとPython3で大きく異る。
Python3のこの代入の振る舞いはRubyの代入と同様な動作となっていて、こちらにまとめた。
###Python3の代入
Python3では、代入は「ターゲットとオブジェクトの間に束縛を作る」ことを意味する。変数にオブジェクトを代入すると、変数にオブジェクトへの参照を格納することになる。
以下はイミュータブルオブジェクトを代入したときのオブジェクトID(識別値)の出力例。
####イミュータブルオブジェクトの場合
変数a
を操作するとa
のid
が変わり、3
という整数オブジェクトに対して変更できないためイミュータブル(変更不可)である。ここでは便宜上、このようなオブジェクトをイミュータブルオブジェクトとして扱う。
a = 3
print("id_3:%d, id_a:%d" % (id(3), id(a)))
b = a
print("id_b:%d" % id(b))
a += 1
print("id_3:%d, id_4:%d, id_a:%d, id_b:%d" % (id(3), id(4), id(a), id(b)))
id_3:1623654960, id_a:1623654960
id_b:1623654960
id_3:1623654960, id_4:1623654992, id_a:1623654992, id_b:1623654960
####ミュータブルオブジェクトの場合
a
を操作して値が変更しても同じid
であり、異なる値に変更することができる。すなわち、ミュータブル(変更可)である。ここでは便宜上、このようなオブジェクトをミュータブルオブジェクトとして扱う。
b
にa
の値を代入した場合、id
が同じため、a
を操作することにより値が変更された場合、b
も変わる。
a = [1,2,3]
b = a
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
a[1] = 4
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
id_a:4332032456, id_b:4332032456
[1, 2, 3] [1, 2, 3]
id_a:4332032456, id_b:4332032456
[1, 4, 3] [1, 4, 3]
#####浅いコピー
ミュータブルオブジェクトの場合、上述の例のように、b
にa
の値を代入した場合、id
が同じため、a
を操作することにより値が変更された場合、b
も変わってしまう。b
の値をa
と独立させるにはコピーをする必要がある。この例でのリストや配列の場合、浅いコピーを施すことで実現できる。
下記の例では浅いコピーをすることで、a
の操作で値が変更されても、b
の値は変わらない。
copy.copy
関数またはa[:]
を代入することで浅いコピーができる。
import copy as cp
a = [1,2,3]
b = cp.copy(a)
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
a[1] = 4
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
b = a[:]
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
a[1] = 3
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print(a, b)
id_a:4460690760, id_b:4460690888
[1, 2, 3] [1, 2, 3]
id_a:4460690760, id_b:4460690888
[1, 4, 3] [1, 2, 3]
id_a:4460690760, id_b:4460691272
[1, 4, 3] [1, 4, 3]
id_a:4460690760, id_b:4460691272
[1, 3, 3] [1, 4, 3]
#####浅いコピーでは不十分となるケース
例えばリストの要素にリスト、配列の要素に配列がある場合、すなわちミュータブルオブジェクトからさらに別のミュータブルオブジェクトがあるような下記の例では、浅いコピーをしてもa[0]
を操作により、b[0]
の値が変更されてしまう。これはPython3.6もRuby2.4も同一の挙動となる。
import copy as cp
a = [[1,2,3], "abc"]
b = cp.copy(a)
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
a[0][1] = 4
a[1] = "def"
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
b = a[:]
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
a[0][1] = 3
a[1] = "abc"
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
id_a:4393106888, id_b:4393107272
id_a[0]:4393106760, id_b[0]:4393106760
id_a[1]:4391239728, id_b[1]:4391239728
[[1, 2, 3], 'abc'] [[1, 2, 3], 'abc']
id_a:4393106888, id_b:4393107272
id_a[0]:4393106760, id_b[0]:4393106760
id_a[1]:4392739984, id_b[1]:4391239728
[[1, 4, 3], 'def'] [[1, 4, 3], 'abc']
id_a:4393106888, id_b:4393112648
id_a[0]:4393106760, id_b[0]:4393106760
id_a[1]:4392739984, id_b[1]:4392739984
[[1, 4, 3], 'def'] [[1, 4, 3], 'def']
id_a:4393106888, id_b:4393112648
id_a[0]:4393106760, id_b[0]:4393106760
id_a[1]:4391239728, id_b[1]:4392739984
[[1, 3, 3], 'abc'] [[1, 3, 3], 'def']
#####深いコピー
「浅いコピーでは不十分なケース」となるミュータブルオブジェクトが別のミュータブルオブジェクトを保持するような場合、b
の値をa
と完全にコピーさせるには浅いコピーではなく深いコピーをする必要がある。
copy.deepcopy
で深いコピーが実現できる。
import copy as cp
a = [[1,2,3], "abc"]
b = cp.deepcopy(a)
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
a[0][1] = 4
a[1] = "def"
print("id_a:%d, id_b:%d" % (id(a), id(b)))
print("id_a[0]:%d, id_b[0]:%d" % (id(a[0]), id(b[0])))
print("id_a[1]:%d, id_b[1]:%d" % (id(a[1]), id(b[1])))
print(a, b)
id_a:4306767304, id_b:4306767688
id_a[0]:4306767176, id_b[0]:4306773064
id_a[1]:4304900144, id_b[1]:4304900144
[[1, 2, 3], 'abc'] [[1, 2, 3], 'abc']
id_a:4306767304, id_b:4306767688
id_a[0]:4306767176, id_b[0]:4306773064
id_a[1]:4306400400, id_b[1]:4304900144
[[1, 4, 3], 'def'] [[1, 2, 3], 'abc']
###PHPの代入
PHPの場合、代入することで左辺の変数の値に右辺の変数の値がコピーされる。Python3の深いコピーと同様な挙動となる。
なお、PHPの$a = &$b;
のような参照による代入はPython3では実現できない。
<?php
$a = [3];
$b = &$a;
$a = [5];
print_r($a);
print_r($b);
Array
(
[0] => 5
)
Array
(
[0] => 5
)
上記例では、$a = [5];
としても、$b
と$a
の参照値は変わらない。
##シンタックス関連
###ダブルクォーテーション、シングルクォーテーション
PHPでは、ダブルクォーテーションの場合は文字列内の特殊文字や変数などを置換し、シングルクォーテーションの場合は置換をしなかった。Python3の場合はどちらの場合もエスケープ文字を置換して出力する。置換しないようにするには、r
またはR
を先頭に付与する。
####PHPの場合
<?php
print("a\r\nb");
print('a\r\nb');
a
ba\r\nb
####Python3の場合
print("a\r\nb")
print('a\r\nb')
print(r"a\r\nb")
print(R"a\r\nb")
a
b
a
b
a\r\nb
a\r\nb
###不等号
PHPで条件式の際、$a > 0 && $a < 10
という表記。Python3でも同様にa > 0 and a < 10
としたいところだが、0 < a < 10
のように簡易化できる。
###3項演算子
PHPでもおなじみの$a = $f ? "hello" : "world"
といった書式。Pyhon3では、a = "hello" if f else "world"
という書式になる。3項演算子でif
とelse
を使うのが特徴。
###インクリメント・デクリメント
Python3ではi++
やi--
といった書式はエラーとなる。インクリメントはi += 1
やi -= 1
とする。
###foreach
PHPでかかせないforeach
、Python3
はfor
で下記の例のように、同等以上のことができる。
####Python3のforの例
print("パターン1")
for v in range(1, 4):
print(v)
print("パターン2")
for v in [x for x in range(4, 1, -1)]:
print(v)
print("パターン3")
for v in (1, 2, 3):
print(v)
print("パターン4")
for k, v in enumerate([1, 2, 3]):
print(k, v)
print("パターン5")
for k, v in {"a":1, "b":2}.items():
print(k, v)
print("パターン6")
for k in {"a":1, "b":2}.keys():
print(k)
print("パターン7")
for v in {"a":1, "b":2}.values():
print(v)
print("パターン8")
for v in [1, 2]:
print(v)
else:
print("else", v)
print("パターン9")
for v in [1, 2]:
print(v)
break
else:
print("else", v)
print("パターン10")
for v in [1, 2]:
print(v)
continue
else:
print("else", v)
print("パターン11")
for v1, v2, v3, v4 in zip([1, 2], (2, 1, 3), {1:"d", 2:"i", 3:"c"}, {"s", "e", "t"}):
print(v1, v2, v3, v4)
print("パターン12")
for v1, v2 in zip([1, 2], [3, 4, 5, 6]):
print(v1, v2)
print("パターン13")
import itertools as it
for v1, v2 in it.zip_longest([1, 2], [3, 4, 5, 6]):
print(v1, v2)
パターン1
1
2
3
パターン2
4
3
2
パターン3
1
2
3
パターン4
0 1
1 2
2 3
パターン5
a 1
b 2
パターン6
a
b
パターン7
1
2
パターン8
1
2
else 2
パターン9
1
パターン10
1
2
else 2
パターン11
1 2 1 s
2 1 2 e
パターン12
1 3
2 4
パターン13
1 3
2 4
None 5
None 6
####range
余談だがrange
はPHPとPython3で境界値が異なる。
#####PHPの場合
<?php
print_r(range(0, 10, 2));
Array
(
[0] => 0
[1] => 2
[2] => 4
[3] => 6
[4] => 8
[5] => 10
)
#####Python3の場合
print(list(range(0, 10, 2)))
[0, 2, 4, 6, 8]
###クロージャ
PHPとPython3で違いがある。パターン分けをして、コード比較の観点で記載。
####関数内での関数定義
#####PHPの場合
関数inner
は名前解決ができていて、外部から呼び出しが可能である。参照渡しでない限り、inner
関数内で外部定義された変数を操作しても外には影響を与えない。また、外に定義されている変数はスコープ外として扱われる。
<?php
function outer($arg1)
{
$out1 = 3;
function inner($arg1, $arg2, &$arg3)
{
$arg1 += isset($out1) ? $out1 : -1;
$arg2 += 5;
$arg3 += 11;
print($arg1. " : inner arg1\n");
print($arg2. " : inner arg2\n");
print($arg3. " : inner arg3\n");
}
inner($arg1, $arg1, $out1);
print($arg1. " : outer arg1\n");
print($out1. " : outer out1\n");
}
outer(10);
$a = 13;
inner(1, 7, $a);
print($a. " : a");
9 : inner arg1
15 : inner arg2
14 : inner arg3
10 : outer arg1
14 : outer out1
0 : inner arg1
12 : inner arg2
24 : inner arg3
24 : a
#####Python3の場合
inner
関数は名前解決されていないため、inner
関数を外部から呼び出すにはouter
の戻り値を取得して呼び出す必要がある。また、外部で定義された変数にもアクセス可能。
15 : inner arg1
の結果からわかるようにout1
が[14]
となっており、前回の状態が保存されている。
def outer(arg1):
out1 = [3]
def inner(arg1, arg2, arg3):
arg1 += -1 if out1 is None else out1[0]
arg2 += 5
arg3[0] += 11
print(arg1, "inner arg1", sep=" : ")
print(arg2, "inner arg2", sep=" : ")
print(arg3[0], "inner arg3", sep=" : ")
inner(arg1, arg1, out1)
print(arg1, "outer arg1", sep=" : ")
print(out1[0], "outer out1", sep=" : ")
return inner
f = outer(10)
a = [13]
f(1, 7, a)
print(a[0], "a", sep=" : ")
13 : inner arg1
15 : inner arg2
14 : inner arg3
10 : outer arg1
14 : outer out1
15 : inner arg1
12 : inner arg2
24 : inner arg3
24 : a
####無名関数
#####PHPの場合
便宜上2回無名関数を定義している。outer
関数でクロージャをreturnし、クロージャ内でもarray_map
でクロージャを定義している。use
を使うことで外部定義の変数にアクセスできるようにしている。
<?php
function outer($arg1)
{
$out = 2;
return function ($arg2) use ($arg1, $out) {
$a = [0 => $arg1, 1 => $arg1 * 2, 2 => $arg1 * 3];
return array_map(function ($k, $v) use ($arg2, $out) {
return ($v + $arg2) * $k + $out;
}, array_keys($a), array_values($a));
};
}
$f = outer(1);
print_r($f(3));
Array
(
[0] => 2
[1] => 7
[2] => 14
)
#####Python3の場合
PHPと同様にouter
関数でクロージャをreturnする。
lambda
でも無名関数を定義できるが、変数定義ができないといった制限もある。
def outer(arg1):
out = 2
def inner(arg2):
a = {0:arg1, 1:arg1 * 2, 2:arg1 * 3}
return map(lambda v: (v[1] + arg2) * v[0] + out, a.items())
return inner
f = outer(1)
print(list(f(3)))
[2, 7, 14]
###可変変数、可変関数
PHPでときどき利用していた可変変数と可変関数。PHPの場合とPython3の場合でコード比較してみる。
####PHPの場合
<?php
$a1 = "b1";
$b1 = "test1";
$a2 = "b2";
function b2 () {
print("test2");
}
print($$a1. "\n");
print($a2());
test1
test2
####Python3の場合
PHPと使い勝手は異なるが、同様なことが実現できる。
a1 = "b1"
b1 = "test1"
a2 = "b2"
def b2():
print("test2")
print(locals()[a1])
print(globals()[a1])
print(eval(a1))
locals()[a2]()
globals()[a2]()
eval(a2)()
test1
test1
test1
test2
test2
test2
###デコレータ
GitHubでPython3のコードを見ていた時に混乱したので、簡単にメモ。その名の通り、装飾をする。
def deco1(func):
def overwrite():
print("overwrite is called")
func()
return overwrite
def deco2(func):
import functools
@functools.wraps(func)
def overwrite(*args,**kwargs):
print("overwrite2 is called")
func(*args,**kwargs)
return overwrite
@deco1
def test1():
print('test1 is called')
test1()
print(test1.__name__)
@deco2
def test2():
print('test2 is called')
test2()
print(test2.__name__)
こちらにわかりやすくまとまっている。
###アノテーション
PHPDocを利用すると耳にするアノテーション。Python3でも利用できるが、ジェネリックなど様々なことで利用することができる。こういった機能に不慣れだとPythonのコードを見たときに、混乱するので、機能自体は把握しておきたいところ。
詳細はこちら
###map、filter、reduceを利用したリスト操作
PHPとpython3でともに利用可能な高階関数。ほとんど差異はないように思える。
####map
まずは、map
について、配列の要素をすべて二乗する処理のサンプル。
#####PHPの場合
<?php
$r1 = array_map(function($n) {
return $n ** 2;
}, range(1, 9));
print_r($r1);
Array
(
[0] => 1
[1] => 4
[2] => 9
[3] => 16
[4] => 25
[5] => 36
[6] => 49
[7] => 64
[8] => 81
)
#####Python3の場合
内包表記でも実現できる。
r11 = list(map(lambda n: n ** 2, range(1, 10)))
r12 = [n ** 2 for n in range(1, 10)]
print(r11)
print(r12)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
[1, 4, 9, 16, 25, 36, 49, 64, 81]
####filter
値が偶数のみの要素を抽出するサンプル。
#####PHPの場合
<?php
$r2 = array_filter(range(1, 9), function($n) {
return $n % 2 == 0;
});
print_r($r2);
Array
(
[1] => 2
[3] => 4
[5] => 6
[7] => 8
)
#####Python3の場合
map
と同様に内包表記でも実現できる。
r21 = list(filter(lambda n: n % 2 == 0, range(1, 10)))
r22 = [n for n in range(1, 10) if n % 2 == 0]
print(r21)
print(r22)
[2, 4, 6, 8]
[2, 4, 6, 8]
####reduce
畳み込み、減少と訳すと違和感があるので、そのように解釈している。rubyではinject
だろうか。
ここでは要素を整数で列挙するサンプル。
#####PHPの場合
<?php
$r3 = array_reduce(range(1, 9), function($a, $b) {
return $a * 10 + $b;
}, 0);
print_r($r3);
123456789
#####Python3の場合
functools
が必要。
from functools import reduce
r3 = reduce(lambda a, b: a * 10 + b, range(1, 10))
print(r3)
123456789
####その他
PHPの方がarray系の関数が豊富に用意されているが、何をどこで利用するか悩むことが多い。array_walk
をメインで利用することが多い。
###正規表現
####はじめに
PCRE
形式の正規表現を利用するには、PHPではパターンをデリミタで囲う必要がある。4桁の数値を例にすると、パターンは'/\d{4}/'
と表現する。
一方、Python3では、まずパターンとなる文字列にr
を付与する。そうすることで、 r"\n"
は改行コードとして扱われず、\
(バックスラッシュ)とn
の文字列として解釈させることができる。r
を付与しない場合、"\\\\n"
とする必要がある。また、正規表現のパターンはre.compile('/\d{4}/')
(import re
が必要)で正規表現オブジェクトとしてコンパイルして利用する。
####関数の比較について
PHPとPython3の簡単な類似比較表は以下のようだろうか。
PHP | Python | 備考 |
---|---|---|
preg_match | re.findall | re.findallはパターンに集合が指定されている場合、配列の要素がタプルとなり、そこに集合要素が入る |
re.match | re.matchは先頭でのみのマッチを確認 | |
re.search | re.searchは文字列のどの位置でも良い | |
preg_replace | re.sub | 1番目にマッチした文字は${1} (PHP)、\1 (Python)と異なる |
preg_replace_callback | re.subの引数replは文字列でも関数でも良い | |
preg_split | re.split |
####その他
PHPとPython3での正規表現の扱いについては、こちらにまとめた。
##class関連
###private、protected
Python3にはprivateもprotectedもない。なので、アンダーバー(_)を1個、または2個をつけて慣習的な運用をする。
class Test(object):
def __init__(self):
self.value = "value"
self._value = "_value"
self.__value = "__value"
def method(self): print("method")
def _method(self): print("_method")
def __method(self): print("__method")
t = Test()
print(t.value)
print(t._value)
print(t._Test__value)
t.method()
t._method()
t._Test__method()
value
_value
__value
method
_method
__method
###クラスメソッドとスタティックメソッド
スタティックメソッドはPHPを利用していると馴染みがある。スタティックメソッドとの違いは第一引数にClass自身を受け取るかどうか。どのクラスから実行されているか判別しやすい。PHPと違い、クラスのインスタンスからでも呼び出せる。
なお、いずれもデコレータが必要。
class MyParent(object):
var = "parent"
@staticmethod
def static_test(called):
print(called, MyParent.var)
@classmethod
def class_test(cls, called):
print(called, cls.var)
class MyChild(MyParent):
var = "child"
def test(self):
print("child test")
MyParent.class_test("Parent")
MyParent.static_test("Parent")
MyParent.class_test("Child")
MyParent.static_test("Child")
p = MyParent()
c = MyChild()
p.static_test(p.__class__)
p.class_test(p.__class__)
c.static_test(c.__class__)
c.class_test(c.__class__)
Parent parent
Parent parent
Child parent
Child parent
<class '__main__.MyParent'> parent
<class '__main__.MyParent'> parent
<class '__main__.MyChild'> parent
<class '__main__.MyChild'> child
####補足
PHPでPython3のようなクラスメソッドを実装する場合、以下のようになると思われる。
この場合、new static()
をnew self()
としても同じ結果となる。
<?php
class MyParent
{
public $var = "parent";
public static function class_method($called) {
$cls = new static();
print($called. " ". $cls->var. "\n");
}
}
class MyChild extends MyParent
{
public $var = "child";
}
MyParent::class_method("Parent");
MyChild::class_method("Child");
Parent parent
Child child
###with構文
Python3にはwith構文がある。
####レガシーな書き方
PHPと書き方が似ている。
file = open("filename")
try:
print(file.read())
finally:
file.close()
####with構文を使った書き方
非常にシンプルになる。
with open("filename") as file:
print(file.read())
####クラスインスタンスをwith構文に対応させる
クラスに__enter__
と__exit__
を定義するだけで、with構文でクラスインスタンスを扱うことができる。
class WithTest:
def __init__(self, name):
self._name = name
def print(self):
print(self._name)
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
print("end of with statement!")
with WithTest("With Statement!") as t:
t.print()
With Statement!
end of with statement!
###イテレータとジェネレータ
csvなどのファイルを1行ずつ読んで処理したり、テーブルからタプルを抽出して、1件ずつ処理したり、などで比較的利用するイテレータ。PHP、Pythonともにイテレータとジェネレータがあるが、差異があるかどうか、実際にコードを用意して試す。
####イテレータ
Python3もPHPと同様に実装できる。ただし、Python3では標準で機能搭載されている。
#####PHPの場合
Iteratorインターフェースを実装するので、必要なのはnext
だけでも他のメソッドも定義する必要があるので、少し煩わしい。
<?php
class MyIterator implements Iterator
{
private $var = [];
public function __construct($array)
{
if (is_array($array)) {
$this->var = $array;
}
}
public function rewind()
{
reset($this->var);
}
public function current()
{
return current($this->var);
}
public function key()
{
return key($this->var);
}
public function next()
{
return next($this->var);
}
public function valid()
{
$key = key($this->var);
return ($key !== NULL && $key !== FALSE);
}
}
$iter = new MyIterator([1, 2, 3, 4, 5]);
foreach ($iter as $k => $v) {
print("$k: $v\n");
}
0: 1
1: 2
2: 3
3: 4
4: 5
#####Python3の場合
PHPと比べシンプルに実装できるが、処理完了する際に、StopIteration
を投げる必要があるので注意が必要。
class MyIterator:
def __init__(self, *numbers):
self._numbers = numbers
self._i = 0
def __iter__(self):
return self
def __next__(self):
if self._i == len(self._numbers):
raise StopIteration()
v = self._numbers[self._i]
self._i += 1
return v
iter = MyIterator(1, 2, 3, 4, 5)
for i, num in enumerate(iter):
print("{0}:{1}".format(i, num))
0:1
1:2
2:3
3:4
4:5
####ジェネレータ
ジェネレータはPython3とPHP(5.5以降)にもある。
なお、ここでのサンプルでは下記ファイルを読み込んでいる。
zero
one
two
three
four
five
#####基本的な実装方法
PHPとPython3で実装方法はほぼ同一。
######PHPの場合
<?php
function my_generator($name)
{
$file = fopen($name, "r");
if ($file) {
while ($line = fgets($file)) {
yield $line;
}
}
fclose($file);
}
$g = my_generator("numbers.txt");
foreach ($g as $k => $v) {
print($k. ":". $v);
}
0:zero
1:one
2:two
3:three
4:four
5:five
######Python3の場合
def my_generator(name):
with open(name) as lines:
for line in lines:
yield line
g = my_generator("numbers.txt")
for k, v in enumerate(g):
print("{0}:{1}".format(k, v), end="")
0:zero
1:one
2:two
3:three
4:four
5:five
#####呼び出すごとにデータを送る
send
で送信するという点では同一だが、使い勝手がPHPとPythonで異なる。
######PHPの場合
yield
に対してデータを送信するようなイメージ。出力結果は「基本的な実装方法」と同じ。
<?php
function my_generator()
{
$k = 0;
while (true) {
$v = yield;
print($k++. ":". $v);
}
}
$g = my_generator();
$file = fopen("numbers.txt", "r");
if ($file) {
while ($line = fgets($file)) {
$g->send($line);
}
}
######Python3の場合
yield
で指定されている変数に対してデータを送信するようなイメージ。yield
の位置に移動するため、__next__
を1回実行する必要がある点がPHPと異なるポイントだと思われる。出力結果は「基本的な実装方法」と同じ。
def my_generator():
k, v = 0, ""
while True:
v = yield v
print("{0}:{1}".format(k, v), end="")
k += 1
g = my_generator()
g.__next__()
with open("numbers.txt") as lines:
for line in lines:
g.send(line)
#####サブジェネレータ
yield from
で再帰的なロジックを実現できる。
######PHPの場合
PHP7から実装されている。出力結果は「基本的な実装方法」と同じ。ファイル読み込みという点ではあまりメリットはないが、配列等の操作の場合、Python3と同等なことができると思われる。
<?php
function my_generator($name)
{
$from = function () use ($name) {
$file = fopen($name, "r");
if ($file) {
while ($line = fgets($file)) {
yield $line;
}
}
fclose($file);
};
yield from $from();
}
$g = my_generator("numbers.txt");
foreach ($g as $k => $v) {
print($k. ":". $v);
}
######Python3の場合
出力結果は「基本的な実装方法」と同じ。「基本的な実装方法」からループが不要になったので、シンプルになった。
def my_generator(name):
with open(name) as lines:
yield from lines
g = my_generator("numbers.txt")
for k, v in enumerate(g):
print("{0}:{1}".format(k, v), end="")
###interface、abstract
Python3にはPHPのようなinterface
は存在しない。Python3は多重継承ができるため、そもそも不要だと思われる。Python3では抽象クラスをABC (Abstract Base Class - 抽象基底クラス)モジュールを使用して実装することができる。
なお、多重継承ができるため、TraitもPython3には不要だと思われる。
####Python3での抽象メソッドの実装例
抽象メソッドを用意するには、クラスのメタクラス(__metaclass__
で指定)がABCMeta
とする、または、それを継承する。下記の出力結果から、抽象クラスで抽象メソッドの実装もでき、サブクラスで未定義でもエラーとならない。
from abc import *
class MyAbstract(object):
__metaclass__ = ABCMeta
@abstractmethod
def test1(self):
print("method")
@staticmethod
@abstractmethod
def static_test():
print("static_method")
@classmethod
@abstractmethod
def class_test(cls):
print("class_method")
class Test(MyAbstract):
pass
MyAbstract.static_test()
MyAbstract.class_test()
Test.static_test()
Test.class_test()
p = MyAbstract()
p.test1()
t = Test()
t.test1()
static_method
class_method
static_method
class_method
method
method
PHPと同じような振る舞いを期待する場合、親クラスのメソッドで下記のように定義することで似た運用ができると思われる。
def test1(self):
raise NotImplementedError("error message")
####仮想的サブクラス
その他、下記のようにクラスメソッド__subclasshook__
をオーバーライドしたクラスを用意し、組み込み関数issubclacss
でメソッドが定義されているかどうか、チェックする方法もある。register
をすることでチェックが通る。
from abc import ABCMeta
class MyAbstract(metaclass=ABCMeta):
@classmethod
def __subclasshook__(cls, C):
if cls is MyAbstract:
if any("test" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
class A(object):
pass
class B(object):
def test(self):
print("test")
print(issubclass(A, MyAbstract))
print(issubclass(B, MyAbstract))
MyAbstract.register(A)
print(issubclass(A, MyAbstract))
False
True
True
###マジックメソッド
PHPのマジックメソッドは、いわゆるPython3のデータモデルの特殊メソッド名に該当するといったとこだろうか。PHPの__toString
がPython3の__str__
に相当しているなど、類似項目も多い。
####定義されていない、または、アクセス権のないプロパティ・メソッドへのアクセス
#####PHPの場合
get_object_vars
でオブジェクトのプロパティが取得できる。
<?php
class Magic
{
public function __get($name)
{
return 0;
}
public function __call($name, $arguments)
{
print($arguments[0]. "\n");
}
}
$m = new Magic();
print($m->x. "\n");
$m->x = 10;
print($m->x. "\n");
print($m->test(20));
print_r(get_object_vars($m));
0
10
20
Array
(
[x] => 10
)
#####Python3の場合
__dict__
にインスタンス変数が格納されているため、組み込み関数vars
でその内容を取得できる。
class Magic(object):
def __getattr__(self, name):
class Callable(int):
def __call__(self, *arguments):
print(arguments[0])
return Callable(0)
m = Magic()
print(m.x)
m.x = 10
print(m.x)
m.test(20)
print(vars(m))
0
10
20
{'x': 10}
####Setter/Getter
余談だが、Python3では以下の方法でもSetter/Getterを定義できる。
class Magic(object):
def __init__(self):
self._x = 0
self._y = 0
def getx(self):
return self._x
def setx(self, value):
self._x = value
x = property(getx, setx)
@property
def y(self):
return self._y
@y.setter
def y(self, value):
self._y = value
m = magic()
print(m.x)
print(m.y)
m.x = 10
m.y = 20
print(m.x)
print(m.y)
0
0
10
20