プログラムでデータを扱うとき、値を一時的に保存しておく箱のような役割を持つのが変数です。
Pythonでは簡潔な書き方で変数を扱える一方で、名前の付け方や参照の仕組みを理解しないと予期せぬ挙動が起きます。
本記事では初心者がつまずきやすいポイントを避けつつ、変数の基本から実践、注意点まで体系的に解説します。
Pythonの変数とは何か
変数の役割とデータを保存する仕組み
Pythonの変数は値そのものを格納するのではなく、オブジェクトへの参照を名前に結びつけるものです。
たとえば、x = 10
は数値オブジェクト10を作り、名前x
をそのオブジェクトに紐付けます。
紐付けは何度でも変更でき、別の型のオブジェクトにも付け替えられます。
# 変数はオブジェクトへの参照(紐付け)
x = 10 # 整数オブジェクト10にxを紐付ける
print(x, type(x))
x = "hello" # 文字列オブジェクト"hello"へ紐付け直す
print(x, type(x))
10 <class 'int'>
hello <class 'str'>
このようにPythonでは型宣言が不要で、同じ変数名に異なる型の値を再度紐付けできます。
代入(=)と再代入の基本
=
は代入演算子であり、右辺の評価結果となるオブジェクトに左辺の名前を結びつけます。
すでに同名の変数がある場合は再代入となり、以前の紐付けは上書きされます。
a = 3 + 4 # 右辺を評価してaに紐付け
print(a)
a = a * 2 # aを読み出して計算し、その結果にaを紐付け直す
print(a)
7
14
変数名の付け方(PEP 8)
PythonのスタイルガイドPEP 8では、読みやすさを最優先とし、変数名はスネークケース(snake_case)を推奨します。
英字かアンダースコアで始め、英数字とアンダースコアのみを使います。
予約語は使えません。
よくある良い例と悪い例は次の通りです。
悪い例 | 良い例 | 理由 |
---|---|---|
x | item_count | 何を数えるか明確にするため |
tmp | file_path | 一時的という曖昧さを避けるため |
Data | user_data | 先頭大文字はクラス名に使う習慣があるため |
n1, n2 | width, height | 意味を持つ語で可読性を高めるため |
list | items | 組み込み名(list)の上書きを避けるため |
組み込み名の上書きはバグの原因です。
たとえばlist = [1, 2]
とすると、以後list()
コンストラクタが使えなくなります。
# 悪い例: 組み込み名の上書き
list = [1, 2, 3]
# print(list("abc")) # TypeError: 'list' object is not callable になる
del list # 使ってしまったらdelで解放して元に戻すのが一案
print(list("abc")) # 文字列を1文字ずつのリストへ
['a', 'b', 'c']
- 関連記事:コーディング規約PEP8の書き方まとめ
定数の書き方(ALL_CAPS)
Pythonには言語レベルの定数はありませんが、変更しない値はALL_CAPSで表記するのが慣習です。
これはあくまで合意であり、技術的には再代入できます。
# 定数の慣習的な書き方
PI = 3.14159
MAX_RETRY = 3
print(PI, MAX_RETRY)
# 技術的には再代入できてしまう
PI = 3.14
print(PI)
3.14159 3
3.14
- 関連記事:型ヒントの書き方と使い方
Python変数の書き方と実践
初期化の基本と命名のコツ
変数は使用前に明示的に初期化すると意図が明確になります。
特に計数や合計などでは0で初期化し、文字列の構築には""
、コレクションには[]
や{}
を使います。
名前は「目的」や「単位」を含めると読みやすくなります。
# 初期化の例
total_price = 0 # 合計金額(円)
user_name = "" # ユーザー名
items = [] # 商品リスト
is_valid = False # 妥当性フラグ
# 値の更新
total_price += 1200
user_name = "Sato"
items.append("Book")
is_valid = True
print(total_price, user_name, items, is_valid)
1200 Sato ['Book'] True
複数代入・アンパックの使い方
Pythonは複数の値を一度に代入できます。
タプルやリストをアンパックする書き方は、読みやすくエラーも減らせます。
# 複数代入
x, y = 10, 20
print(x, y)
# リスト/タプルのアンパック
point = (3, 5)
px, py = point
print(px, py)
# アスタリスク付きアンパック: 残りをまとめて受け取る
first, *middle, last = [1, 2, 3, 4, 5]
print(first, middle, last)
10 20
3 5
1 [2, 3, 4] 5
- 関連記事:タプルのアンパッキングのやり方
スワップ(値の入れ替え)の書き方
一時変数なしで値を入れ替えられるのはPythonの強みです。
a, b = 1, 9
a, b = b, a # スワップ
print(a, b)
9 1
可読性が上がる変数名の例
可読性は保守性そのものです。
抽象的なtmpやdataを避け、文脈で特定できる名詞を使うと差が出ます。
単位や型のヒントを含めるのも有効です。
目的 | 悪い例 | 良い例 | 説明 |
---|---|---|---|
残り時間 | t | remaining_seconds | 単位を含めると誤用防止に役立つ |
税込金額 | price2 | total_price_with_tax | 意味が伝わる複合語を使用 |
設定ファイルのパス | cfg | config_path | 役割と型のニュアンスを含む |
注文一覧 | arr | orders | データの中身が分かる複数形 |
短さよりも明確さを優先しましょう。
変数のスコープと寿命
ローカルとグローバルの違い
スコープは名前が有効な範囲です。
関数の中で代入した変数はローカルになり、関数外の変数はグローバルです。
同名がある場合、ローカルが優先されます。
x = "global"
def demo():
x = "local" # 関数内で代入するとローカル扱い
print("inside:", x)
demo()
print("outside:", x)
inside: local
outside: global
- 関連記事:ローカル変数とグローバル変数
globalとnonlocalの使い方
関数内から外側の変数を書き換えたいときはglobal
やnonlocal
を使います。
ただし使い過ぎは可読性を損なうため注意します。
# globalの例: モジュールレベルの変数を更新
counter = 0
def inc():
global counter # グローバル変数を明示して更新
counter += 1
inc(); inc()
print(counter)
# nonlocalの例: 一つ外側の関数スコープの変数を更新
def outer():
count = 0
def inner():
nonlocal count # 直近の外側スコープの変数
count += 1
return count
print(inner(), inner(), inner())
outer()
2
1 2 3
NameErrorの原因と対処
NameErrorは「その名前が見つからない」ときに発生します。
典型例は以下の通りです。
- 参照前に代入していない
- スコープの勘違い(関数内で代入してローカル化してしまう)
- 綴りミスや大文字小文字の違い
# 参照前に代入がない例
def f():
# print(msg) # NameError: msgはまだ定義されていない
msg = "hi"
return msg
print(f())
# スコープが原因の例
x = 10
def g():
# xを読みたいだけでも、代入があるとローカル扱いになりエラー
# print(x) # UnboundLocalErrorになる: ローカルxが未初期化
y = x + 1 # ← ここでxを参照しているため問題
return y
# 対処1: 代入をやめて読むだけにする、または
def g_fixed_readonly():
return x + 1
# 対処2: グローバルを明示
def g_fixed_global():
global x
x = x + 1
return x
print(g_fixed_readonly())
print(g_fixed_global())
hi
11
12
- 関連記事:よくある5大エラーの原因と対策
変数とオブジェクトの関係
参照としての変数を理解する
変数はオブジェクトの別名にすぎません。
別名同士は同じオブジェクトを指すため、片方の変更がもう片方にも見えます。
可変オブジェクトではこの性質が重要です。
# 同じリストオブジェクトを指す別名
a = [1, 2]
b = a # bはaと同じリストを参照
b.append(3)
print("a:", a, "b:", b, "same_obj:", a is b)
a: [1, 2, 3] b: [1, 2, 3] same_obj: True
再代入とコピーの違い
再代入は名前の紐付け先を入れ替えるだけです。
コピーは新しいオブジェクトを作って中身を複製します。
浅いコピーと深いコピーの違いにも注意します。
import copy
orig = [[1, 2], [3, 4]]
alias = orig # 参照の共有(別名)
shallow = list(orig) # 浅いコピー(内側は共有)
deep = copy.deepcopy(orig) # 深いコピー(内側も複製)
orig[0].append(99) # 内側のリストを書き換え
print("orig:", orig)
print("alias:", alias) # 共有なので変化が見える
print("shallow:", shallow) # 浅いコピーも内側は共有なので見える
print("deep:", deep) # 深いコピーは影響を受けない
orig: [[1, 2, 99], [3, 4]]
alias: [[1, 2, 99], [3, 4]]
shallow: [[1, 2, 99], [3, 4]]
deep: [[1, 2], [3, 4]]
よくある落とし穴とベストプラクティス
落とし穴1: ミュータブルなデフォルト引数。
関数定義時に評価され、一度作られたリストや辞書が使い回されます。
思わぬ共有を避けるにはNone
を使って内部で生成します。
# 悪い例
def append_item_bad(item, bucket=[]): # ← 一度作られたリストが使い回される
bucket.append(item)
return bucket
print(append_item_bad(1))
print(append_item_bad(2)) # 前回の続きになってしまう
[1]
[1, 2]
# 良い例
def append_item_good(item, bucket=None):
if bucket is None:
bucket = [] # 呼び出しごとに新規作成
bucket.append(item)
return bucket
print(append_item_good(1))
print(append_item_good(2)) # 独立したリストになる
[1]
[2]
落とし穴2: 破壊的操作と非破壊的操作の混同。
例えばlist.append
は破壊的で戻り値はNone
です。
nums = [1, 2]
result = nums.append(3) # 破壊的: numsを書き換える。戻り値はNone
print(nums, result)
[1, 2, 3] None
ベストプラクティスとして次の指針が有益です。
- 意味のある名前を付け、スネークケースに従う
- 不変にしたい値はALL_CAPSで表す
- 可変オブジェクトの共有に注意し、必要ならコピーする
- スコープを意識し、globalやnonlocalの多用は避ける
まとめ
本記事では、Pythonの変数はオブジェクトへの参照であり、代入は名前の紐付けを変える操作であることを中心に、命名規則、スコープ、アンパックやスワップの実践、コピーの考え方、そして落とし穴と対策までを丁寧に解説しました。
特に可変オブジェクトの共有とスコープ起因のNameErrorは初心者がつまずきやすい部分です。
意味のある変数名とPEP 8に基づくスタイルを心がけ、必要に応じて浅いコピー・深いコピーを使い分ければ、予期せぬバグを効果的に防げます。
最後に、定数はALL_CAPSで意図を伝え、読みやすいコードを常に目指すことが上達への近道です。
- 関連記事:型ヒント×mypyでバグを未然に防ぐ