today::エンジニアに憧れる非エンジニア

今のところは、エンジニアとは言えないところの職種です。しかしエンジニア的なものの考え方に興味津津。

「Head First Python」2章 - panic.pyの動作を説明してみる

概要

「Head First Python 第2版」を進めていった中で考えたことの記録です。

Head First Python 第2版 ―頭とからだで覚えるPythonの基本

Head First Python 第2版 ―頭とからだで覚えるPythonの基本

  • 作者:Paul Barry
  • 発売日: 2018/03/24
  • メディア: 単行本(ソフトカバー)

サンプルコード

phrase = "Don't panic!"
plist = list(phrase)
print(phrase)
print(plist)
for i in range(4):
    plist.pop()
plist.pop(0)
plist.remove("'")
plist.extend([plist.pop(), plist.pop()])
plist.insert(2, plist.pop(3))
new_phrase = ''.join(plist)
print(plist)
print(new_phrase)

最終的にどうなるのか

標準出力に以下の内容が表示されます。

Don't panic!
['D', 'o', 'n', "'", 't', ' ', 'p', 'a', 'n', 'i', 'c', '!']
['o', 'n', ' ', 't', 'a', 'p']
on tap

内部的な動作は以下となります。

  1. 文字列Don't panic!を生成し、リストに変換する
  2. 1.で生成された文字列の内容とリストの内容を標準出力に表示する
  3. 1.で生成されたリストに様々な操作を行い、文字列on tapが生成されるようなリストに作り替える
  4. 3.の操作を行ったリストから、文字列on tapを生成する
  5. 3.の操作を行ったリストの内容と4.で生成された文字列の内容を標準出力に表示する

このコードで行う処理を理解するにあたり、重要な事柄は以下と思われます。

  • Don't panic!という文字列には、on tapという文字列に必要な文字が全て含まれている

上記サンプルコードを実行して発生すること

初期状態を作る

phrase = "Don't panic!"
plist = list(phrase)
print(phrase)
print(plist)
  1. 変数phraseに、文字列リテラル1Don't panic!を代入する
  2. 変数phraseの内容を元としてリストを生成し、生成されたリストを変数plistに代入する
    • 生成されるのは、「変数phraseの1文字を1要素としたリスト」である
  3. 変数phraseが指す内容を標準出力に表示する
  4. 変数plistが指す内容を標準出力に表示する

この時点で、標準出力には以下の内容が表示されます。

Don't panic!
['D', 'o', 'n', "'", 't', ' ', 'p', 'a', 'n', 'i', 'c', '!']

末尾4文字は不要なので削除する

Don't panic!という文字列のうち、末尾4文字のnic!on tapという文字列に含まれません。文字列on tapを生成するには不要な文字なので、plistから除去してしまいます。

引数なしのpop()をリストに対して実行すると、対象のリストの末尾1要素が除去されます。plistに対してpop()を4回実行すれば、末尾4要素が除去されるわけです。

Pythonにおいて「処理を4回繰り返し実行する」最もシンプルな方法は、「大きさ4の範囲を対象としてfor文を実行する」というものですね。

結果、plist.pop()を4回繰り返し実行するコードは以下になります。

for i in range(4):
    plist.pop()

実際にplistに対してpop()を4回実行すると、各回の結果は以下のようになります。

plist.pop()
# plist => ['D', 'o', 'n', "'", 't', ' ', 'p', 'a', 'n', 'i', 'c']

plist.pop()
# plist => ['D', 'o', 'n', "'", 't', ' ', 'p', 'a', 'n', 'i']

plist.pop()
# plist => ['D', 'o', 'n', "'", 't', ' ', 'p', 'a', 'n']

plist.pop()
# plist => ['D', 'o', 'n', "'", 't', ' ', 'p', 'a']

他の不要な文字を削除する

現時点のplistから文字列を生成すると、結果は以下のようになります。

print(''.join(plist))
# => Don't pa

この時点では、文字列on tapを生成するためにはD'という不要な文字が残っています。この2文字をplistから除去する必要があります。

最初の不要な文字であるDは、plistの先頭の要素として存在します。Pythonにおいて、リストの先頭の要素を指すインデックス番号は0です。ということは、「plistに対してpop(0)を実行すれば、plistからDを取り除くことができる」ことになりますね。

# plist => ['D', 'o', 'n', "'", 't', ' ', 'p', 'a']
plist.pop(0)
# plist => ['o', 'n', "'", 't', ' ', 'p', 'a']

もう一つの不要な文字である'は、文字そのものを指定して取り除くこととしましょう。Pythonのリストにおいては、例えばxという値を持つ最初の要素を取り除く場合、remove(x)を実行すればOKです。"'"という値であれば、remove("'")を実行すればよいわけですね。

# plist => ['o', 'n', "'", 't', ' ', 'p', 'a']
plist.remove("'")
# plist => ['o', 'n', 't', ' ', 'p', 'a']

必要な文字だけが残ったので、順番を入れ替える

