この記事では、Pythonの設計哲学から特有の言語機能までを簡潔に解説します。内容は「Every Python Concept Explained in 12 Minutes」を元に、補足説明を加えています。
Pythonの禅(Zen of Python)
Pythonインタプリタで import this と入力すると、Pythonの設計思想を表す19の格言が表示されます。これらはコードの美しさ、明瞭さ、シンプルさを重視する文化を形成しています。
- 美しさは醜さより優れている
- 明示的は暗黙的より優れている
- 単純さは複雑さより優れている
- 複雑さは煩雑さより優れている
- フラットな構造はネストより優れている
- 疎な配置は密な配置より優れている
- 可読性が重要である
- 特別なケースもルールを破るほど特別ではない
- 純粋さより実用性が勝ることもある
- エラーは静かに通過してはならない
- 明示的に抑制しない限り
- 曖昧さに直面したら、推測する誘惑を拒否せよ
- 唯一の、できれば明白な方法があるべきである
- その方法は最初は明白でないかもしれない(あなたがオランダ人でなければ)
- 今やることはやらないよりまし
- ただし「今すぐやる」より「やらない」方がましなこともある
- 実装の説明が難しいなら、それは悪いアイデアである
- 実装の説明が簡単なら、それは良いアイデアかもしれない
- 名前空間は素晴らしいアイデアだ。もっと使おう!
if __name__ == "__main__"
この構文は、Pythonで最も広く使われるイディオムの一つで、スクリプトが直接実行されているのか、モジュールとしてインポートされているのかを判別します。このチェックにより、テストコードや実行例をモジュールに含めつつ、インポート時には実行されないように制御できます。Pythonの禅にある「実用性が純粋さに勝る」を体現した機能です。
# my_module.py
def greet(name):
return f"こんにちは、{name}さん!"
def add(a, b):
return a + b
if __name__ == "__main__":
# 直接実行時のみ動作
print("my_module.pyを直接実行中")
print(greet("太郎"))
print(f"3 + 5 = {add(3, 5)}")
直接実行時:
$ python my_module.py
my_module.pyを直接実行中
こんにちは、太郎さん!
3 + 5 = 8
インポート時:
# main.py
import my_module
print(my_module.greet("花子"))
$ python main.py
こんにちは、花子さん!
すべてがオブジェクト
Pythonでは、整数、文字列、関数、クラス、モジュールに至るまですべてがオブジェクトです。各オブジェクトは属性とメソッドを持ち、変数への代入、引数としての受け渡し、戻り値としての返却が可能です。この一貫性がPythonの柔軟性を支えています。ただし、オブジェクトのオーバーヘッドがメモリ消費に影響する場合もあります。
空白とインデント
Pythonはインデントでコードブロックを定義します。C言語やJavaScriptの波括弧、RubyやLuaの end キーワードとは異なり、この方式はコードの可読性を強制的に高めます。同じインデントレベルは同一ブロックを意味し、ネストは追加のインデントで表現されます。不整合なインデントは IndentationError を引き起こします。
ループの else 節
for や while ループに else を付加できます。このブロックはループが break で終了せず、正常に完了した場合にのみ実行されます。これは条件分岐ではなく「breakされなかった場合の処理」と考えると理解しやすいでしょう。素数判定やタイムアウト付きループなどで実用的です。
リスト内包表記
リスト内包表記は、ループと条件を一行で記述し、新しいリストを生成する簡潔な方法です。
基本例:
# 従来のループ
squares = []
for i in range(1, 6):
squares.append(i ** 2)
# リスト内包表記
squares = [i ** 2 for i in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]
条件付き:
# 偶数の平方のみ
even_squares = [i ** 2 for i in range(1, 11) if i % 2 == 0]
print(even_squares) # [4, 16, 36, 64, 100]
使用上の注意:
# 複雑になりすぎる場合は避ける
result = [x * y for x in range(1, 4) for y in range(1, 4) if x != y and x + y > 3]
# 同等の従来ループ(可読性が高い)
result = []
for x in range(1, 4):
for y in range(1, 4):
if x != y and x + y > 3:
result.append(x * y)
複雑なロジックでは従来のループの方が適切な場合があり、Pythonの禅「可読性が重要」を意識しましょう。
多重代入とタプルアンパック
一行で複数の変数に値を代入できます。右辺は暗黙的にタプルとして扱われ、左辺の変数に展開されます。
# 基本
name, age, city = "田中", 30, "東京"
print(name, age, city) # 田中 30 東京
# 変数スワップ
a, b = 10, 20
a, b = b, a
print(a, b) # 20 10
# リストのアンパック
colors = ["赤", "青", "緑"]
c1, c2, c3 = colors
# 関数からの戻り値
def get_info():
return "鈴木", 25, "エンジニア"
name, age, job = get_info()
# 余分な値を無視する _
data = ("佐藤", 28, "大阪", "デザイナー")
name, age, _, job = data
# 文字列のアンパック
a, b, c = "ABC"
print(a, b, c) # A B C
# ループ内で活用
users = [("山田", 85), ("田中", 92)]
for name, score in users:
print(f"{name}の点数は{score}点")
注意: 変数の数と値の数は一致する必要があります。
# エラー
a, b = 1, 2, 3
# 可変長アンパック
a, b, *rest = 1, 2, 3, 4, 5
print(a, b, rest) # 1 2 [3, 4, 5]
動的型付けと強い型付け
Pythonは動的型付け言語であり、変数の型は実行時に決定されます。同時に強い型付けを採用しており、暗黙の型変換は行われず、不適切な型操作は TypeError を発生させます。これにより柔軟性と安全性のバランスが取れています。
ダックタイピング
「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルである」という考え方に基づき、オブジェクトの型ではなく、持つメソッドや振る舞いを重視します。型チェックではなく、オブジェクトが「何ができるか」に注目することで、ポリモーフィズムを実現します。
pass 文
何も実行しない文です。構文的にコードブロックが必要だが、処理をまだ記述したくない場合のプレースホルダーとして使用します。
def yet_to_implement():
pass # 後で実装
class EmptyClass:
pass
第一級関数とクロージャ
関数は第一級オブジェクトであり、変数への代入、引数としての受け渡し、戻り値としての返却が可能です。
# 変数への代入
def hello(name):
return f"こんにちは、{name}さん"
func = hello
print(func("太郎")) # こんにちは、太郎さん
# 引数として渡す
def apply_twice(f, value):
return f(f(value))
def add_one(n):
return n + 1
print(apply_twice(add_one, 5)) # 7
# 戻り値として返す(クロージャ)
def multiplier(factor):
def multiply(x):
return x * factor
return multiply
double = multiplier(2)
print(double(5)) # 10
クロージャは、外部スコープの変数を記憶する関数オブジェクトです。
ダンダーメソッド(特殊メソッド)
前後にアンダースコアが2つ付いたメソッド(例:__init__、__str__)で、特定の操作時に自動的に呼び出されます。通常は直接呼び出さず、Pythonの内部機構として動作します。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 + p2) # Point(4, 6)
*args と **kwargs
*args は可変長位置引数、**kwargs は可変長キーワード引数を受け取るための構文です。
def log(*args, **kwargs):
print("位置引数:", args)
print("キーワード引数:", kwargs)
log("エラー", 404, severity="高", module="auth")
# 位置引数: ('エラー', 404)
# キーワード引数: {'severity': '高', 'module': 'auth'}
セイウチ演算子(:=)
Python 3.8で導入された代入式で、式の中で変数に値を代入し、その値を同時に使用できます。重複した計算や余分な行を削減します。
# 従来の方法
import re
text = "連絡先: 090-1234-5678"
pattern = r'\d{3}-\d{4}-\d{4}'
match = re.search(pattern, text)
if match:
print(f"電話番号: {match.group()}")
# セイウチ演算子
if (match := re.search(pattern, text)):
print(f"電話番号: {match.group()}")
# ループでの活用
while (line := input("入力(空行で終了): ")) != "":
print(f"入力: {line}")
# リスト内包表記
def expensive(x):
print(f"計算: {x}")
return x ** 2
results = [result for x in range(5) if (result := expensive(x)) > 5]
デコレータ
関数やクラスの機能を、ソースコードを変更せずに拡張する仕組みです。
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} の実行時間: {time.time() - start:.4f}秒")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
print("処理完了")
slow_function()
# 処理完了
# slow_function の実行時間: 1.0023秒
with 文とコンテキストマネージャ
リソースの取得と解放を自動化します。__enter__ と __exit__ メソッドを実装したオブジェクトと共に使用します。
# ファイル処理の例
with open("data.txt", "r") as file:
content = file.read()
# 自動的にファイルが閉じられる
# カスタムコンテキストマネージャ
class ManagedResource:
def __enter__(self):
print("リソースを取得")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("リソースを解放")
def use(self):
print("リソースを使用")
with ManagedResource() as resource:
resource.use()
__slots__ によるメモリ最適化
通常、クラスのインスタンス属性は辞書に格納されますが、__slots__ を宣言すると固定サイズのタプルに置き換えられ、メモリ使用量が削減されます。多数のインスタンスを生成する場合に有効です。
class OptimizedPoint:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
# 辞書が使えないため、動的属性追加や vars() が使えなくなる
p = OptimizedPoint(1, 2)
# p.z = 3 # AttributeError
エラーハンドリングの else
try-except ブロックに else を追加すると、例外が発生しなかった場合のみ実行されます。
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError:
print("ゼロ除算エラー")
except TypeError:
print("型エラー")
else:
print(f"計算成功: {result}")
return result
safe_divide(10, 2) # 計算成功: 5.0
可変デフォルト引数(注意点)
関数定義で可変オブジェクト(リストや辞書)をデフォルト値にすると、そのオブジェクトは関数が定義された時点で一度だけ評価され、以降の呼び出しで共有されます。
# 注意が必要な例
def add_item(item, items=[]):
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] ← 前回の結果が残る
# 正しい対処法
def add_item_correct(item, items=None):
if items is None:
items = []
items.append(item)
return items
グローバルインタプリタロック(GIL)
GILは、Pythonオブジェクトへのアクセスを保護する排他ロックです。これにより、同一プロセス内の複数スレッドが同時にPythonバイトコードを実行することを防ぎます。この設計はメモリ管理の安全性を確保するためのもので、特にCPython実装での特徴です。CPUバウンドな処理ではマルチスレッドの恩恵を受けにくい一方、I/Oバウンドな処理では影響が少ないです。