LoginSignup
130
139

More than 5 years have passed since last update.

PHPとPythonの違いについてのまとめ

Last updated at Posted at 2017-05-28

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を省略した場合、最後までとなる。mnが負の場合、逆から指定することができる。また、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を操作するとaidが変わり、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であり、異なる値に変更することができる。すなわち、ミュータブル(変更可)である。ここでは便宜上、このようなオブジェクトをミュータブルオブジェクトとして扱う。
baの値を代入した場合、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]
浅いコピー

ミュータブルオブジェクトの場合、上述の例のように、baの値を代入した場合、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項演算子でifelseを使うのが特徴。

インクリメント・デクリメント

Python3ではi++i--といった書式はエラーとなる。インクリメントはi += 1i -= 1とする。

foreach

PHPでかかせないforeachPython3forで下記の例のように、同等以上のことができる。

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以降)にもある。
なお、ここでのサンプルでは下記ファイルを読み込んでいる。

numbers.txt
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
130
139
20

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
130
139