リスト — 名前1つにたくさんの値
名前1つの下に好きなだけの値を保持し、インデックスとスライスで中身にアクセスし、リストを変更するメソッドを学び、2つの名前が同じ1つのリストにくっついてしまう罠を見ます。
どうやって学びますか?
解決する問題
店で3つの商品の価格を保持する必要があります。ここまでに学んだことだけでは、やり方は1つしかありません:
price_1 = 15
price_2 = 60
price_3 = 850
print(price_1 + price_2 + price_3)925さて問題です:40個の商品になったら? 40個の変数です。そして40個の項が並んだ算術の行。それに店が新しい商品を売り始めると、コードに入って新しい変数を考え出さなければなりません。
問題はタイピングだけではなく、形です。Pythonにとって、price_1、price_2、price_3はまったく無関係な3つの名前です — nameとageと同じくらい無関係です。それらが同じものの3つの例だと分かるのはあなただけです。「同じもののたくさん」をコードで書く方法が、まだ私たちにはないのです。
リストがその方法です。名前は1つ、その下に好きなだけの値が順番に入っています。
この章を終える頃には
- リストを作り、インデックスとスライスで中身にアクセスできる
append、insert、remove、popでリストを変更できる- なぜテキストは変更できないのにリストは変更できるのか説明できる
b = aで2つの名前が1つのリストにくっついてしまうことを見抜いて避けられるIndexErrorとValueErrorを読んで、何が起きたか言える
前提条件: forループとrange.
リストを作る
角括弧の中に、値をカンマで区切って並べます:
prices = [15, 60, 850]
print(prices)
print(len(prices))[15, 60, 850]
3len()はここでもテキストのときと同じように動きます — 値が3つなので3です。
print()するとリスト全体が括弧ごと表示されます。これはユーザーに読ませるものではなく、あなたが理解するためのものです:[15, 60, 850]と表示されることで、これが925ではなくリストだと分かるのです。
中身にアクセスする
インデックスの働きは文字列のときとまったく同じです — 0から数え、負の数は後ろから:
prices = [15, 60, 850]
print(prices[0])
print(prices[-1])
print(prices[1:])15
850
[60, 850]3行すべてが第6章のルールそのままで、変更はありません。1つだけ気づいておきたい違いがあります:prices[0]が返すのは数ですが、prices[1:]が返すのはリストです。スライスは常に、切り出した元と同じ種類のものを返します。1個だけが返ることはありません。
そしてここが本当の違い:リストは変更できる
第6章では、テキストの中の文字を取り替えられないことを確かめました:
name = "rafi"
name[0] = "R"TypeError: 'str' object does not support item assignment同じ行がリストでは動きます:
prices = [15, 60, 850]
prices[0] = 20
print(prices)[20, 60, 850]新しいリストは作られていません — このリストが変わったのです。 これがリストの最も重要な性質で、この章の最後の罠はそこから直接生じています。
これを表す言葉:テキストはイミュータブル(変更不可能)、リストはミュータブル(変更可能)です。
追加と削除
stock = ["pen", "notebook"]
stock.append("bag")
stock.insert(1, "eraser")
print(stock)
stock.remove("pen")
print(stock)
last = stock.pop()
print(last)
print(stock)['pen', 'eraser', 'notebook', 'bag']
['eraser', 'notebook', 'bag']
bag
['eraser', 'notebook']4つのメソッド、4つの違う仕事です:
append(x)— 末尾に追加するinsert(i, x)— 位置iに入れ、そのあとの要素をすべて右にずらすremove(x)— その値を持つ最初の項目を探して取り出すpop()— 最後の項目を取り出すとともに、それを返す
後ろ2つの違いに、混乱の大半が集中しています:removeは何を取り出すかを知りたがり、popはどこから取り出すかを知りたがります。そしてpopは取り出したものを渡してくれます;removeは渡しません。
これらのメソッドはリストを返しません。 テキストではname.upper()の結果を保存する必要がありました — 新しいテキストを作って渡してくるからです。リストは逆です:appendはリスト自体を変更してNoneを返します。
これで、次のコードは古典的な間違いになります:
stock = ["pen", "notebook"]
stock = stock.append("bag")
print(stock)Noneリストは消えます。stock = stock.append("bag")ではなく、stock.append("bag")と書きましょう。
検索とループ
stock = ["pen", "notebook", "bag"]
print("pen" in stock)
print("chair" in stock)
for item in stock:
print(item.upper())True
False
PEN
NOTEBOOK
BAGinはテキストの中を検索しましたが、リストの中も検索します — 同じ単語が同じ仕事をしているのです。
そしてfor item in stockは値を1つずつ辿ります。rangeもインデックスの計算もないことに注目してください。第11章で単語の文字を1つずつ辿ったときとまったく同じです。
位置と値の両方が必要なときはenumerate:
stock = ["pen", "notebook"]
for position, item in enumerate(stock, start=1):
print(position, item)1 pen
2 notebookstart=1がなければカウントは0から始まります。人間に見せるリストは通常、1から始めたいものです。
誰もが引っかかる罠
第4章ではこれを確かめました:
a = 3
b = a
b = b + 1
print(a, b)3 4bは値を受け取って、aを見張っていませんでした。では同じことをリストでやってみましょう:
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)[1, 2, 3, 4]
[1, 2, 3, 4]bが変わると、aも変わりました。
その理由は前のルールと矛盾しません — むしろそのルールそのものです。b = aの行で、bが受け取ったのはaが持っていたものそのものでした — そしてaが持っていたのはリストの場所で、リストのコピーではありません。だから2つの名前は今、同じ1つのリストを指しています。b.append(4)はその1つのリストを変え、2つの名前はどちらも同じ変化を示します。
数ではこのことは決して起きません。b = b + 1は古い数を変えるのではなく、新しい数を作ってbに置き込むからです。数が安全なのは、まさに変更できないからです。リストは変更できるので、そうではありません。
別々のコピーが欲しいときは、自分で求めなければなりません:
a = [1, 2, 3]
b = a.copy()
b.append(4)
print(a)
print(b)[1, 2, 3]
[1, 2, 3, 4]注意すべきとき。 リストに2つ目の名前をつけるとき、そしてリストを関数に渡すとき(第19章)は、一度だけ自分に尋ねましょう:私はこの1つを変えたいのか、それともコピーが欲しいのか。この質問をしない人は、「ここでは何も変えていないのに」というバグを追いかけて何日も過ごすことになります。
完全な例
shopping.py:
# A shopping list, built up and then summarised
items = []
items.append("pen")
items.append("notebook")
items.append("bag")
items.insert(0, "eraser")
print("List :", items)
print("Count :", len(items))
print("First :", items[0])
print("Last :", items[-1])
print("Has bag :", "bag" in items)
items.remove("pen")
dropped = items.pop()
print()
print("After removing 'pen' and popping the last one:")
print("List :", items)
print("Dropped :", dropped)
print("Count :", len(items))
print()
print("Numbered:")
for position, item in enumerate(items, start=1):
print(f" {position}. {item}")List : ['eraser', 'pen', 'notebook', 'bag']
Count : 4
First : eraser
Last : bag
Has bag : True
After removing 'pen' and popping the last one:
List : ['eraser', 'notebook']
Dropped : bag
Count : 2
Numbered:
1. eraser
2. notebook3つ注目してください。
空のリストから始まります。 items = []の行は、第10章でループの外側に置いた累積変数がしていたのと同じ仕事をしています — かつてtotal = 0があった場所に、今度は[]があります。集め始める前に、集める場所が必要です。
insert(0, "eraser")はそれを先頭に入れました。 そのため、eraserは表示されたリストの1番目になります。追加した順序が、最終的な順序になるとは限りません。
pop()は取り出したものを返したので、droppedはbagを持っています。remove("pen")は何も返さなかったので、取り出されたpenは単に消えました。
うまくいかないとき
IndexError: list index out of range リストの値の数を超えたインデックスです。3個のリストなら有効なインデックスは0、1、2 — prices[3]は存在しません。最後の値にはprices[-1]が最も安全です。長さを知る必要がないからです。
ValueError: list.remove(x): x not in list 取り出すように頼んだ値がそこにはありません。まずif x in stock:で確認するか、第24章のtryを使いましょう。
AttributeError: 'NoneType' object has no attribute 'append' どこか前の行でstock = stock.append(...)と書いたため、stockが今Noneになっています。結果を保存しないでください — stock.append(...)と書けば、それだけで処理は完結です。
あるリストを変えたら、別のリストも変わった 2つの名前が1つのリストにくっついています。b = aではなくb = a.copy()と書きましょう。
ループの中でremoveしていると、要素がすり抜ける
numbers = [1, 2, 2, 3, 4]
for n in numbers:
if n % 2 == 0:
numbers.remove(n)
print(numbers)[1, 2, 3]2が1つ残りました。走査しているリストを短くすると、Pythonのカウントがリストの位置とずれ、ある値は飛び越えられます。修正はremoveすることではなく、残したいものを新しいリストに集めることです。
numbers = [1, 2, 2, 3, 4]
kept = []
for n in numbers:
if n % 2 != 0:
kept.append(n)
print(kept)[1, 3]変数をlistと名付けたせいで、list(...)が動かなくなった listはPython自身が使う名前の1つです。意味のある名前を使いましょう — items、prices、namesなどです。
ステップ 4 / 6 — 予想する
理解度をチェック
appendの結果がstockに保存されています。何と表示されますか?
stock = ["pen", "notebook"]
stock = stock.append("bag")
print(stock)- ANone
- B['pen', 'notebook', 'bag']
- C['bag']
- D`AttributeError`
bだけが変更されました。2行は何と表示されますか?
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)- A[1, 2, 3] [4]
- B[1, 2, 3] [1, 2, 3, 4]
- C[1, 2, 3, 4] [1, 2, 3]
- D[1, 2, 3, 4] [1, 2, 3, 4]
リストには3つの値があり、インデックス3を求めています。何が起きますか?
prices = [15, 60, 850]
print(prices[3])- A空のリスト
- B`IndexError` — 3つの値なら有効なインデックスは`0`、`1`、`2`
- CNone
- D850 — 最後の値
回答にはアカウントが必要です
ログインして答えを確認
問題は上にあります。頭の中で答えを考えることがいちばん大切です。ログインすると、正解・解説・3 段階のヒントを確認できます。
やってみよう
marks.pyというファイルを書いて、少なくとも6個の点数を含むリストを保存しましょう。
そして次を表示します:
- リスト全体と、そこに何個の点数があるか
- 最初と最後の点数 — 最後には
[-1]を使う - 合計と平均。ループで累積し、手で打ち込まない
enumerateを使った番号付きリスト。1から始める- 合格点(40以上)の新しいリストと、その個数
そして2つの実験をしてください。どちらも重要です:
- 別の点数を
appendで末尾に追加し、合計をもう一度表示する。合計を計算する行を変更する必要がありましたか? なかったなら、本物の計算が書けたということです。 backup = marksと書き、次にmarks.append(100)として、両方を表示する。backupの中には何が入っていますか? 今度は.copy()で同じことをして、違いを見てください。
その最後の実験が、この章で最も価値のある3分です。一度自分の目で見れば、後で午後を丸ごとそのことに費やすことはなくなります。