はじめに
開発やコードレビューをしていく中で、これが使えるとコードがシンプルになって嬉しいなと思ったPythonの文法や仕様をまとめてみました。
主に、Pythonは一通り書けるようになって次にレベルアップしたいと思っている方を対象としています。
1. f-string
対応:Python3.6〜
f-stringは、'f'や'F'が先頭に付いた文字列で、正式には「フォーマット済み文字列リテラル」と呼ばれます。
特徴としては、中に変数や式を埋め込むことができます。
a = 12.345
print(f"value: {a}") # `"value" + str(a)`より簡単に書ける
# value: 12.345
print(f"value: {a * 10}") # 式も埋め込める
# value: 123.45
また、フォーマットを指定することもできます。
print(f"value: {a:.2f}")
# value: 12.35
その他の用例はこちらの記事を参考にしてみてください。
2. 比較演算子の連結
Pythonでは、比較演算子を連結できます。
a = 12.345
if 1 < a < 100:
print(True)
# True
使う場面はないですが、文法上こんなこともできます。
1 < a > 10 # same as `1 < a and a > 10`
1 in [1, 2, 3] in [[1, 2, 3], [4, 5, 6]]
# True
# True
参考:
3. 短絡評価
Pythonでは、and/or
による論理式の評価は、全体の真理値が決定された時点で終了します↓
式 x and y は、まず x を評価します; x が偽なら x の値を返します; それ以外の場合には、 y を評価した結果値を返します。
式 x or y は、まず x を評価します; x が真なら x の値を返します; それ以外の場合には、 y を評価した結果値を返します。
https://docs.python.org/ja/3/reference/expressions.html#boolean-operations より
また、and/or
がTrue/False
を返すとは限らず、最後に評価された値が返されます。
したがって、以下のような結果となります。
"a" and 1 # same as `1 if bool("a") else "a"`
"a" or 1 # same as `"a" if bool("a") else 1`
"" or 1
# 1
# 'a'
# 1
こちらの実用的な使用場面としては、Noneチェックが挙げられます。
def f(l: list | None = None):
l = l or []
print(l)
# 以下と等価
def f(l: list | None = None):
if not l:
l = []
print(l)
問題になることはほぼないと思いますが、引数に空リスト([]
)を渡した場合にも、l
に新たに[]
が代入されます。
参考:
4. 内包表記(comprehension)
リストなどのイテラブルオブジェクトの要素を反復して、新たなリスト等を作成します。
l = [1, 2, 3, 4, 5]
[x ** 2 for x in l]
# [1, 4, 9, 16, 25]
[x ** 2 for x in l if x % 2 == 0] # 条件に当てはまる要素(=2の倍数)のみが選ばれる
# [4, 16]
作成されるオブジェクトは、具体的には、
- リスト内包表記 (
[ 内包表記 ]
) - 辞書内包表記 (
{ key : value for 要素 in イテラブル [if 条件] }
) - 集合内包表記 (
{ 内包表記 }
) - ジェネレータ式 (
( 内包表記 )
)
の4パターンがあります。
参考:
5. セイウチ演算子(walrus operator, :=
)
対応:Python3.8〜
"walrus"は日本語にすると「セイウチ」です。
大きな構文の一部として、変数に値を割り当てる新しい構文 := が追加されました。 この構文は セイウチの目と牙 に似ているため、「セイウチ演算子」の愛称で知られています。
https://docs.python.org/ja/3/whatsnew/3.8.html#assignment-expressions より
変数に代入しつつ、その値を返すことのできる式です。
変数がNone
であるかチェックし、None
でない場合にはその変数の値を利用したい場合に、少々簡潔に表記することができます。
import re
s = "Hello world!"
if m := re.search("world", s):
print(m.group())
if (m := re.search("japan", s)) is not None: # :=は結合性が低いので()で囲う必要がある
print(m.group())
# world
リスト内包表記と組み合わせることで、関数(以下の例ではre.search
)を2回呼び出すのを防ぐこともできます。
l = ["a1", "b2", "c"]
[m.group() for x in l if (m := re.search(r"\d", x))] # 文字列リストから数字を取り出す
# ['1', '2']
参考:
6. for-else文
forループがbreak
によって終了されなかった場合の処理をelse:
以下に定義することができます。
l = [2, 1, 3]
for i in l:
print(i)
if i >= 5:
break
else:
print("smaller than 5")
# 2
# 1
# 3
# smaller than 5
l = [2, 5, 3]
の場合には、条件に引っかかりbreak
されるため、出力が
# 2
# 5
となり、elseの中身は実行されません。
for-elseのメリットとしては、制御フロー変数の不要化が挙げられます。
これを使わないと以下のようになり、余計な変数を追う必要が出てきます。
f = True # これが制御フロー変数
for i in l:
print(i)
if i >= 5:
f = False
break
if f:
print("smaller than 5")
7. match文
対応:Python3.10〜
対象の値とパターンをマッチングし、処理を分岐させることができます。
l = [1, 2]
match len(l):
case 0:
print("empty")
case 1 | 2:
print("single or double")
case _:
print("more than 2")
# single or double
match文を使わないと以下のようになります。
if len(l) == 0:
print("empty")
elif len(l) == 1 or len(l) == 2:
print("single or double")
else:
print("more than 2")
match文の方がスッキリした見た目になることがわかると思います。
また、他にも
match l:
case []:
print("empty")
case [x]:
print(f"first: {x}")
case [x, y]:
print(f"first: {x}, second: {y}")
case _:
print("many")
# first: 1, second: 2
match l:
case list(x):
print("list")
case tuple(x):
print("tuple")
case _:
print("other")
# list: [1, 2]
にような、構造やクラスによる複雑なマッチングもできます。
参考:
まとめ
他にも色々書きたいことはありますが、長くなりそうなので一旦ここまで。
Pythonにはまだまだ便利で面白いテクニックがたくさんあります。
また気が向いたら続編も書こうと思います。
最後に、この記事が面白いと思ってくださった方、またはもっと深いことを知りたいと思った方はぜひ以下の記事も読んでみてください!