Pythonではアンダースコア( _ )が多目的に使われます。
本記事では一時変数として値を捨てる使い方、国際化(i18n)の翻訳関数としての使い方、プライベート変数の目印としての使い方を、初心者向けに丁寧に解説します。
どれも日常的に登場するため、使い分けを理解しておくとコードの可読性と保守性が高まります。
以下の表は本記事の要点を先にまとめたものです。
用途 | 代表的な書き方 | 目的 | 主な注意点 |
---|---|---|---|
一時(捨て)変数 | for _ in range(n) , (x, _) | 不要な値を明示的に無視する | 意味のある値を入れない。i18nの_() と競合注意 |
国際化(i18n) | _ = gettext.gettext , _("...") | メッセージ文字列を翻訳する | 捨て変数の_ と同ファイル併用しない。REPLの_ とは別物 |
プライベート目印 | _name , __name | 内部用/名前マングリング | 絶対的な秘匿ではない。PEP8とimport * の挙動理解 |
_ を一時変数として使う
捨てたい値に_
を代入して「使わない」意思を示すのがPythonの慣習です。
PEP8でも_
はダミー名として一般的で、静的解析ツールも「未使用変数」として許容することが多いです。
for _ in range(n) で値を捨てる
ループ変数が不要な場合に_
を使います。
意図が一目でわかり、読み手に親切です。
# 3回だけ処理したいが、カウンタ値自体は使わない例
for _ in range(3):
print("tick")
tick
tick
tick
タプルのアンパッキングで不要要素を _ に入れる
戻り値などの一部だけ使いたい時に、不要部分を_
に受けます。
# 緯度経度高度のタプルから、高度は捨てて緯度と経度だけ使う
def get_position():
return (35.6895, 139.6917, 44) # lat, lon, altitude
lat, lon, _ = get_position() # 高度は捨てる
print(f"lat={lat}, lon={lon}")
lat=35.6895, lon=139.6917
enumerate で index や値を無視する( _, v )/( i, _ )
enumerate
の戻りは(index, value)
です。
どちらか一方だけ必要なら_
で捨てます。
names = ["Ada", "Grace", "Guido"]
# indexは不要
for _, name in enumerate(names):
print(name.upper())
# 値は不要で、indexだけ使いたい
for i, _ in enumerate(names):
print(f"position = {i}")
ADA
GRACE
GUIDO
position = 0
position = 1
position = 2
複数要素を無視する慣習( _ を繰り返し使う)
_
はあくまで「使わない」という印(しるし)なので、複数あっても同じ_
を繰り返し使います。
# 5要素の戻り値のうち、先頭と末尾だけ使う
def complex_result():
return (1, "ignore1", "ignore2", "ignore3", 99)
head, _, _, _, tail = complex_result()
print(head, tail)
1 99
補足: Pythonには「スター付きアンパッキング」(例: head, *_, tail = seq
)もありますが、本記事の主題外なので名称のみの紹介に留めます。
注意: _ は「使い捨て」専用にして意味のある値を入れない
意味のある値を_
に代入しないでください。
読み手に「ここは無視される」と誤解させ、深刻なバグの温床になります。
特に、後述のi18nで_
を翻訳関数にした後にfor _ in range(...)
を同ファイルで書くと壊れます。
# 悪い例: 先に _ を翻訳関数として定義したのに、あとで捨て変数に使ってしまう
from gettext import gettext as _
print(_("Start")) # ここはOK
for _ in range(3): # ここで _ の束縛が int に上書きされる(モジュールスコープの場合)
pass
print(_("End")) # TypeError: 'int' object is not callable になる
Traceback (most recent call last):
...
TypeError: 'int' object is not callable
回避策: i18nで_
を使うファイルでは、捨て変数に_
を使わず__
や_unused
など別名にするのが安全です。
_ を国際化(i18n)で翻訳関数として使う
多くのPythonプロジェクトでは翻訳関数に_
という短い別名を与える慣習があります。
短いのでメッセージ文字列が読みやすく、i18n対応がしやすくなります。
gettext で _ = gettext.gettext を定義する
標準ライブラリgettext
を使うと、翻訳資源(拡張子.mo
)があれば自動で翻訳されます。
資源がなければ原文のまま表示されます。
# i18nの最小構成例: 資源がなければ原文のまま(フォールバック)
import gettext
# 実際には 'locale/<言語>/LC_MESSAGES/messages.mo' を用意します
localedir = "locale"
domain = "messages"
lang = "ja"
try:
trans = gettext.translation(domain=domain, localedir=localedir, languages=[lang])
_ = trans.gettext # 慣習的に _ に束縛
except FileNotFoundError:
_ = gettext.gettext # フォールバック(翻訳なし)
print(_("Hello"))
name = "Taro"
# 変数埋め込みは f-string ではなく後段で format するのが定石
print(_("Hello, {name}!").format(name=name))
Hello
Hello, Taro!
ポイント: f-string で_("Hello, {name}!")
を囲むと翻訳時点で変数が展開されてしまい、翻訳キーが一致しなくなる恐れがあります。
プレースホルダを残したまま翻訳してから.format
で差し込むのが安全です。
メッセージを _(“…”) で囲む基本パターン
_("メッセージ")
で囲んだ文字列が翻訳抽出対象になります。
複数箇所で使うメッセージは、同一の原文に統一すると訳が再利用され品質が安定します。
# よくある利用パターン
print(_("File not found"))
print(_("Permission denied"))
File not found
Permission denied
注意: 捨て変数の _ と競合させない(同一ファイルで併用しない)
同じファイルで「捨て変数の_
」と「翻訳関数の_
」を併用しないでください。
特にモジュールスコープのfor _ in ...
は翻訳関数を上書きします。
どうしても必要なら、翻訳関数に_t
やL
など別名を使います。
# 競合を避けるための別名パターン
from gettext import gettext as _t # 翻訳関数は _t
for _ in range(3): # こちらは捨て変数として _
pass
print(_t("Done"))
Done
注意: REPL の直前結果の _ とは別物である点に注意
対話実行環境(REPL)では、_
が「直前の評価結果」を指す特別名として使えますが、スクリプトでは単なる通常の識別子です。
# (REPLの例) 対話モードだけの振る舞い
# >>> 1 + 2
# 3
# >>> _
# 3
# >>> _ + 10
# 13
# スクリプトでは上記のような自動束縛は起きません。
# _ は自分で代入した時だけ意味を持ちます。
混同しないコツ: REPLで_
を手動で再代入するのは避け、スクリプトでは用途ごとに命名規則を守ると安全です。
_ をプライベート変数の目印に使う
先頭にアンダースコアを付けると「公開APIではない」意思表示になります。
Pythonはアクセス制御を強制しませんが、PEP8に沿った慣習として広く使われています。
先頭1つのアンダースコア(_name)は「内部用」の慣習
_name
はモジュールやクラスの内部実装用であることを示します。
アクセスは技術的には可能ですが、外部から触らないのが前提です。
class Cache:
def __init__(self):
self._store = {} # 内部用
def set(self, key, value):
self._store[key] = value
def get(self, key, default=None):
return self._store.get(key, default)
c = Cache()
c.set("x", 10)
print(c.get("x"))
# 可能だが非推奨: 外部から _store を直接触る
print("internal keys =", list(c._store.keys()))
10
internal keys = ['x']
先頭2つのアンダースコア(__name)は名前マングリング
__name
はクラス定義内で名前マングリングされ、実際には_ClassName__name
という内部名に置き換えられます。
サブクラスからの誤った上書きを防ぎやすくなります。
class User:
def __init__(self, token: str):
self.__token = token # マングリング対象
def masked(self) -> str:
return self.__token[:2] + "***"
u = User("ABCD1234")
print(u.masked())
# 直接アクセスは失敗する
try:
print(u.__token)
except AttributeError as e:
print("AttributeError:", e)
# 実際の属性名(マングリング後)を使えば参照できてしまう(非推奨)
print("mangled access =", u._User__token)
# 確認
print([name for name in dir(u) if "token" in name])
AB***
AttributeError: 'User' object has no attribute '__token'
mangled access = ABCD1234
['_User__token']
補足: __init__
や__str__
のように前後をダブルアンダースコアで挟む「マジックメソッド」は特別扱いで、通常のマングリング対象とは目的が異なります。
絶対的な非公開ではないが外部から触らないのが原則
アンダースコアは契約(コントラクト)を文書化するための信号です。
動的言語の理念に沿い、強制ではなく合意で守ります。
テストやデバッグで内部に触れる時は、影響範囲を把握し自己責任で行います。
PEP8 に沿った命名と from module import * の挙動を理解する
PEP8では_internal
のような先頭アンダースコアは「非公開」を意味し、from module import *
では先頭_
の名前はインポートされません。
さらに__all__
を定義すると公開シンボルを明示できます。
以下は2ファイル構成の例です。
# mylib.py
__all__ = ["public_func"] # 公開APIを明示
_public = "visible-but-not-exported" # 先頭1つ -> import * では除外(ただし __all__ があればそちらが優先)
_internal = 42 # これも非公開意図
def public_func():
return "I am public"
def _helper():
return "I am internal"
# main.py
from mylib import *
print(public_func())
# 非公開名は import * で入ってこない
names = [n for n in dir() if n.endswith(("helper", "_internal", "_public"))]
print("imported names:", names)
# 明示的に import すれば取れるが、慣習上は避ける
import mylib
print("explicit:", mylib._helper(), mylib._internal)
I am public
imported names: []
explicit: I am internal 42
指針:
- ライブラリ作者は
__all__
で公開APIを明示すると利用者に優しいです。 - 利用者側は
from module import *
を避け、import module
やfrom module import name
のように明示すると可読性が上がります。
まとめ
本記事ではアンダースコア( _ )の3つの主要用途を紹介しました。
捨て変数としての_
は「使わない」意思の明確化に役立ち、i18nでは_ = gettext.gettext
の慣習により翻訳文字列が簡潔に書けます。
プライベートの目印としての_name
や__name
は公開範囲を示し、PEP8やimport *
の挙動とも整合します。
最後にもう一度だけ注意点を整理します。
i18nの_
と捨て変数の_
を同じファイルで混在させない、意味のある値を_
に入れない、そしてアンダースコアは強制ではなく契約であるという3点です。
これらを守れば、読みやすく安全なPythonコードに一歩近づけます。