ゼロから始めるPython(5)

ゼロから始めるPythonの5回目になります。
[四回目はこちら](https://qiita.com/wind0411/items/01e096a22b29f2a919b6)

#おさらい

前回は主に制御部分と関数の定義についてまとめていました。
引き続き、分割の投稿となります。

#チュートリアル
[Python3.7 チュートリアル](https://docs.python.org/ja/3.7/tutorial/index.html)

#7. データ構造


##7.1. リスト型の詳細
[三回目](https://qiita.com/wind0411/items/1e406fab05e681054814#441-リスト型list)でもまとめており、重複する内容もあります。

###7.1.1. append

>list.append(x)

リストの`末尾`に要素を1つ追加します。

```python
>>> l = []
>>> l.append(1)
>>> l
[1]
```

また、次の式と等価になります。

>l[len(l):] = [x]

```python
>>> l = []
>>> l[len(l):] = [1]
>>> l
[1]
```

###7.1.2. extend

>list.extend(iterable)

イテレータブル(反復処理が可能)なオブジェクトの`全ての要素`を対象のリストに追加し、対象のリストを拡張します。
この時、**同じ値を持つ要素があっても重複して存在できること**に注意しましょう。

```python
>>> l = [1,2,3]
>>> l.extend([4,5,6])
>>> l
[1, 2, 3, 4, 5, 6]
>>> l.extend([1,3,5,7])
>>> l
[1, 2, 3, 4, 5, 6, 1, 3, 5, 7]
```

また、対象のリストを「**拡張**」しているので、オブジェクトID自体は変わらないことにも注意が必要です。

```python
>>> l = [1, 2, 3]
>>> id(l)
139860095859016
>>> l.extend([4, 5, 6])
>>> id(l)
139860095859016 # 拡張前とIDが同じ
```

###7.1.3. insert

>list.insert(i, x)

位置`i`の直前に要素`x`を挿入します。

```python
>>> l = [1, 2]
>>> l.insert(1, 3)
>>> l
[1, 3, 2]
```

また、次の動作は等価です。

> list.insert(len(i), x) = list.append(x)

```python
>>> l = [1, 3, 2]
>>> l.insert(len(l),4)
>>> l
[1, 3, 2, 4]
```

###7.1.4. remove

>list.remove(x)

要素`x`をリストから削除します。
ただし、要素がリストに存在しない場合は**ValueError**が発生します。

```python
>>> l = [1, 2, 3]
>>> l.remove(1)
>>> l.remove(4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
```

###7.1.5. pop

>list.pop([i])

位置`i`に指定された要素をリストから削除してその要素を返します。
位置を指定しない場合は**末尾の要素**をリストから削除して返します。
※**引数の「角括弧」はリスト型を示すものではありません。ここではオプションを示すものなのでご注意下さい。**

```python
>>> l = [1, 2, 3]
>>> l.pop(1)
2
>>> l
[1, 3]
```

###7.1.6. clear

>list.clear()

言わずもがな、リストのすべての要素を削除します。

```python
>>> l = [1, 2, 3]
>>> l.clear()
>>> l
[]
```

また、次の式と等価です。

>del list[:]

```python
>>> l = [1, 2, 3]
>>> del l[:]
>>> l
[]
```

###7.1.7. index

>list.index(x[,start[,end]])

リスト中の要素`x`の要素位置をゼロから始まる添字で返します。
同じ要素がある場合は最初にヒットした要素位置を返します。
またこの時、xの後にスライスを用いてリストの探索範囲を指定することができます。
(**ただし、返却される添字はリスト全体の先頭からの位置**）

```python
>>> l = list('helloWorld')
>>> l.index('l')
2
>>> l.index('W',2,6) #要素位置[2～5]が探索範囲
5
>>> l.index('W',2,5) # 探索範囲に存在しない
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: 'W' is not in list
```

###7.1.8. count

> list.count(x)

リスト内での要素`x`の件数を返します。

```python
>> values = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
>>> values.count(1)
1
>>> values.count(4)
4
```

###7.1.9. sort

■ [参考サイト(ソートHOW TO)](https://docs.python.org/ja/3.7/howto/sorting.html#sortinghowto)

> list.sort(key(=None), reverse(=False))

リストの項目を`インプレース演算`（対象のリストがミュータブル(状態の変更が可能)な場合は新しいオブジェクトを生成せず元のオブジェクトを置き換える）によってソートします。

(**結構ここの仕様は落とし穴になりそう**)
以下、検証してみました。

```python
>>> print([1,5,3,4,2].sort()) # sortを実行した結果、ソートされたリストを返すわけではない
None # だから戻り値も当然ない
>>> l = [1,5,3,4,2]
>>> l.sort() # 一度ソートしてリストデータを変更し
>>> l #出力するとソートされた結果を返す
[1, 2, 3, 4, 5]
```

また、公式チュートリアルには次のような記述もあります。

>sorted(iterable, *, key=None, reverse=False)
>iterable の要素を並べ替えた新たなリストを返します

こちらは、リストをソートした上で新しいオブジェクトを返します。

```python
>>> l = [1,5,3,4,2]
>>> id(l)
140087708991112
>>> id(sorted(l))
140087649958536 # 異なるID
>>> print(sorted(l))
[1, 2, 3, 4, 5] # sortedによって得られたオブジェクト
>>> l
[1, 5, 3, 4, 2] # 元のリストオブジェクト
```
###7.1.10. reverse

>list.reverse()

リストの要素を`インプレース演算`で逆順にします。
sort()と同様、注意しましょう。

```python
>>> l = [1,5,3,4,2]
>>> id(l)
140087649978824 # 元のリストオブジェクト
>>> l.sort()
>>> l.reverse()
>>> id(l)
140087649978824 # IDが一致している
```

###7.1.11. copy

■ [参考サイト(Pythonの浅いコピー、深いコピー)](http://hima-jew.chobi.net/Blog/wordpress/?p=94)

>list.copy()

リストの`浅い`(シャロー)コピーを返却します。

> list_to = list[:]

と等価です。

Pythonに限った話ではありませんが、浅いコピーとはコピー元とコピー先が同じメモリ上のデータを参照した状態のコピーを指します。

下記で検証してみます。
まずは浅いコピーの想定できる動作について。

```python
>>> list_a = [1, 2, 3]
>>> list_b = [4, 5, 6]
>>> list_c = [7, 8, 9]
>>> merge_list = [list_a, list_b, list_c]
>>> 
>>> list_ref = merge_list # merge_listへの参照
>>> merge_list[0] = [10, 11, 12] # merge_listを変更すると
>>> merge_list
[[10, 11, 12], [4, 5, 6], [7, 8, 9]] # 当然変更されており
>>> list_ref
[[10, 11, 12], [4, 5, 6], [7, 8, 9]] # 参照しているだけなので変わっている
>>>
>>> copy_list = merge_list.copy() # 次に浅いコピー
>>> merge_list[0] = [1, 2, 3] # 1つ目の要素を変更すると
>>> merge_list
[[1, 2, 3], [4, 5, 6], [7, 8, 9]] # コピー元は変更されているが
>>> copy_list
[[10, 11, 12], [4, 5, 6], [7, 8, 9]] # コピー先は変更されない
```

ここまではなんとなく想像できますね。
ですが、次は我々が普段からイメージする「コピー」とは少し異なってくると思います。

![浅いコピー.png](https://qiita-image-store.s3.amazonaws.com/0/174260/48dcd910-12c1-4469-2c6e-137bc05374fa.png)

↓

![浅いコピー2.png](https://qiita-image-store.s3.amazonaws.com/0/174260/abefe578-b955-c072-00e8-8e38f9795298.png)



```python
>>> list_a = [1, 2, 3]
>>> list_b = [4, 5, 6]
>>> list_c = [7, 8, 9]
>>> merge_list = [list_a, list_b, list_c]
>>> copy_list = merge_list.copy() # 浅いコピー
>>> list_a = [10,2,3] # 参照先の1つを変更すると
>>> copy_list
[[10, 2, 3], [4, 5, 6], [7, 8, 9]] # 浅いコピーの方も変更される
>>> merge_list
[[10, 2, 3], [4, 5, 6], [7, 8, 9]] # コピー元も参照先の値が変わるので変更される
```

あくまで、元の`merge_list`はlist_a,list_b,list_cの参照をもつリストになります。

```python:続き
>>> id(list_a)
139718526679752 # IDは同じ
>>> id(merge_list[0])
139718526564872 # IDは同じ
```





今回、ここでは触れませんが「深い(deep)コピー」というのもあります。
こちらは我々の馴染みのある「コピー」という概念と等しいでしょう。
コピー元のオブジェクトを「複製」してコピーを生成します。

###7.1.12. スタック、キュー

これまでで説明した機能を使用することで、リストをスタックやキューとして扱うことが出来ます。

>スタック：後入れ先出し方式(last-in first-out)
>キュー：先入れ先出し方式(first-in first-out)

```python:stack
>>> stack = []
>>> stack.append(1) # データ格納
>>> stack.append(2) # データ格納
>>> stack.append(3) # データ格納
>>> stack
[1, 2, 3] 
>>> stack.pop() # 最後のデータ取り出し ([1, 2])
3
>>> stack.pop() # 最後のデータ取り出し([1])
2
# 結果 [1]
```

```python:queue(dequeを使用)
>>> from collections import deque # importは後々説明
>>> queue = deque([1, 2, 3])
>>> queue.popleft() # 先頭データ取り出し
1
>>> queue.popleft() # 戦闘データ取り出し
2
# 結果 [3](厳密にはdeque([3])
```

#8. 内包表記

チュートリアルにはさらっと書いてありましたが、詳しく取り上げたいので今回は概要だけ。

内包表記では、リストやタプルなどの`イテラブル`(反復可能)なオブジェクト内の要素を1つずつ変数名に格納し、**指定した式で評価した**結果の要素リストを返却します。

> [式 for 要素名 in イテラブルオブジェクト]

例として、
「0から9までの値を全て倍にしたリストを取得する」
とします。

for文で行うと

```python
>>>> results = []
>>> for d in range(10) :
...     results.append(d * 2)
...
>>> results
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
```

内包表記で行うと

```python
>>> results = [d * 2 for d in range(10)]
>>> results
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
```

このように、それぞれの要素に対して式を適用する場合などに便利です。
また、細かい説明は別途記事で取り上げたいと思います。

#9. del文

指定したインデックス`i`のリストの要素、または変数全体を削除します。

>del list[i]

この時、pop()のように**削除する対象の要素を返さない**ことに注意しましょう。

```python
>>> value = [1,2,3,4,5]
>>> value.pop(1)
2
>>> value = [1,2,3,4,5]
>>> del value[1]
>>> del value
>>> value
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'value' is not defined
```

#10. タプル、シーケンス

`シーケンス`とは要素の順番を保持するオブジェクトで、`list`,`tuple`などがあります。
また、`str`も「**テキストシーケンス**」と呼ばれていることを覚えておきましょう。
([三回目(4.1.3.)](https://qiita.com/wind0411/items/1e406fab05e681054814#413-文字列のインデックスによる取得)に検証しましたが、もう一度)

```python
word = 'Python'
word[0] # P
word[5] # n
word[-1] # n
word[-6] # P
```

そして、タプルに関しても[三回目(4.4.3)](https://qiita.com/wind0411/items/1e406fab05e681054814#443-タプル型)で説明しています。

> (要素, 要素, ...)
> ※必ずしも括弧で囲むことによって定義するわけではありません

タプルは要素を変更できない、と説明しましたね。
ただし、タプル内のミュータブルなオブジェクトの場合はその限りではありません。

```python
>>> t = ({'apple':'りんご','grape':'ぶどう','peach':'もも'},{'one':'イチ','two':'ニ','three':'サン'})
>>> t[0]['banana'] = 'ばなな' # タプルに格納されたミュータブルオブジェクトの中では変更可能
>>> t
({'apple': 'りんご', 'grape': 'ぶどう', 'peach': 'もも', 'banana': 'ばなな'}, {'one': 'イチ', 'two': 'ニ', 'three': 'サン'})
>>> t[2] = dict() # タプルそのものの変更は不可
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
```

またtupleはイミュータブルオブジェクトで、複数の型の要素から成ることがあるため、
アンパックやインデックスでアクセスすることが多いです。

>アンパック:　リストやタプルの要素を展開し、複数の変数に代入すること

```python
>>> data = (1, 2, 3) # tupleの要素を
>>> a, b, c = data # 変数にアンパック
>>> a
1
>>> b
2
>>> c
3
```

#11. 辞書型

辞書型はキーと値のペアの集合です。
この時、キーは辞書内でユニークである必要があります。
辞書型は主に「**ある値に何らかのキーを付与して記憶、キーを指定して値を取得する**」ことを目的に使用されます。

> {key : value, key : value, ....}

```python
>>> d = {'one':'いち','two':'に','three':'さん'}
```

また、`dict`コンストラクタを用いることで、キーと値のペアのタプルから成るリストから辞書を生成できます。

> dict()

```python
>>> d = dict([('one','いち'),('two','に'),('three','さん')])
```

キーが単純な文字列の場合は、`キーワード引数`でも定義できます。

```python
>>> d = dict(one='いち',two='に',three='さん')
```

#12.ループテクニック

##12.1. items()

辞書に対しては、`items()`メソッドを用いることでキーと値のペアを同時に取得できます。

```python
>>> d = dict([('one','いち'),('two','に'),('three','さん')])
>>> for key, value in d.items():
...     print(key, value)
...
one いち
two に
three さん
```

##12.2. enumerate()

シーケンス型に対しては、`enumerate()`メソッドを用いることで要素のインデックスと要素を同時に取得できます。

```python
>>> l = ['apple','grape','peach']
>>> for idx, value in enumerate(l):
...     print(idx, value)
...
0 apple
1 grape
2 peach
```

##12.3. zip()

2つ以上のシーケンス型に対して同時にループする場合、`zip()`メソッドを用いて各要素をペアに出来ます。

```python
>>> english = ['one','two','three']
>>> japanese = ['いち','に','さん']
>>> for eng, jpn in zip(english, japanese):
...     print(eng, jpn)
...
one いち
two に
three さん
```

##12.4. reversed()

シーケンスを逆方向にループする場合、`reversed()`メソッドを用います。

```python
>>> names = ['たろう','じろう','さぶろう']
>>> for name in reversed(names):
...     print(name)
...
さぶろう
じろう
たろう
```

#まとめ

ようやくチュートリアルの折り返しくらいまで進みました。
次はモジュールについてまとめます。