Pythonでは、複数の変数に対して1行で代入できる同時代入が自然に使えます。
例えばx, y = 1, 2
やa, b = b, a
のように簡潔に書けるため、コードが読みやすくミスも減ります。
本記事では、基本からアンパック応用、よくある落とし穴まで丁寧に解説します。
Pythonの同時代入とは?
同時代入は、右辺の複数値を左辺の複数変数へ一度に割り当てるPythonの基本機能です。
右辺の評価が先に行われ、その結果が左辺の変数へ順にアンパックされます。
一見シンプルですが、タプル(やリスト)の自動パック/アンパック、連鎖代入との違いなど、理解しておくべきポイントがあります。
基本構文 x, y = 1, 2
最も基本的な例は次のとおりです。
2つの値を2つの変数に同時に代入します。
# 基本的な同時代入の例
x, y = 1, 2
print(x, y) # 期待: 1 2
1 2
この形は、式の左と右で「並び」を対応させるだけです。
カンマの位置が重要で、(1, 2)
のように括弧がなくてもタプルとして扱われます。
かっこ省略とタプルの自動パック/アンパック
Pythonはカンマ区切りで並べた値を自動的にタプルに「パック」します。
反対に、タプル(やリスト)を左辺の複数変数に「アンパック」することができます。
# パック(右辺)とアンパック(左辺)の基本
pair = 10, 20 # かっこ省略でもタプル (10, 20)
a, b = pair # アンパック
print(pair, type(pair))
print(a, b)
(10, 20) <class 'tuple'>
10 20
括弧は省略可能ですが、ネストや長い行では括弧を明示した方が読みやすく安全です。
連鎖代入 a = b = 0 との違い
a = b = 0
は「同じオブジェクトを複数の変数へ同時に代入」する構文です。
x, y = 1, 2
とは目的が異なります。
特に可変オブジェクト(リストや辞書など)での連鎖代入は注意が必要です。
# 連鎖代入は「同じオブジェクト」を共有する
a = b = [] # 空リストオブジェクトをaとbで共有
a.append(1)
print(a, b) # bも同じリストを参照するため [1] [1]
# 同時代入なら別オブジェクトにできる
c, d = [], [] # それぞれ独立した空リスト
c.append(1)
print(c, d) # dは空のまま [1] []
[1] [1]
[1] []
以下の表は、同時代入と連鎖代入の違いを要点で整理したものです。
観点 | 同時代入(x, y = 1, 2) | 連鎖代入(a = b = 0) |
---|---|---|
目的 | 複数値を位置対応で割り当て | 同じオブジェクト参照を複数名に付与 |
右辺評価 | 右辺全体を先に評価してから割り当て | 右辺を1度評価して全変数へ同じ参照を割り当て |
可変オブジェクト | 各変数を独立にできる | 共有になりやすくバグの元 |
可読性 | 値の対応が明確 | 意図が「共有」に偏りやすい |
初心者は基本的に同時代入を使い、連鎖代入は不変オブジェクト(int, strなど)に限定するのが安全です。
a,b=b,aで値を入れ替える
Pythonなら、変数の値の入れ替え(スワップ)が1行で書けます。
一時変数を作る必要がありません。
一時変数なしのスワップ
# 値を入れ替える典型パターン
a, b = 5, 9
print("before:", a, b)
a, b = b, a # aとbを入れ替え
print("after: ", a, b)
before: 5 9
after: 9 5
この書き方は、右辺がタプルとして評価され、左辺にアンパックされるだけです。
余計な一時変数を使わないのでスッキリしています。
リスト要素のスワップ例
リストの2要素を入れ替える場合も同様です。
添字で直接指定できるので、初心者にありがちな一時退避のコードを書かずに済みます。
# リストの要素を入れ替える
nums = [10, 20, 30, 40]
print("before:", nums)
nums[1], nums[3] = nums[3], nums[1] # 2番目と4番目を入れ替え
print("after: ", nums)
before: [10, 20, 30, 40]
after: [10, 40, 30, 20]
右辺が先に評価される仕組み
同時代入では右辺の評価が必ず先です。
したがって、a, b = b, a
は、まず右辺の(b, a)
が現時点の値で作られてから、左辺のa
とb
に順に代入されます。
# 評価順序を観察する例
def mark(name):
print(f"eval {name}")
return name
x, y = mark("RHS-1"), mark("RHS-2") # 右辺が先に評価される
print("=>", x, y)
# スワップ時も同様に右辺が先
x, y = y, x
print("swapped =>", x, y)
eval RHS-1
eval RHS-2
=> RHS-1 RHS-2
swapped => RHS-2 RHS-1
関数呼び出しや計算が含まれていても右辺が先であることを覚えておくと、評価の副作用を伴うコードの理解に役立ちます。
アンパックの実用テクニック
アンパックは「複数の値の分解と受け取り」を行う基本技です。
リストやタプル、文字列などの反復可能オブジェクトを柔軟に分解できます。
反復可能オブジェクトのアンパック
リストやタプルだけでなく、文字列やrangeもアンパックできます。
要素数は左辺の数に一致させます。
# 反復可能オブジェクトのアンパック例
a, b, c = [1, 2, 3]
u, v, w = ("A", "B", "C")
x, y, z = range(3)
s1, s2, s3 = "猫犬鳥" # 文字列も1文字ごとにアンパック(サロゲートに注意)
print(a, b, c)
print(u, v, w)
print(x, y, z)
print(s1, s2, s3)
1 2 3
A B C
0 1 2
猫 犬 鳥
スターアンパック *rest で残りをまとめる
*
を付けた変数には、残りの要素がリストとしてまとめて入ります。
左辺に置けるスターターゲットは1つだけです。
# スターアンパックで残りをまとめ取り
head, *middle, tail = [10, 20, 30, 40, 50]
print("head:", head) # 10
print("middle:", middle) # [20, 30, 40]
print("tail:", tail) # 50
head: 10
middle: [20, 30, 40]
tail: 50
先頭/末尾だけ取り出す
先頭や末尾を取り出したい場合に便利です。
「残りはまとめて捨てる」こともスマートに書けます。
# 先頭を取り、残りはまとめて受ける
first, *rest = [1, 2, 3, 4]
print(first, rest) # 1 [2, 3, 4]
# 末尾だけ取り、他はまとめて受ける
*body, last = "abcdef"
print(body, last) # ['a', 'b', 'c', 'd', 'e'] f
1 [2, 3, 4]
['a', 'b', 'c', 'd', 'e'] f
使わない値は _(アンダースコア) を使う
使わない値に意味のある変数名を付けると誤解を招きます。
慣習的に_
を使って「意図的に未使用」であることを示します。
# 未使用の値にはアンダースコアを使う
x, _, z = (100, 200, 300)
print(x, z) # 100 300
# まとめて捨てるときも使える
first, *_, last = range(10) # 間の値は使わない
print(first, last) # 0 9
100 300
0 9
初心者がハマりやすいポイント
アンパックは便利ですが、前提条件(要素数・評価順序・共有参照など)を外すとすぐに例外やバグになります。
よくある落とし穴をあらかじめ押さえておきましょう。
要素数不一致の ValueError
左辺の変数数と右辺の要素数が一致しないとValueErrorになります。
スターアンパックを活用すると不一致を吸収できます。
# 要素数が足りない/多いとValueError
try:
a, b = [1] # 要素が足りない
except ValueError as e:
print("Error1:", e)
try:
x, y = [1, 2, 3] # 要素が多すぎる
except ValueError as e:
print("Error2:", e)
# スターアンパックで不一致を吸収
head, *rest = [1] # OK: restは空リスト
print("head:", head, "rest:", rest)
Error1: not enough values to unpack (expected 2, got 1)
Error2: too many values to unpack (expected 2)
head: 1 rest: []
ネストしたアンパックの書き方
タプル(やリスト)が入れ子になっている場合、左辺も同じ形に対応させます。
括弧を明示すると読みやすく安全です。
# ネストした構造のアンパック
data = ((1, 2), (3, 4))
(a1, a2), (b1, b2) = data
print(a1, a2, b1, b2) # 1 2 3 4
# スターをネスト側に使う例(左辺にスターは一つまで)
data2 = (("A", "B", "C"), ("D", "E"))
(first, *mid), (last1, last2) = data2
print(first, mid, last1, last2) # A ['B', 'C'] D E
1 2 3 4
A ['B', 'C'] D E
可変オブジェクトの連鎖代入に注意 a = b = []
可変オブジェクトでの連鎖代入は共有参照になります。
初心者のバグの温床です。
# 悪い例: 可変オブジェクトの連鎖代入
a = b = {} # 同じ辞書を共有
a["k"] = 1
print(a, b) # bにも反映される
# 良い例: 同時代入で独立に生成
c, d = {}, {}
c["k"] = 1
print(c, d) # dには影響なし
{'k': 1} {'k': 1}
{'k': 1} {}
不変オブジェクト(int, str, tupleなど)なら連鎖代入も基本的に安全ですが、可変なら常に避けるか注意深く使いましょう。
PEP 8の読みやすさ重視のコツ
PEP 8はPythonのスタイルガイドです。
同時代入・アンパックに関しては次を意識すると読みやすくなります。
- カンマの後にはスペースを入れる。例:
x, y = 1, 2
- 単純なケースでは括弧を省略して可読性を上げ、ネストや長い行では括弧を明示して誤読を防ぐ。
- 1行に詰め込みすぎない。変数の並びが3〜4個を超えるなら、意味の塊ごとに分ける。
- スターアンパックは1つだけ。複雑なパターンマッチは分割代入を複数行に分ける。
- 未使用の値は
_
を使い、読者に意図を伝える。 - 型ヒントを補助的に使うと意図が伝わりやすくなります。例:
x: int; y: int; x, y = 1, 2
# PEP 8に沿った読みやすい書き方の一例
from typing import Tuple
def split_point(p: Tuple[int, int]) -> tuple[int, int]:
# 括弧を明示し、左辺の並びを右辺の構造に合わせる
(x, y) = p
return x, y
pt = (3, 4)
a, b = split_point(pt)
print(a, b)
3 4
読みやすさは品質です。
同時代入は強力ですが、過度な技巧は避けて、誰が読んでも意図が分かるコードを目指しましょう。
まとめ
同時代入とアンパックは、Pythonらしい表現力を支える基本機能です。
右辺が先に評価されること、要素数を一致させること、連鎖代入は可変オブジェクトで避けることの3点を押さえておけば、大半の落とし穴を回避できます。
a, b = b, aのスワップや*restを使った柔軟な受け取りは、日常のコーディングを簡潔で安全にしてくれます。
まずはx, y = 1, 2
やhead, *body, tail = seq
といった基本パターンから手に馴染ませ、読みやすさ(PEP 8)を意識して積極的に活用していきましょう。