コンテンツ

Python速習ガイド - ステップ1 基本的な文法とデータ型 (6) - リスト (2)

Info
  • 本講座は、Pythonプログラミングの基礎を手を動かしながら最速で身につけるための講座です。
  • 「スタイルガイド」では、Pythonできれいなコードを書くためのガイドライン(PEP8)で紹介されている内容を主に記載しています。
  • 各コードは実行して結果を確認することができます。
    ページの再読み込みで元の内容に戻りますので、自由にいじってみてください。

「ステップ1 基本的な文法とデータ型」の続きです。

  • ある要素がリスト内に存在するかどうかを確認するためには、 in 演算子を使用します。
  • not in演算子を使用すると、in演算子と逆の判定を行います。
  • [開始位置:終了位置] のように範囲を指定して、リストの一部分を取得することができます。
    • このようにして取得できるリストの一部分のことを スライス といいます。
    • この場合、開始位置以上終了位置未満 (開始位置 <= x < 終了位置)の要素からなるスライスとなり、
      終了位置で指定したインデックスの要素は 含まれない ことに注意してください。
  • 指定した範囲が実際のリストの範囲を超えている場合、
    エラーは発生せずに、指定範囲のうち実際の範囲内のスライスを返します。
    • 指定した範囲に値が1つも存在しない場合、空のリスト([])を返します。
    • スライスの場合、範囲外のインデックスを指定してもIndexErrorは発生しません。
  • 「開始位置」を省略した場合([:終了位置])、先頭からのスライスとなります。
  • 「終了位置」を省略した場合([開始位置:])、末尾までのスライスとなります。
  • [開始位置:終了位置:ステップ数]のように指定すると、ステップ数で指定した個数飛びのスライスを取得します。
  • 「ステップ数」にマイナスの数を指定すると、逆順のスライスを取得できます。
    • 後ろから逆順に進むため、開始位置以下終了位置超 (開始位置 >= x > 終了位置)
      の範囲のスライスとなります。終了位置の要素はスライスに含みません。
  • マイナスのステップ数の場合、
    • 開始位置を空にすると、末尾からのスライスとなります。
    • 終了位置を空にすると、先頭までのスライスとなります。
📚練習問題

1~20を要素とするリストを使用して、以下を実施しましょう。

  1. 5~15の数からなるスライスを出力しましょう。
  2. 5の倍数のスライスを出力しましょう。
  3. 3の倍数の逆順のスライスを出力しましょう。
  • スライスを使用してリストの要素を書き換えることができます。
  • サイズの異なるリストの代入もエラーが発生せずできてしまうため、注意してください。

「1.4. 変数と代入」にて、変数は「ラベル」のようなものであると説明しました。
ここで、Pythonでよくある間違いを紹介して、変数が「ラベル」であることを示します。

上記コードを実行すると分かる通り、元の価格リスト(original_prices)も変化してしまっています。
この理由は、変数が「ラベル」であることを意識すると理解できます。

graph TD
    subgraph "ステップ3: 要素の変更"
        list3["[500, 1500, 5000]"]
        original_prices3[original_prices] --> list3
        new_prices3[new_prices] --> list3
    end
    
    subgraph "ステップ2: 新しい変数に代入"
        list2["[1000, 1500, 5000]"]
        original_prices2[original_prices] --> list2
        new_prices2[new_prices] --> list2
    end

    subgraph "ステップ1: 初期状態"
        list1["[1000, 1500, 5000]"]
        original_prices1[original_prices] --> list1
    end
    
    style list1 fill:#fff3bf,stroke:#333,stroke-width:2px
    style list2 fill:#fff3bf,stroke:#333,stroke-width:2px
    style list3 fill:#fff3bf,stroke:#ff0000,stroke-width:2px

このように、Pythonでは変数を新しい変数に代入した場合、
新しい変数にデータが移るのではなく、新しい変数も元のデータを指し示すラベルとなります。
(つまり、最初の変数も新しい変数もどちらも同じ元データを参照します。)

  • リストをコピーするには、copy()メソッドを使用します。
