初めに
Pythonは一般的なオブジェクト指向言語と比べ独特な部分があります。
主にPHPとの相違点に注目して挙げてみます。
2023/10/17
コメントでの指摘を反映
命名規則
PythonではPEP8という命名規則が用いられています。
しかし、他言語と比べ独特な部分があるので、フォーマッタの導入をお勧めします。
個人的には、カスタマイズの幅が狭い(=チーム内でコーディングルールを統一しやすい)Blackがお勧めです。
文字コード
Python3のデフォルト文字コードはUTF-8です。
コードの冒頭に# -*- coding: <encoding name> -*-
と書くことでコードの文字コードを変更することができますが、PEP8では非推奨です。
インデント
Pythonでは最初の字下げをインデントとして認識します。
字下げの手段はタブでもスペースでも構いません。
途中からインデントの方法を変えた場合、正しく認識しません。
特に、コードの途中でスペースx4からタブに変えたり、スペースx4からスペースx2に変えると誤動作をします。
VSCodeなど空白を表示できるエディタを使ってください。
PEP8ではスペースx4を推奨しています。
また、制御が複雑になると、エディタデフォルトのインデントラインだけでは非効率な場合があります。
VSCodeではindent-rainbow拡張がお勧めです。
名前空間
Pythonにも名前空間の概念はありますが、PHPのようにスコープの制限目的としては用い辛いです。
ライブラリのインポート
import文を用い組み込みのオプションモジュールやpipでダウンロードしたモジュールを用います。
import xxxxx as yyyyy
で別名をつけることができます。
算術計算ライブラリのnumpyのように別名で用いるのがデファクトスタンダードになっているモジュールもあります。
import numpy as np
制御文
Pythonでは、他の言語でいうスコープ({ ... })の代わりにインデントを用いてプログラムを制御します。
他の言語のスコープの範囲をインデントに置き換え、スコープの開始行末に:をつけるイメージで大体動きます。
pass
何も処理を行わないブロックがある場合はpassを用います。
末尾が:で終わる行で終わること or 末尾が:で終わる行が連続することは許されません。
制御文での使用例
if a == b :
a = 1
elif a < b:
pass
else
a = 2
関数定義での使用例
def do_nothing():
pass
補足
Pythonはインタプリンタに分類される言語ですが、実行前にプリコンパイルされます。(それでも遅いです)
その際に文法チェックをしてくれますが、過信は禁物です。
コメント
一行コメントは#
複数行コメントは""" ~ """
で囲みます。
関数名 or メソッド名直後の"""
はドキュメントコメント(PHPでいう///
)として認識してピックアップしてくれるIDEもあります。
比較
==
を使い値を比較します。
残念ながらPythonには===
がないためサブクラスの比較の場合、意図しないものになる場合があります。
オブジェクトのクラス名を取得する手段は用意されているので適宜条件文に組み込んでください。
a = 1
b = 1.0
c = True
print((a == b == c)) # → True
# bool型はint型のサブクラスで、
# Falseはint型の0、Trueはint型の1として実装されています。
a = 1
b = 1.0
print(a.__class__.__name__) # → int
print(b.__class__.__name__) # → float
print((a == b and a.__class__.__name__ == b.__class__.__name__)) # → False
# こちらの方が楽です
print(a == b and type(a) == type(b)) # → False
is
オブジェクトが同一か調べます。
オブジェクトが明らかに一つしかないときにしか用いないでください。(一般には見えないオブジェクトIDで比較するため、誤判定の原因になります。)
反面、==
に比べ動作は早いのでNoneか否かを判定する際は積極的に使ってください。
a = None
print((a is None)) # → True
データ構造
デフォルトで配列([]
)と辞書({}
)タプル(()
)、セット({}
:重複した要素を許可しない配列 要素数0のものを宣言する場合はset()
を用いる)をサポートしています。
それぞれ要素の型に制限はなく、ネストが可能です。
配列と辞書が使えれば大多数のプログラムをカバーできます。
配列
一般にfor文で用いることも多いため、ネストはできる限り行わず、要素の型も統一するのが好ましいです。
for文で使いにくい配列の例
[1, 2, "a", ["b", ["c", 3]]] # 要素の型が統一されていない, ネストがある
for文で使いやすい配列の例
[1, 2, 5, 8. 10, 11] # 要素の型が統一されている, ネストがない
辞書
PHPの連想配列に同じです。
配列の時のよりネストや型の制約はゆるくてかまいません。
辞書の動的作成
ネストされた辞書を動的に作成するとなると、PHPよりも手間がかかります。
PHP:
hoo["a"]["b"]["c"]["d"] = "bar";
Python:
foo = {}
foo.setdefault("a", {})
foo["a"].setdefault("b", {})
foo["a"]["b"].setdefault("c", {})
foo["a"]["b"]["c"]["d"] = "bar"
(浅い)コピー
要素には値ではなく値が格納されているメモリアドレスを格納しています。
配列や辞書をコピーしても、子の要素は別のオブジェクトとなりますが、ネストされている孫要素のメモリアドレスまでは変わりません。
origin = [[0, 1, 2], 3]
copied = origin.copy()
print((copied is origin)) # → False
copied[1] = 9
print(copied) # → [[0, 1, 2], 9]
print(origin) # → [[0, 1, 2], 3]
copied[0][1] = 10
print(copied) # → [[0, 10, 2], 9]
print(origin) # → [[0, 10, 2], 3]
### スライス
スライス[:]で複製した場合も浅いコピーとなります。
origin = [[0, 1, 2], 3]
copied = origin[:]
print((copied is origin)) # → False
copied[1] = 9
print(copied) # → [[0, 1, 2], 9]
print(origin) # → [[0, 1, 2], 3]
copied[0][1] = 10
print(copied) # → [[0, 10, 2], 9]
print(origin) # → [[0, 10, 2], 3]
深いコピー
copyモジュールのdeepcopyメソッドを使うことで要素を完全に別アドレスとすることができます。
import copy
origin = [[0, 1, 2], 3]
copied = copy.deepcopy(origin)
print((copied is origin)) # → False
copied[1] = 9
print(copied) # → [[0, 1, 2], 9]
print(origin) # → [[0, 1, 2], 3]
copied[0][1] = 10
print(copied) # → [[0, 10, 2], 9]
print(origin) # → [[0, 1, 2], 3]
繰り返し (for文)
Pythonでは他言語とは異なり、イテレート可能なオブジェクト(順送り可能なオブジェクト etc.配列)に対してのみfor文が実行されます。
回数で指定する場合もイテレート可能なRangeオブジェクトに変換する必要があります。
for n in range(3):
print(f"{n}回目")
例外
throw
ではなく、raise
を使います。
クラス
Pythonでもクラスを定義できますがPHPとは少し構造が違います。
PHPとは違いPythonでは複数のクラスを継承可能ですが、できるだけ単一継承にした方が他のプログラミング言語に置き換える際のハードルが低くなります。
クラスの構造
class SampleClasss(継承元のクラス(ある場合のみ)):
class_val = 1 # PHPと異なりこの場所で宣言した変数はクラスに共通した値を持つ変数となります
def __init__(self):
"""コンストラクタ"""
self.__instance_val1 = 10 # インスタンス変数はコンストラクタ内で宣言してください
self.__instance_val2 = "20" # ただし、インスタンス変数はself.~の形で宣言してください
@property # seter, getterにはデコレーション(@~)を用います
def Num(self):
return self.__instance_val1
@Num.setter
def Num(self, x):
self.__instance_val1 = x
@property
def Str(self):
return self.__instance_val2
@Str.setter
def Str(self, val):
self.__instance_val2 = val
# メソッドの第一引数にはselfを指定してください
# インスタンス変数、インスタンスメソッドを読み込みます
def __private_method(self, x: int) -> int:
return x + 3
def public_method(self, y):
self.Num = y
# 自クラス内のメソッドを使用する場合はself.~と指定してください
return self.__private_method(self.Num)
アクセスレベル
PEP8 では、プライベートアクセスはアンダースコア1個を推奨しています。
from M import * は、アンダースコアで始まる名前のオブジェクトをimportしません。
(アンダースコアx2)をつけたものはプライベートアクセス扱いになります。
実は{クラス名}_{メソッド名}でグローバルアクセスできますが、そんなコードは実際に運用するアプリケーションには書かないでください。
クラス変数
PHPのインスタンス変数に該当する部分に書いた変数は、クラス変数(クラス共通の変数)として機能するため、インスタンス変数として用いないでください。
# 上記のサンプルクラスのクラス変数を使った例
a = SampleClasss()
b = SampleClasss()
print(a.class_val) # → 1
print(b.class_val) # → 1
a.__class__.class_val = 20
print(a.class_val) # → 20
print(b.class_val) # → 20 b.class_valも変化する
インターフェース・抽象クラス
Pythonには言語使用としてのインターフェースはありません。
abcモジュールをimportし、抽象クラスとして定義してください。
import abc
class IMailSender(metaclass=abc.ABCMeta):
@abc.abstractmethod
def send(self, mail: Mail) -> None:
raise NotImplementedError()
Pythonでは複数クラスを継承可能なため抽象クラスも複数継承できます。
抽象メソッドを定義 & 実装する場合はデコレーションをつけてください。
オーバーロード
Pythonでもメソッドのオーバーロードは基本的にできません。
最後に現れたメソッド名でオーバーライトされてしまいます。
class Sample():
def overload(self, a, b, c):
return "A"
def overload(self, a, b, c, d, e):
return "B"
s = Sample()
print(s.overload(1,2,3,4,5)) # → B
print(s.overload(1,2,3)) # → エラー
Pythonでオーバーロードを実装するためには可変長引数を利用する必要があります。
終わりに
PHPを前提にすると引っ掛かりそうな点をざっと挙げました。
きれいなコードの書き方は他の記事をあたってください。
継承やデコレーション、特殊メソッド等も突き詰めていくと奥が深いです。