現時点のplistから文字列を生成すると、結果は以下のようになります。

print(''.join(plist))
# => ont pa

plistにはon tapという文字列を生成するために必要な文字だけが残っています。しかしながら、順番が一致していません。ont paon tapにするには、例えば以下の操作の組が必要となります。

  1. 現在4文字目にある空白文字を3文字目に移動する
    • ont paの4文字目にある空白文字を3文字目に移動すると、on tpaになる
  2. 末尾2文字を入れ替える
    • on tpaの末尾2文字を入れ替えると、on tapになる
末尾2文字を入れ替える

同じリストに対し、引数なしのpop()メソッドを2回実行すると、各pop()メソッドの戻り値はそれぞれ以下となります。

  • 1回目のpop()メソッドの戻り値…元のリスト末尾の要素の値
  • 2回目のpop()メソッドの戻り値…元のリスト末尾から2つ目の要素の値
plist = ['o', 'n', 't', ' ', 'p', 'a']
print(plist.pop())
# => a
print(plist.pop())
# => p
# plist => ['o', 'n', 't', ' ']

「あるリストに対してpop()メソッドを2回実行し、実行結果を順番に並べたリスト」を生成すると、その中身はどうなるのでしょうか。答えは、「元のリスト末尾から2つの要素を、元のリストにおける順番とは逆順に並べたリスト」となるのです。

plist = ['o', 'n', 't', ' ', 'p', 'a']
print([plist.pop(), plist.pop()])
# => ['a', 'p']

リストaの末尾に新たな要素を追加するメソッドの一つに、a.extend(x)メソッドがあります。a.extend(x)メソッドには、「引数xにリストを与えた場合、リストxを構成する一つ一つの要素が順にリストaの末尾に順番に追加される」という特徴2があります。

上記を踏まえると、「リストplistの末尾2要素の順序を入れ替える」ためには、以下の処理を実行すればよい…ということになります。

# plist => ['o', 'n', 't', ' ', 'p', 'a']
plist.extend([plist.pop(), plist.pop()])
# plist => ['o', 'n', 't', ' ', 'a', 'p']
4文字目と3文字目を入れ替える

Pythonのリストには、以下の特徴があります。

  • リストのpopメソッドを実行すると、その戻り値は「リストから除去された要素」となる
  • リストのinsert(i, x)メソッドを実行すると、当該リストに新たな要素を挿入することができる
    • 新たな要素を挿入する位置は、インデックスiの直前
    • 挿入される要素の内容はx

plistから生成される文字列の4文字目と3文字目を入れ替えるには、「plistの4番目の要素を除去し、その内容をplistの3番目の要素の直前に挿入する」という操作を行えばよいということになります。Pythonのリストにおいて、インデックス番号は0から始まるので、4番目の要素のインデックス番号は3、3番目の要素のインデックス番号2です。以上を踏まえると、現時点で必要な処理は以下となります。

plist.insert(2, plist.pop(3))

現時点のplistに対し、実際に上記の処理を実行すると、その結果は以下のようになります。

# plist => ['o', 'n', 't', ' ', 'a', 'p']
plist.insert(2, plist.pop(3))
# plist => ['o', 'n', ' ', 't', 'a', 'p']

以上にて、pliston tapという文字列を生成できる内容のリストとなりました。

文字列からリストを生成する

Pythonにおいて、文字列からリストを生成するためには、以下の関数を実行するのが最もシンプルと思われる方法です。

'*区切り文字列*'.join(x)

文字列のjoin(x)メソッドですね。「引数xで与えたリストの各要素を、*区切り文字列*で指定した文字列を区切り文字列として連結した文字列を返す」という動作をします。*区切り文字列*に空文字列を与えれば、引数xで与えたリストの内容は、区切り文字なしで連結されることになります。

# plist => ['o', 'n', ' ', 't', 'a', 'p']
new_phrase = ''.join(plist)
# new_phrase => 'on tap'

処理結果を標準出力に表示する

# plist => ['o', 'n', ' ', 't', 'a', 'p']
# new_phrase => 'on tap'
print(plist)
print(new_phrase)

内部的な動作は以下となります。

  1. 変数plistが指す内容を標準出力に表示する
  2. 変数new_phraseが指す内容を標準出力に表示する

plistおよびnew_phraseが上記の内容であれば、標準出力に表示される内容は以下となります。

['o', 'n', ' ', 't', 'a', 'p']
on tap

以上で、サンプルコード全体の説明が終わりました。


  1. より正確には「文字列リテラルへの参照」と言ったほうがいいのではないかと考えています。ただし、このあたりの違いは、本項目で説明する内容において本質的な事柄ではないため、簡単に「文字列リテラル」と言ってしまいます。以降同様の概念についても、「○○への参照」とはあえて言わないこととします。

  2. これに対し、a.append(x)メソッドの引数xにリストを与えた場合、「リストxそのものがリストaの末尾に追加される」という動作になります。