graph TB
    %% ステップ1: 初期状態
    subgraph step1["ステップ1: 初期状態"]
        direction TB
        op1[original_prices] --> list1["[1000, 1500, 5000]"]
    end
    
    step1 --> step2
    
    %% ステップ2: copy()メソッド実行後
    subgraph step2["ステップ2: copy()メソッドを使ってリストをコピー"]
        direction TB
        op2[original_prices] --> list2["[1000, 1500, 5000]"]
        list2 -. "copy()" .-> list2_copy["[1000, 1500, 5000] (コピー)"]
        np2[new_prices] --> list2_copy
    end
    
    step2 --> step3
    
    %% ステップ3: 変更後
    subgraph step3["ステップ3: 新しいリスト側の要素を変更後"]
        direction TB
        op3[original_prices] --> list3["[1000, 1500, 5000]"]
        np3[new_prices] --> list3_copy["[500, 1500, 5000]"]
    end
    
    %% スタイル設定
    style list1 fill:#fff3bf,stroke:#333,stroke-width:2px
    style list2 fill:#fff3bf,stroke:#333,stroke-width:2px
    style list3 fill:#fff3bf,stroke:#333,stroke-width:2px
    style list2_copy fill:#fff3bf,stroke:#333,stroke-width:2px
    style list3_copy fill:#fff3bf,stroke:#ff0000,stroke-width:2px
    linkStyle 3 stroke:#4285F4,stroke-width:2px
  • 開始位置と終了位置を省略したスライス[:]でも同様にコピーすることができます。

しかし、copy() メソッドやスライス [:] でも問題が生じる場合があります。
以下でその例を紹介します:

これは、copy()メソッドやスライス[:]では、外側のリスト([..., ..., ...])は複製されても
内側の値(["apple", 120]など)は複製されず、元のリストと同じ値を参照してしまうためです。
このようなコピーのことを「浅いコピー (シャローコピー)」といいます。

graph TB
    %% ステップ1: 初期状態
    subgraph step1["ステップ1: 初期状態"]
        direction TB
        np1[name_and_prices] --> outer1["[..., ..., ...]"]
        outer1 --> apple1["['apple', 120]"]
        outer1 --> orange1["['orange', 150]"]
        outer1 --> grape1["['grape', 180]"]
    end
    
    step1 --> step2
    
    %% ステップ2: copy()メソッド実行後
    subgraph step2["ステップ2: copy()メソッドを使ってリストをコピー"]
        direction TB
        np2[name_and_prices] --> outer2["[..., ..., ...]"]
        nnp2[new_name_and_prices] --> outer2_copy["[..., ..., ...] (コピー)"]
        
        outer2 --> apple2["['apple', 120]"]
        outer2 --> orange2["['orange', 150]"]
        outer2 --> grape2["['grape', 180]"]
        
        outer2_copy --> apple2
        outer2_copy --> orange2
        outer2_copy --> grape2
    end
    
    step2 --> step3
    
    %% ステップ3: 変更後
    subgraph step3["ステップ3: 新しいリスト内の要素(リスト)の要素を変更後"]
        direction TB
        np3[name_and_prices] --> outer3["[..., ..., ...]"]
        nnp3[new_name_and_prices] --> outer3_copy["[..., ..., ...] (コピー)"]
        
        outer3 --> apple3["['apple', 120]"]
        outer3 --> orange3["['orange', 200]"]
        outer3 --> grape3["['grape', 180]"]
        
        outer3_copy --> apple3
        outer3_copy --> orange3
        outer3_copy --> grape3
    end
    
    %% スタイル設定
    style outer1 fill:#fff3bf,stroke:#333,stroke-width:2px
    style outer2 fill:#fff3bf,stroke:#333,stroke-width:2px
    style outer3 fill:#fff3bf,stroke:#333,stroke-width:2px
    style outer2_copy fill:#fff3bf,stroke:#333,stroke-width:2px
    style outer3_copy fill:#fff3bf,stroke:#333,stroke-width:2px
    
    style apple1 fill:#ffc078,stroke:#333,stroke-width:2px
    style orange1 fill:#ffc078,stroke:#333,stroke-width:2px
    style grape1 fill:#ffc078,stroke:#333,stroke-width:2px
    style apple2 fill:#ffc078,stroke:#333,stroke-width:2px
    style orange2 fill:#ffc078,stroke:#333,stroke-width:2px
    style grape2 fill:#ffc078,stroke:#333,stroke-width:2px
    style apple3 fill:#ffc078,stroke:#333,stroke-width:2px
    style orange3 fill:#ffc078,stroke:#ff0000,stroke-width:2px
    style grape3 fill:#ffc078,stroke:#333,stroke-width:2px
  • 要素(リスト)内の要素を変更するのではなく、要素自体を(丸ごと)入れ替える場合は、
    「1.4.3. 変数への再代入」で説明した「ラベルの付け替え」操作となるため、元のリストには影響を与えません。
