変更要約: 初版
6.1Pythonの基本要素とスクリプトの解釈
変数・リスト・辞書・ループ・条件分岐・関数というPythonの基本要素を、「文法の暗記」ではなく目の前のスクリプトが何をするか/どこが誤っているかを読み解く道具として学びます。ネットワーク自動化で頻出の「機器一覧を回して条件に合う機器だけ処理する」パターンを、インデント・キーの有無・返り値という典型的な破綻点から診断できるようにします。
ENCORのドメイン6でPythonが問われるとき、期待されているのはプログラマとしての実装力ではなく、運用者としての読解力です。試験に出るのは「変数とは何か」ではなく、十数行のスクリプトが提示され、その出力を予測するか、意図どおりに動かない原因を指摘する形です。ネットワーク自動化のスクリプトは驚くほど型が決まっていて、ほとんどが「機器の一覧(リスト)を回し、各機器の属性(辞書)を見て、条件に合うものだけ処理する」という骨格をしています。この骨格を押さえたうえで、インデントがループの内か外か・辞書に本当にそのキーがあるか・関数が値を返しているかという3つの破綻点を見る癖をつければ、初見のスクリプトでも判断できます。
6.1.1データを保持する要素:変数・リスト・辞書
- リスト(
[])は順序のある可変長の並びで、devices = ["r1", "r2", "r3"]のように同種のものを列挙する。要素は0から始まる番号で取り出す(devices[0]は"r1")ので、「3台あるからdevices[3]」と考えると範囲外エラーになる。ネットワーク自動化では機器一覧・インタフェース一覧・取得結果の並びがリストになる。 - 辞書(
{})はキーと値の対応で、dev = {"host": "r1", "ip": "10.0.0.1"}のように1台の属性をまとめる。取り出しはdev["ip"]だが、存在しないキーを角括弧で引くとKeyErrorで停止する。停止させたくない場合はdev.get("ip")(無ければNone)やif "ip" in dev:で存在確認するのが定石で、機器ごとに項目の有無がまちまちなインベントリを扱う実務ではこの差が効く。 - リストと辞書は入れ子にできるのが実務上の要点で、
inventory = [{"host": "r1", "role": "core"}, {"host": "r2", "role": "access"}]のような「辞書のリスト」がAPI応答やインベントリの標準形になる。この形ならfor dev in inventory:で1台ずつ取り出し、dev["role"]で属性を判定できる——外側がリスト(何台か)/内側が辞書(1台の属性)という対応を読み違えないこと。
6.1.2制御を担う要素:ループ・条件分岐・インデント
- ループ(
for dev in inventory:)は並びの要素を1つずつ処理する。Pythonではインデント(字下げ)がブロックの範囲そのものであり、他言語の波括弧に相当する。したがって同じ行でもインデント1段の差でループの内側か外側かが変わり、実行回数が「台数分」から「1回だけ」に変わる——集計結果の出力がループ内にあると台数分だけ出力され、外にあると最後に1回だけ出力される。 - 条件分岐(
if/elif/else)は判定に応じて処理を分ける。ここで頻出の誤りが=(代入)と(比較)の取り違えと、大文字小文字の不一致("Core""core"は偽)である。またcontinueはそのループ回のみ飛ばして次の要素へ進み、breakはループ自体を打ち切る——「該当しない機器を飛ばす」つもりでbreakを書くと、最初の非該当機器で処理全体が終わる。 - 関数(
def check(dev):)は処理に名前を付けて再利用する。返り値が要るならreturnを書く必要があり、returnのない関数はNoneを返す——result = check(dev)としたのに関数内でprintしかしていなければ、resultはNoneになり、その後のif result:は常に偽になる。「画面に出ている=値が返っている」ではない点が、スクリプト診断で最も見落とされる。
ENCORのPython設問は提示されたスクリプトの動作予測と誤り指摘が本体です。読む順序を固定しましょう:(1)データの形(リストか辞書か・入れ子か)→(2)ループの範囲(インデントで何が内側か)→(3)条件の判定式(か=か・大小文字)→(4)関数のreturnの有無。この4点で、出力が「台数分か1回か」「途中で止まるか」「常に偽になるか」がほぼ説明できます。
運用チームから「コアスイッチだけを対象にバックアップ対象の一覧を作るスクリプトを書いたが、期待どおりに動かない」と相談され、次のコードを見せられたとします。inventory = [{"host": "sw1", "role": "core"}, {"host": "sw2", "role": "access"}, {"host": "sw3", "role": "core"}] に続けて、targets = [] を用意し、def is_core(dev): の中で if dev["role"] "core": print(dev["host"]) とだけ書き、呼び出し側は for dev in inventory: if is_core(dev): targets.append(dev["host"])、最後に print(len(targets)) としている——実行すると画面にはsw1とsw3が出るのに、最後の件数が0と表示される、という症状です。ここで「printされているのだから関数は正しく判定している」と考えるのが最大の落とし穴で、is_coreにはreturnがありません。関数は画面に出力こそすれ、呼び出し元へは常にNoneを返し、Noneは偽と評価されるためtargets.append(...)は一度も実行されない——だからlen(targets)は0になります。修正はprintをreturn True(および該当しないときreturn False)へ改めることで、「表示する」と「値を返す」は別の行為だという原則がそのまま診断の鍵になります。ここでもし相談者が「inventoryの書き方が悪いのでは」とfor dev in inventory: を for dev in inventory[0]: に変えれば、外側のリストではなく1台目の辞書を回してしまい、devにはキー文字列("host", "role")が入ってdev["role"]がTypeErrorになります——外側がリスト・内側が辞書という構造の読み違えです。さらに別のよくある症状として、print(len(targets)) をループの内側にインデントしてしまうと、件数が最後に1回ではなく台数分(3回)出力されます。エラーではなく「回数がおかしい」症状を見たら、まずインデントの段数を疑うのが定石です。そして条件をif dev["role"] = "core":と書けば構文エラーで起動すらしない、if dev["Role"] "core":と書けばKeyErrorで最初の1台目で停止する——症状(何も動かない/途中で止まる/件数が合わない/常に空)から破綻点を逆引きできるようになることが、この節の到達点です。
| 症状 | 疑うべき破綻点 | 確認の一手 | 修正 |
|---|---|---|---|
| 画面には出るが集計が0件 | 関数に`return`が無く`None`が返る | `def`の中に`return`があるか | `print`を`return True`/`return False`に改める |
| 最初の1台目で停止する | 存在しないキーを角括弧で参照し`KeyError` | キー名の綴り・大文字小文字 | `dev.get("ip")`または`if "ip" in dev:`で存在確認 |
| 出力回数が台数分に増える | 本来ループ外の行がインデントで内側に入っている | その行のインデント段数 | インデントを1段戻してループ外に出す |
| 2台目以降が処理されない | 該当しない要素を飛ばす意図で`break`を使った | `break`か`continue`か | `continue`に置き換える |
| そもそも実行されない | 比較の`==`を代入の`=`と書いた等の構文エラー | `if`行の比較演算子 | `==`に修正する |
ひっかけ: 「関数内でprintしているのだから、呼び出し元のifも真になる」は誤りです——returnの無い関数はNone(偽)を返すので、表示されていても後続の判定は通りません。また「インデントは読みやすさのための飾り」も誤り=Pythonではインデントがブロックの定義そのもので、1段の差が実行回数を変えます。さらに「3台あるからdevices[3]」も誤り=添字は0起点なので3台目はdevices[2]です。
6.1.3この節のまとめ
- 自動化スクリプトの標準形は「辞書のリスト」を
forで回しifで絞る構造で、外側=台数のリスト/内側=属性の辞書を読み違えないことが出発点 - スクリプトの誤りはインデント(ループの範囲)・キーの存在(
KeyError)・returnの有無(None)の3点に集中し、症状から破綻点を逆引きできる - 「表示する」と「値を返す」は別であり、
printは診断の材料にはなっても呼び出し元の判定を真にはしない
進捗の記録にはログインが必要です。
理解度チェック
(軽い確認用)Q1. コアスイッチだけを抽出するスクリプトで、関数 `def is_core(dev):` の本体は `if dev["role"] == "core": print(dev["host"])` のみであり、呼び出し側は `for dev in inventory: if is_core(dev): targets.append(dev["host"])` としている。実行すると該当機器のホスト名は画面に表示されるが、最後の `print(len(targets))` は常に 0 になる。原因として最も適切なものはどれか。
Q2. インベントリを走査して未対応機器をスキップする意図で、`for dev in inventory:` の中に `if dev["os"] != "IOS-XE": break` と書いた。インベントリの2番目の要素が `IOS-XE` 以外だったとき、このスクリプトの実際の挙動として最も適切なものはどれか。
Q3. インベントリの各辞書から管理IPを取り出す処理で、一部の機器にだけ `mgmt_ip` キーが無い。現在は `ip = dev["mgmt_ip"]` と書いており、キーの無い機器に到達した時点でスクリプトが停止してしまう。残りの機器の処理を継続させる修正として最も適切なものはどれか。