graph TB
    %% ステップ1: 初期状態
    subgraph step1["ステップ1: 初期状態"]
        direction TB
        np1[name_and_prices] --> outer1["[..., ..., ...]"]
        outer1 --> apple1["['apple', 120]"]
        outer1 --> orange1["['orange', 150]"]
        outer1 --> grape1["['grape', 180]"]
    end
    
    step1 --> step2
    
    %% ステップ2: copy()メソッド実行後
    subgraph step2["ステップ2: copy()メソッドを使ってリストをコピー"]
        direction TB
        np2[name_and_prices] --> outer2["[..., ..., ...]"]
        nnp2[new_name_and_prices] --> outer2_copy["[..., ..., ...] (コピー)"]
        
        outer2 --> apple2["['apple', 120]"]
        outer2 --> orange2["['orange', 150]"]
        outer2 --> grape2["['grape', 180]"]
        
        outer2_copy --> apple2
        outer2_copy --> orange2
        outer2_copy --> grape2
    end
    
    step2 --> step3
    
    %% ステップ3: 要素丸ごと入れ替え後
    subgraph step3["ステップ3: 新しいリストの1要素を丸ごと入れ替え後"]
        direction TB
        np3[name_and_prices] --> outer3["[..., ..., ...]"]
        nnp3[new_name_and_prices] --> outer3_copy["[..., ..., ...] (コピー)"]
        
        outer3 --> apple3["['apple', 120]"]
        outer3 --> orange3["['orange', 150]"]
        outer3 --> grape3["['grape', 180]"]
        
        outer3_copy --> apple3
        outer3_copy --> orange3
        outer3_copy -. "置き換え前" .-> grape3
        outer3_copy -- "置き換え後" --> banana3["['banana', 200]"]
    end
    
    %% スタイル設定
    style outer1 fill:#fff3bf,stroke:#333,stroke-width:2px
    style outer2 fill:#fff3bf,stroke:#333,stroke-width:2px
    style outer3 fill:#fff3bf,stroke:#333,stroke-width:2px
    style outer2_copy fill:#fff3bf,stroke:#333,stroke-width:2px
    style outer3_copy fill:#fff3bf,stroke:#333,stroke-width:2px
    
    style apple1 fill:#ffc078,stroke:#333,stroke-width:2px
    style orange1 fill:#ffc078,stroke:#333,stroke-width:2px
    style grape1 fill:#ffc078,stroke:#333,stroke-width:2px
    style apple2 fill:#ffc078,stroke:#333,stroke-width:2px
    style orange2 fill:#ffc078,stroke:#333,stroke-width:2px
    style grape2 fill:#ffc078,stroke:#333,stroke-width:2px
    style apple3 fill:#ffc078,stroke:#333,stroke-width:2px
    style orange3 fill:#ffc078,stroke:#333,stroke-width:2px
    style grape3 fill:#ffc078,stroke:#333,stroke-width:2px
    style banana3 fill:#ffc078,stroke:#ff0000,stroke-width:2px
📚練習問題

上記コードにて、copy()メソッドでコピーせずに実施した場合にどうなるか考えてみましょう。

浅いコピーと深いコピー

上記の浅いコピー(シャローコピー)に対して、その要素や要素の要素などすべて含めて、
完全に複製するコピーのことを「深いコピー (ディープコピー)」といいます。

深いコピーはcopyライブラリのdeepcopy関数を用いて、以下のようにして実施できます:
(「ライブラリ」については、詳しくは後ほど解説します)

  • リストの要素をそれぞれ別の変数に代入したい場合、イコール(=)の左側に変数をカンマ(,)区切りで書くことで代入できます。
    • この操作のことを アンパック (unpack) といいます。
  • 変数の数と代入値の要素数が異なる場合、ValueErrorが発生します。
  • アンパック演算子 * を使うと、「最初の要素」と「その他」で分けるようなアンパックが可能です。
    (掛け算で使われる2項演算子の * とは異なり、変数の頭に*をつけます。)
  • 複数のアンパック演算子 * を使ったアンパックは SyntaxError (構文エラー)が発生します。
  • アンパックで使わない値はアンダースコア (_) で受け取ることができます。
    • これはその値を使わないということを明示するためのPythonでの慣習です。
  • リストにbool関数を適用すると、空のリスト([])の場合にFalse、それ以外にTrueを返します。

関連記事