このブログを検索

第2章 ネイティブデータ型

2.1. 没頭しよう

この章ではデータ型についてです。前章での、はじめてのPythonプログラムのことは少し忘れて、データ型の話をしましょう。Pythonではすべての値にデータ型がありますが、宣言する必要はありません。では、どうやっているのでしょうか?Pythonは、変数にはじめて値が代入されたときにデータ型を把握し、内部で記憶しているのです。

Pythonには多くのネイティブデータ型があります。重要なものを挙げると…
  1. ブール演算子はTrueかFalse
  2. は整数型(1, 2)、浮動小数点型(1.1, 1.2)、分数(1/2, 2/3)、複素数も可
  3. 文字列はUnicode文字列(例:html文書)
  4. バイトはバイト配列(例:JPEG画像ファイル)
  5. リストは値の配列。順序あり
  6. タプルは値の配列。順序あり。変更不可。
  7. セットは値が入った袋。順序なし
  8. 辞書は(キー, 値)のペアが入った袋。順序なし

もちろん、他にも多くの型があります。Pythonではすべてがオブジェクトなので、型の中にはmodule、function、class、method、fileというものがありますし、compiled fileという型もあります。

前章で見たように、モジュールには名前があり、関数にはドキュメンテーション文字列があります。classについてはクラスとイテレータの章で、fileについてはファイルの章で学ぶことになります。

文字列とバイトは非常に重要、かつ非常に複雑なので、個別の章で説明します。他の型を先に見ていきましょう。

2.2. ブール演算子

ブール演算子は、真・偽のどちらかです。Pythonでは、True、Falseという名前の2つの定数があり、ブール値に直接代入して使うことができます。条件記述を使ってブール値を判定することも可能です。if文では、ブール値を判定する表現が書かれることを想定しています。この部分は、ブール値コンテクストと呼ばれます。

ブール値コンテクストでは、Pythonが真偽を判定しますが、どのような表現をしてもかまいません。データ型が違う場合、ブール値コンテクストで値の真偽を決めるルールも変わります。(この章で具体的な例が出てきますので、理解が深まるでしょう。)

例として、第1章のhumansize.pyから、このスニペットを見てみましょう。
if size < 0:
    raise ValueError('number must be non-negative')

sizeと0は整数、<は比較演算子です。size<0の結果は、必ずブール演算子になります。Pythonの対話型シェルで試してみるとよいでしょう。
>>> size = 1
>>> size < 0
False
>>> size = 0
>>> size < 0
False
>>> size = -1
>>> size < 0
True

Python2からの名残で、ブール演算子は数字として扱うこともできます。Trueが1、Falseが0です。
>>> True + True
2
>>> True - False
1
>>> True * False
0
>>> True / False
Traceback (most recent call last):
  File "", line 1, in 
ZeroDivisionError: int division or modulo by zero

おっと!これはやってはいけません。見なかったことにしてください!

2.3. 数字

数字は素晴らしいものです。無数にある候補から選ぶことができるからです。Pythonでは整数型、浮動小数点型、どちらにも対応しています。宣言して数字の型を区別しなくても、小数点が存在するかどうかによってPythonが判断してくれます。
>>> type(1)                 # ①

>>> isinstance(1, int)      # ②
True
>>> 1 + 1                   # ③
2
>>> 1 + 1.0                 # ④
2.0
>>> type(2.0)

①type()関数を使って、値や変数の型を調べることができます。予想通り、1は整数(int)です。

②同様に、isistance()関数を使って、値や変数が特定の型であるかを調べることができます。

③int にintを加えると結果はintになります。

④int にfloatを加えると結果はfloatになります。加算するときにPythonがintをfloatに変換しています。そのため、結果はfloatになります。

2.3.1. 整数と浮動小数点数の変換


上で見たように、加算などの演算子は必要に応じて整数を浮動小数点数に変換します。自分で変換することもできます。
>>> float(2)                # ①
2.0
>>> int(2.0)                # ②
2
>>> int(2.5)                # ③
2
>>> int(-2.5)               # ④
-2
>>> 1.12345678901234567890  # ⑤
1.1234567890123457
>>> type(1000000000000000)  # ⑥


①float()関数を呼び出して、明示的にintをfloatに変換できます。

②当然、int()関数を呼び出して、floatをintに変換することができます。

③int()関数では、少数部分は四捨五入ではなく、切り捨てられます。

④int()関数では、負の数は0に向かって切り捨てられます。「切り捨て式」であって、floor関数ではありません。

⑤小数点以下は15桁までが正確な値となります。(四捨五入して小数点以下16桁になっている)

⑥整数はどれだけ大きくてもかまいません。

👉 Python2ではintとlongの型を区別していました。intデータ型はsys.maxintで制限されていて、プラットフォーム依存性がありました(232-1が多い)。Python3では整数型は1つで、Python2のlong型と同じように振る舞います。詳細はPEP237に書かれています。

2.3.2. 数値演算

数字を使った計算は、どんなものでもできます。
>>> 11 / 2      # ①
5.5
>>> 11 // 2     # ②
5
>>> −11 // 2    # ③
−6
>>> 11.0 // 2   # ④
5.0
>>> 11 ** 2     # ⑤
121
>>> 11 % 2      # ⑥
1

①演算子 / は浮動小数点型の割り算です。そのため、分子、分母がどちらも整数でも、浮動小数点数を返します。

②演算子 // は整数での割り算ですが、少し変則的なので注意が必要です。商が正の数であれば、小数点第1位の切り捨て(四捨五入ではなく)だと考えてください。

③演算子 // を使って負の数を整数で割った場合、結果は最も近い整数に「切り上げ」られます。数学的には、-6は-5より小さいので、「切り下げ」ですが、-5に切り捨てられると考えていると失敗します。

④演算子 // は必ずしも整数を返すわけではありません。分子か分母が浮動小数点数のときは、最も近い整数を返しますが、値としては浮動小数点数になります。

⑤演算子 ** は2乗を意味します。11**2は121です。

⑥演算子 % は整数で割った余りです。11÷2は5余り1です。したがって、この場合は1が返ります。

👉 Python2では、演算子 / は常に整数の割り算で、特別なコードを加えることで浮動小数点数でも割り算が可能になります。Python3では、 / は常に浮動小数点数の割り算になります。詳細はPEP238に書かれています。

2.3.3. 分数


Pythonの計算は、整数や浮動小数点数だけではありません。高校で習って、すっかり忘れてしまっている楽しい数学にも対応しています。
>>> import fractions              # ①
>>> x = fractions.Fraction(1, 3)  # ②
>>> x
Fraction(1, 3)
>>> x * 2                         # ③
Fraction(2, 3)
>>> fractions.Fraction(6, 4)      # ④
Fraction(3, 2)
>>> fractions.Fraction(0, 0)      # ⑤
Traceback (most recent call last):
  File "", line 1, in 
  File "fractions.py", line 96, in __new__
    raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
ZeroDivisionError: Fraction(0, 0)

①分数を使うためにfractionsモジュールをインポートします。

②分数を定義します。Fractionオブジェクトを作って、分子分母を与えます。

③分数に対して、通常の数学演算ができます。演算は新しい分数のオブジェクトを返します。2*(1/3)=(2/3)です。

④分数オブジェクトは自動的に約分されます。つまり、(6/4)=(3/2)です。

⑤分母が0にならないように、Pythonがエラーを出します。

2.3.4. 三角法


Pythonでは、基本的な三角法を使うことができます。
>>> import math
>>> math.pi                # ①
3.1415926535897931
>>> math.sin(math.pi / 2)  # ②
1.0
>>> math.tan(math.pi / 4)  # ③
0.99999999999999989

①mathモジュールには定数πがあります。円周と直径の比です。

②mathモジュールには基本的な三角関数であるsin()、cos()、 tan()や、asin()といった逆三角関数も備わっています。

③Pythonの精度は無限ではないことに注意しましょう。tan(π/4)は0.99999999999999989が返っていますが、正しくは1.0です。

2.3.5. ブール値コンテクストの中の数値


if文のようなブール値コンテクストで、数を使うことができます。ゼロ値は偽で、ゼロでない数値は真です。
>>> def is_it_true(anything):             # ①
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(1)                         # ②
yes, it's true
>>> is_it_true(-1)
yes, it's true
>>> is_it_true(0)
no, it's false
>>> is_it_true(0.1)                       # ③
yes, it's true
>>> is_it_true(0.0)
no, it's false
>>> import fractions
>>> is_it_true(fractions.Fraction(1, 2))  # ④
yes, it's true
>>> is_it_true(fractions.Fraction(0, 1))
no, it's false
①Pythonの対話型シェルの中でも、自分で関数を定義できると知っていましたか?各行の末尾で改行して、関数が終わるときには空白行を入れます。

②ブール値コンテクストでは、ゼロでない整数はTrue、ゼロはFalseです。

③ゼロでない浮動小数点数はTrueです。ただし、0.0にはFalseを返します。ここは注意が必要です! 非常に小さな丸め込みエラーがあった場合(前節で見たように、あり得ない話ではありません)、Pythonは0ではなく、0.0000000000001を使って判定するので、Trueが返ることになります。

④分数もブール値コンテクストで使うことができます。Fraction(0, n)はnがどんな値であっても0なのでFalseです。他のすべての分数はTrueが返ります。

2.4. リスト

リストはPythonで馬車馬のようにはたらく強力なデータ型です。リストと言えば、こんな想像をするかもしれません。「先にサイズを宣言する必要があって、同じタイプのものだけが入る…etc」。そんなことはありません。リストはもっと便利です。
👉 PythonのリストはPerl5のarrayと似ています。Perl5ではarrayに入る変数名は必ず@記号で始まります。一方、Pythonでは、変数名は何でもよく、データ型はPython内部で記憶しています。
👉 PythonのリストはJavaのarrayよりずっとよいものです(もし一生Javaを使いたければそれでもかまいませんが)。最も似ているのはJavaのArrayListクラスで、任意のオブジェクトを保持できて、新しいアイテムを加えて動的に拡大できるというものです。

2.4.1. リスト生成

コンマで区切った値を角括弧[ ]で囲むだけで、簡単にリストが生成できます。
>>> a_list = ['a', 'b', 'mpilgrim', 'z', 'example']  # ①
>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example']
>>> a_list[0]                                        # ②
'a'
>>> a_list[4]                                        # ③
'example'
>>> a_list[-1]                                       # ④
'example'
>>> a_list[-3]                                       # ⑤
'mpilgrim'
①まず、5つの要素をもつリストを定義します。出力してみると、定義したときの順番が保持されていることがわかります。これは偶然ではありません。リストは、順番のある要素の集まりなのです。

②リストは0始まりの配列のように使うこともできます。空でないリストの最初の要素は常にa_list[0]です。

③リストのインデックスは必ず0始まりなので、5つの要素があるリストの最後の要素はa_list[4]です。

④負のインデックスは、リストを最後から逆順に読みます。空でないリストの最後の要素は必ずa_list[-1]です。

⑤もしも負のインデックスが紛らわしければ、このように考えればよいでしょう。a_list[-n] == a_list[len(a_list) - n] ですから、a_list[-3] == a_list[5-3] == a_list[2] となります。

2.4.2. リストをスライスする


リストを定義したあとに、好きな部分を新しいリストとして取り出すことができます。この操作をリストのスライスといいます。
>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example']
>>> a_list[1:3]            # ①
['b', 'mpilgrim']
>>> a_list[1:-1]           # ②
['b', 'mpilgrim', 'z']
>>> a_list[0:3]            # ③
['a', 'b', 'mpilgrim']
>>> a_list[:3]             # ④
['a', 'b', 'mpilgrim']
>>> a_list[3:]             # ⑤
['z', 'example']
>>> a_list[:]              # ⑥
['a', 'b', 'mpilgrim', 'z', 'example']

①2つのインデックスを指定して、リストの一部分(スライス)を取り出します。返り値は新しいリストで、2つのインデックスの間のすべての要素を含みます(1番目のインデックスを含み、2番目のインデックスは含みません。a_list[1:3]の場合は、a_list[1]を含み、a_list[3]を含みません)。

②スライスのインデックスは、片方が負の値、あるいは両方負でもかまいません。こんな風に考えると理解しやすいかもしれません。リストを左から右へと読んで、1番目のインデックスは欲しい部分の最初の要素を設定して、2番目のインデックスは不要になる最初の要素を設定します。その間の値がすべて返ります。

③リストのインデックスは0から始まるので、a_list[0:3]は最初の3つの要素を返します。a_list[0]から始まり、a_list[3]は含みません。

④左側のスライス インデックスが0である場合、0を省略しても意味が成り立ちます。a_list[:3]はa_list[0:3]と同じです。0から開始する意味を含んでいるからです。

⑤同じように、右側のスライス インデックスがリストの長さと同じであれば、省略できます。今の場合は、リストが5つの要素を持っているので、a_list[3:]はa_list[3:5]と同じです。この5要素のリストでは、a_list[:3]は最初の3つの要素を返し、a_list[3:]は最後の2つのリストを返します。一般化すると、a_list[:n]は常に最初のn個の要素を表します。長さにかかわらず、a_list[n:]は残りの要素を表します。

⑥スライス インデックスが両方とも省略された場合は、すべての要素が含まれます。しかし、これは元のa_list変数そのものではなく、同じ要素を持った、別の新しいリストなのです。このように、a_list[:]を使うとリストの同一コピーを簡単に作ることができます。

2.4.3. リストに要素を加える


リストに要素を加える方法は4つあります。
>>> a_list = ['a']
>>> a_list = a_list + [2.0, 3]    # ①
>>> a_list                        # ②
['a', 2.0, 3]
>>> a_list.append(True)           # ③
>>> a_list
['a', 2.0, 3, True]
>>> a_list.extend(['four', 'Ω'])  # ④
>>> a_list
['a', 2.0, 3, True, 'four', 'Ω']
>>> a_list.insert(0, 'Ω')         # ⑤
>>> a_list
['Ω', 'a', 2.0, 3, True, 'four', 'Ω']

①+演算子でリストを結合して新しいリストを作ります。リストの要素数はいくつでもよく、サイズに(消費メモリ以外の)制限はありません。メモリを気にするなら、リスト結合の際には2つ目のリストがメモリ内に作られていることを意識しましょう。この場合、新しいリストが存在する変数a_listに直ちに代入されます。つまり、この行のコードは、結合して代入する、という2ステップのプロセスなのです。大きなサイズのリストを扱う場合は、(一時的に)大量のメモリを消費する可能性があります。

②リストにはどんな型の要素でも入れることができますが、リスト中の要素が同じ型である必要はありません。このリストは、文字列、浮動小数点数、整数を含んでいます。

③リストはクラスとして実装できます。リストを"生成"すると、クラスを生成することになります。つまり、リストにはリスト内で使えるメソッドがあるということです。append()メソッドは、1つの要素をリストの末尾に付け加えます。(このときは、4種類のデータ型がリストに入っています! )

④extend()メソッドは1つのリストを引数として受け取り、受け取ったリストの各要素を元のリストに追加します。

⑤insert()メソッドは、1つの要素をリストに挿入します。最初の引数は、リストのどこに挿入するかを示すインデックスで、挿入によって元の要素のインデックスは、ずれていきます。リストの中にある要素は、すべて異なっている必要はありません。例えば、今の場合は2つの”Ω”がリストの中にあります。1つ目はa_list[0]で、2つ目はa_list[6]です。

👉 a_list.insert(0, value)はPerlにおけるunshift()関数と同じです。要素をリストの最初に追加して、他の要素はインデックスが1つずれます。

append()とextend()の違いをもう少し詳しく見ていきましょう。
>>> a_list = ['a', 'b', 'c']
>>> a_list.extend(['d', 'e', 'f'])  # ①
>>> a_list
['a', 'b', 'c', 'd', 'e', 'f']
>>> len(a_list)                     # ②
6
>>> a_list[-1]
'f'
>>> a_list.append(['g', 'h', 'i'])  # ③
>>> a_list
['a', 'b', 'c', 'd', 'e', 'f', ['g', 'h', 'i']]
>>> len(a_list)                     # ④
7
>>> a_list[-1]
['g', 'h', 'i']

①extend()は引数として受け取るのはリストだけで、受け取ったリストの各要素をa_listに追加します。

②元のリストの要素数は3で、新たに加えたリストの要素数は3なので、要素数は6になります。

③一方、append()メソッドでは、型は何でもよいので引数を1つ受け取ります。ここでは、append()メソッドに3要素のリストを渡しています。

④要素数6のリストに、3要素のリストを加えたら・・・要素数は7になりました。何故7なのでしょうか?append()で加えた最後の要素はリストでした。リストには、どんな型のデータでも、つまり別のリストを入れることができるからです。望むと望まざるとにかかわらず。求めたものが得られるのです。

2.4.4. リスト内で値を探す


>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new']
>>> a_list.count('new')       # ①
2
>>> 'new' in a_list           # ②
True
>>> 'c' in a_list
False
>>> a_list.index('mpilgrim')  # ③
3
>>> a_list.index('new')       # ④
2
>>> a_list.index('c')         # ⑤
Traceback (innermost last):
  File "", line 1, in ?
ValueError: list.index(x): x not in list

①予想通り、count()メソッドはある値がリスト内に現れる回数を返します。

②ある値がリスト内にあるかどうかを調べたいだけであれば、in演算子を使うとcount()メソッドよりも少し速くなります。ただし、in演算子は常にTrueかFalseを返すだけなので、リストに何回現れているかはわかりません。

③in演算子、count()メソッドでは、目的の値がリストの場所はわかりません。どこにあるのかを知りたい場合は、index()メソッドを呼び出します。デフォルトではリスト全体を探しますが、追加で第2の引数(0始まり)を設定して、開始インデックスとします。さらに第3の引数(0始まり)を設定して、探索終了のインデックスとすることもできます。

④index()メソッドは、その値がリストの中で最初に現れる場所を探します。例では"new"はa_list[2]とa_list[4]の2つありますが、index()メソッドでは最初に現れるインデックスだけを返します。

⑤予想しなかったかもしれませんが、値がリストに見つからない場合、index()メソッドは例外を出します。

待ってください、本当でしょうか?そうです、index()メソッドは、値がリストに見つからない場合は例外を出します。他のプログラミング言語であれば例外値(-1など)を返しますから、Pythonとは大きく違います。このことは最初は気になるでしょうが、いずれ感謝することになるでしょう。これは、プログラムが原因が解った状態でクラッシュするということです。あとになって原因不明でひっそりと失敗するのではありません。覚えていますか、-1はリストのインデックスとしてあり得るということを。もし、index()メソッドが-1を返したならば、楽しくないデバッグセッションに陥るかもしれません!

2.4.5. リストから要素を削除する


リストは要素数に合わせて自動的に拡大、縮小します。拡大に関しては、これまで見てきた通りです。

リストから要素を削除する方法も、いくつかあります。
>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new']
>>> a_list[1]
'b'
>>> del a_list[1]         # ①
>>> a_list
['a', 'new', 'mpilgrim', 'new']
>>> a_list[1]             # ②
'new'

①del文を使って、指定した要素をリストから削除できます。

②インデックスの要素を消したあとにインデックス1にアクセスしても、エラーにはなりません。要素が削除されてできた"隙間"を埋めるように、残りの全要素が移動します。

場所のインデックスがわからないときは?問題ありません。値を指定して要素を消すこともできます。
>>> a_list.remove('new')  # ①
>>> a_list
['a', 'mpilgrim', 'new']
>>> a_list.remove('new')  # ②
>>> a_list
['a', 'mpilgrim']
>>> a_list.remove('new')
Traceback (most recent call last):
  File "", line 1, in 
ValueError: list.remove(x): x not in list

①remove()メソッドを使って、リストから要素を削除することができます。remove()メソッドは、受け取った値をリストの中で探して、最初に現れるものだけを削除します。ここでも、削除された要素のあとの部分は、隙間を埋めるのでインデックスが下がります。

②好きなようにremove()メソッドを呼び出すことができますが、削除したい値がリストに無ければ、例外が出ます。

2.4.6. リストから要素を削除する:ボーナスラウンド


この他にも、pop()という面白いメソッドがあります。このメソッドもリストから要素を削除しますが、ひねりが利いています。
>>> a_list = ['a', 'b', 'new', 'mpilgrim']
>>> a_list.pop()   # ①
'mpilgrim'
>>> a_list
['a', 'b', 'new']
>>> a_list.pop(1)  # ②
'b'
>>> a_list
['a', 'new']
>>> a_list.pop()
'new'
>>> a_list.pop()
'a'
>>> a_list.pop()   # ③
Traceback (most recent call last):
  File "", line 1, in 
IndexError: pop from empty list

①引数なしでpop()を呼び出すと、リストの最後の要素を削除して、返します。

②リストから任意の要素を取り出すこともできます。場所インデックスをpop()メソッドに渡すと、要素を削除して残りの要素をずらして"隙間"を埋めて、値を返します。

③pop()を空のリストに対して呼び出すと、例外が出ます。

👉 pop()リストメソッドを引数なしで呼び出すのは、Perlでのpop()関数と同じです。リストの最後の要素を削除して返す、というものです。Perlには他にshift()という関数があり、これはリストの最初の要素を削除して値を返すもので、Pythonにおけるa_list.pop(0)と等価です。

2.4.7. ブール値コンテクストのリスト


if文のようなブール値コンテクストで、リストを使うことができます。
>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true([])             # ①
no, it's false
>>> is_it_true(['a'])          # ②
yes, it's true
>>> is_it_true([False])        # ③
yes, it's true

①ブール値コンテクストでは、空のリストはFalseです。

②少なくとも1つの要素があるリストはTrueです。

③少なくとも1つの要素があるリストはTrueで、値は関係ありません。

2.5. タプル

タプルはイミュータブルのリストです。生成されたあとは、どうやっても変えることはできません。
>>> a_tuple = ("a", "b", "mpilgrim", "z", "example")  # ①
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
>>> a_tuple[0]                                        # ②
'a'
>>> a_tuple[-1]                                       # ③
'example'
>>> a_tuple[1:3]                                      # ④
('b', 'mpilgrim')

①タプルはリストと同様に定義されますが、要素は四角括弧ではなく、丸括弧に入っています。

②タプルの要素は、リストと同様に順序が決まっていて、インデックスはゼロ始まりです。空でないタプルの最初の要素は常にa_taple[0]です。

③負のインデックスはタプルの最後からカウントします。これもリストと同様です。

④スライスもリストと同様です。タプルをスライスすると新しいタプルができます。

リストとタプルの大きな違いは、タプルは変更できない、ということです。専門用語では、タプルはイミュータブルである、といいます。タプルを変更するために使える方法は一切ありません。リストにはappend()、extend()、insert()、remove()、pop()といったメソッドがありますが、タプルにはありません。スライスは、新しいタプルを作ることになるので、可能です。特定の値が入っているかを調べることも、タプルを変えないので可能です。他にもいくつかあります。
# continued from the previous example
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
>>> a_tuple.append("new")               # ①
Traceback (innermost last):
  File "", line 1, in ?
AttributeError: 'tuple' object has no attribute 'append'
>>> a_tuple.remove("z")                 # ②
Traceback (innermost last):
  File "", line 1, in ?
AttributeError: 'tuple' object has no attribute 'remove'
>>> a_tuple.index("example")            # ③
4
>>> "z" in a_tuple                      # ④
True

①タプルに要素を加えることはできません。タプルにはappend()、extend()メソッドはありません。

②タプルから要素を削除することはできません。タプルにはremove()、pop()メソッドはありません。

③タプルの要素を探すことはできます。この場合はタプルを変更しないからです。

④in演算子を使ってタプルの中に要素があるかどうかを調べることができます。

では、タプルの何がよいのでしょうか?

  • タプルはリストよりも高速です。変わらない値の組を定義して繰り返し使うのであれば、リストではなくタプルを使えばよいでしょう。
  • コードが不必要に変更されないように"書き込み禁止"にすれば、安全性が高まります。リストではなくタプルを使うと、そのデータは不変であるというアサートステートメントを持つので、オーバーライドするには特別な意図(あるいは、特殊な関数)が必要になります。
  • タプルは辞書のキーとして使われることもあります(例えば、文字列、数値、他のタプルのようなイミュータブル値をもったタプルを辞書のキーにします。)リストはイミュータブルではないので、辞書キーとして使うことは不可能です。
👉 タプルはリストに変換することができますし、逆も可能です。ビルトインのtuple()関数はリストを受け取って同じ要素のタプルを返します。list()関数はタプルを受け取ってリストを返します。言うなれば、tuple()はリストを凍結し、list()はタプルを解凍するのです。

2.5.1. ブール値コンテクストのタプル


if文のようなブール値コンテクストにおいて、タプルを使うことができます。
>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(())             # ①
no, it's false
>>> is_it_true(('a', 'b'))     # ②
yes, it's true
>>> is_it_true((False,))       # ③
yes, it's true
>>> type((False))              # ④

>>> type((False,))

①ブール値コンテクストでは、空のタプルはFalseです。

②少なくとも1つの要素があるタプルはTrueです。

③少なくとも1つの要素があるタプルはTrueです。値は何でもかまいません。コンマが入っているのはどういうことでしょうか?

④1つだけの要素でタプルを作る場合は、値のあとにコンマが必要です。コンマがない場合、タプルではなく括弧が2重になっているだけだ、とPythonは解釈してしまいます。それ自体はエラーになるわけではありませんが、タプルは生成されません。

2.5.2. 同時に複数の値を代入する


クールなショートカットを紹介しましょう。Pythonではタプルを使って複数の値をまとめて代入することができます。
>>> v = ('a', 2, True)
>>> (x, y, z) = v       # ①
>>> x
'a'
>>> y
2
>>> z
True

①vは3要素のタプルで、(x, y, z)は3変数のタプルです。タプルを代入すると、vの値を順番に変数に代入します。

これはいろいろな場面で使えます。ある範囲の値に名前をつけることを考えてみましょう。ビルトインのrange()関数と複数の変数代入によって、連続した値を素早く代入することができます。
>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)  # ①
>>> MONDAY                                                                       # ②
0
>>> TUESDAY
1
>>> SUNDAY
6

①ビルトインのrange()関数は連続した整数です(厳密にはrange()関数はイテレータを返すので、リストやタプルではありません。この区別についてはあとで学びます)。ここで定義している変数はMONDAY、 TUESDAY、 WEDNESDAY、 THURSDAY、 FRIDAY、 SATURDAY、 SUNDAYです。(この例はcalendarモジュールから来ています。UNIXプログラムのcalのように、カレンダーを出力する素敵なモジュールです。このモジュールでは、曜日に対して整数を定義しています)

②MONDAYが0、TUESDAYが1、というように、変数はそれぞれ値をもっています。

複数の変数を代入して、複数の値を返す関数を作ることもできます。すべての値が入ったタプルを返せばよいだけです。呼び出し側はこれを1つのタプルとして扱うことができ、変数それぞれに値を代入できます。多くのPython標準ライブラリではこれをやっています。次の章で出てくるosモジュールも同様です。

2.6. セット

セットは値の入った「袋」です。同じ値は1つだけで、値の順序は決まっていません。セットには、どんな値でもイミュータブル型として入れることができます。2つのセットがあれば、標準的なセット操作(和、積、差)ができます。

2.6.1 セットを生成する


まず、第一歩目から。セットの生成は簡単です。


>>> a_set = {1}     # ①
>>> a_set
{1}
>>> type(a_set)     # ②

>>> a_set = {1, 2}  # ③
>>> a_set
{1, 2}

①セットに1つの値を入れて作るには、値を波括弧{}に入れます。

②正確にいうとセットはクラスとして実装されるのですが、今は気にしないでもかまいません。

③複数の値からセットを作るには、コンマで分けて波括弧に入れます。

リストからセットを作ることもできます。
>>> a_list = ['a', 'b', 'mpilgrim', True, False, 42]
>>> a_set = set(a_list)                           # ①
>>> a_set                                         # ②
{'a', False, 'b', True, 'mpilgrim', 42}
>>> a_list                                        # ③
['a', 'b', 'mpilgrim', True, False, 42]
①リストからセットを作るには、set()関数を使います。(セットがどのように実装されるか知っている人は、これは関数を呼び出しているのではなく、クラスを生成しているのだと指摘することでしょう。そういう違いが、この本を読むとわかるようになるとお約束します。差し当たっては、set()は関数のように振る舞ってセットを返す、とだけ知っておいてください。)

②先ほど述べたように、セットにはどんな型の値でも入りますし、順番はありません。このセットは元のリストの順番を覚えていません。このセットに要素を加えたときも、どの順に加えたかを記憶しません。

③元のリストは変わりません。

値が1つもないですか?何も問題はありません。空のセットを作ることだってできるのです。
>>> a_set = set()    # ①
>>> a_set            # ②
set()
>>> type(a_set)      # ③

>>> len(a_set)       # ④
0
>>> not_sure = {}    # ⑤
>>> type(not_sure)


①空のセットを作るには、set()を引数なしで呼び出します。

②空のセットを出力がされますが、少し奇妙に見えるかもしれません。{}が出てくると思ったのではないでしょうか?{}は、空の辞書を表すもので、空のセットではありません。辞書については、あとで出てきます。

③出力は奇妙ですが、これはセットで・・・

④・・・このセットには要素がありません。

⑤Python2から引き継いだ歴史的な習慣で、波括弧でセットを作ることはできません。これは空のセットではなく、空の辞書を作っています。

2.6.2. セットを変更する


セットに値を加える方法は2つあります。add()メソッドとupdate()メソッドです。
>>> a_set = {1, 2}
>>> a_set.add(4)  # ①
>>> a_set
{1, 2, 4}
>>> len(a_set)    # ②
3
>>> a_set.add(1)  # ③
>>> a_set
{1, 2, 4}
>>> len(a_set)    # ④
3

①add()メソッドは引数を受け取り(型は何でもよい)、その値をセットに加えます。

②このとき、セットには3つの要素があります。

③セットは同一の値を持つことはありません。そのため、すでにセットに入っている値を加えようとすると、何も起こりません。エラーも出ず、何も起こらないのです。

④3要素で変わっていません。
>>> a_set = {1, 2, 3}
>>> a_set
{1, 2, 3}
>>> a_set.update({2, 4, 6})                       # ①
>>> a_set                                         # ②
{1, 2, 3, 4, 6}
>>> a_set.update({3, 6, 9}, {1, 2, 3, 5, 8, 13})  # ③
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 13}
>>> a_set.update([10, 20, 30])                    # ④
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 10, 13, 20, 30}

①update()メソッドは、セットを引数として受け取り、その中身をすべて元のセットに加えます。加える側のセットの中身1つ1つに対して、add()メソッドを呼び出した場合と同じです。

②セットに同じ値が複数入ることはないので、同じ値を与えても無視されます。

③実際のところ、update()メソッドには引数がいくつあっても大丈夫です。2つのセットを引数にしたときは、update()メソッドは各セットの中身すべてを元のセットに(重複を除いて)追加します。

④update()メソッドは、型が違う数値オブジェクト(例えばリスト)を受け取ることができます。リストで呼び出した場合、update()メソッドはリストの要素すべてを元のセットに追加します。

2.6.3. セットから要素を削除する

セットから要素を削除する方法は3通りあります。discard()、remove()、pop()です。discard()とremove()の間には小さな差があります。
>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
>>> a_set
{1, 3, 36, 6, 10, 45, 15, 21, 28}
>>> a_set.discard(10)                        # ①
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}
>>> a_set.discard(10)                        # ②
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}
>>> a_set.remove(21)                         # ③
>>> a_set
{1, 3, 36, 6, 45, 15, 28}
>>> a_set.remove(21)                         # ④
Traceback (most recent call last):
  File "", line 1, in 
KeyError: 21

①discard()メソッドは1つの値を引数として受け取り、その値をセットから削除します。

②discard()メソッドを、セットに入っていない値で呼び出した場合は、何もしません。エラーは出ず、何も起こりません。

③remove()メソッドも、1つの値を引数として受け取り、その値をセットから削除します。

④ここにremoveとdiscardの違いがあります。セットに値がない場合は、remove()メソッドはKeyError例外を出します。


リストと同様、セットにもpop()メソッドがあります。
>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
>>> a_set.pop()                                # ①
1
>>> a_set.pop()
3
>>> a_set.pop()
36
>>> a_set
{6, 10, 45, 15, 21, 28}
>>> a_set.clear()                              # ②
>>> a_set
set()
>>> a_set.pop()                                # ③
Traceback (most recent call last):
  File "", line 1, in 
KeyError: 'pop from an empty set'

①pop()メソッドでは、セットから取り出した値を1つ返しますが、セットには順序がないので"最後の値"がありません。どの値を削除するかは決める方法がなく、全くの任意になります。

②clear()メソッドは、セットのすべての値を削除するので、空のセットが残ります。このはa_set = set() として空のセットを生成して上書きすることと等しくなります。

③空のセットからpop()で値を取り出そうとしても、KeyError例外が出ます。

2.6.4. 共通のSet操作


Pythonのset型にも、共通の操作がサポートされています。
>>> a_set = {2, 4, 5, 9, 12, 21, 30, 51, 76, 127, 195}
>>> 30 in a_set                                                     # ①
True
>>> 31 in a_set
False
>>> b_set = {1, 2, 3, 5, 6, 8, 9, 12, 15, 17, 18, 21}
>>> a_set.union(b_set)                                              # ②
{1, 2, 195, 4, 5, 6, 8, 12, 76, 15, 17, 18, 3, 21, 30, 51, 9, 127}
>>> a_set.intersection(b_set)                                       # ③
{9, 2, 12, 5, 21}
>>> a_set.difference(b_set)                                         # ④
{195, 4, 76, 51, 30, 127}
>>> a_set.symmetric_difference(b_set)                               # ⑤
{1, 3, 4, 6, 8, 76, 15, 17, 18, 195, 127, 30, 51}

①in演算子を使って、ある値がセットに入っているかどうかを調べることができます。in演算子はリストのときと同じように働きます。

②union()メソッドは、2つのセットに入っているすべての要素を新しいセットに入れて返します。

③intersection()メソッドは、2つのセットのどちらにもある要素を新しいセットに入れて返します。

④difference()メソッドはa_setには入っているがb_setには入っていない要素のセットを返します。

⑤symmetric_difference()メソッドは、どちらか片方にだけ入っている要素を返します。

この中で、3つのメソッドは対称性があります。
# continued from the previous example
>>> b_set.symmetric_difference(a_set)                                       # ①
{3, 1, 195, 4, 6, 8, 76, 15, 17, 18, 51, 30, 127}
>>> b_set.symmetric_difference(a_set) == a_set.symmetric_difference(b_set)  # ②
True
>>> b_set.union(a_set) == a_set.union(b_set)                                # ③
True
>>> b_set.intersection(a_set) == a_set.intersection(b_set)                  # ④
True
>>> b_set.difference(a_set) == a_set.difference(b_set)                      # ⑤
False

①a_setのb_setからの対称差と、b_setのa_setからの対称差の値は異なるように見えます。しかし、セットには順序がないことを忘れてはいけません。2つのセットは、すべての値が(残らず)同じであれば等しいとみなされます。

②これがまさにそうです。Pythonのシェルの出力に惑わされてはいけいけません。2つのセットは同じ値を持っているので、等しいのです。

③2つのセットの和は対称性があります。

④2つのセットの積は対称性があります。

⑤2つのセットの差は対称性はありません。数字から別の数字を引く場合を考えるとわかりやすいです。どちらから引くかで変わってきます。

最後に、セットに関しての疑問に答えましょう。
>>> a_set = {1, 2, 3}
>>> b_set = {1, 2, 3, 4}
>>> a_set.issubset(b_set)    # ①
True
>>> b_set.issuperset(a_set)  # ②
True
>>> a_set.add(5)             # ③
>>> a_set.issubset(b_set)
False
>>> b_set.issuperset(a_set)
False

①a_setはb_setの下位集合(部分集合)です。a_setのすべての要素はb_setの要素です。

②反対の質問をしてみると、b_setはa_setの上位集合であることがわかります。a_setのすべての要素はb_setの要素です。

③b_setにはない値をa_setに追加すると、どちらのテストもFalseになります。

2.6.5. ブール値コンテクストでのセット


if文のようなブール値コンテクストで、セットを使うことができます。
>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(set())          # ①
no, it's false
>>> is_it_true({'a'})          # ②
yes, it's true
>>> is_it_true({False})        # ③
yes, it's true

①ブール値コンテクストでは、空のセットはFalseです。

②1つでも要素が入っていればTrueです。

③1つでも要素が入っていればTrueで、値は何でもかまいません

2.7. 辞書

辞書は、順序のないキーと値のペアです。キーを辞書に加えたときは、そのキーに対する値も加えなくてはならないのです(値はいつでも変えられます)。Pythonの辞書は、キーがわかっているときに値が得られる仕様になっていて、逆では使えません。

👉 Pythonの辞書はPerl5でのhashに似ています。Perl5では、hashを保存する変数は必ず%記号から始まります。Pythonでは名前は何でもよくて、データ型はPythonが内部で記憶してくれます。

2.7.1. 辞書の作成


辞書は簡単に作成できます。構文はsetと似ていますが、値だけではなくキーと値のペアを入れます。辞書が作成されたら、キーを使えば値を調べることができます。
>>> a_dict = {'server': 'db.diveintopython3.org', 'database': 'mysql'}  # ①
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> a_dict['server']                                                    # ②
'db.diveintopython3.org'
>>> a_dict['database']                                                  # ③
'mysql'
>>> a_dict['db.diveintopython3.org']                                    # ④
Traceback (most recent call last):
  File "", line 1, in 
KeyError: 'db.diveintopython3.org'

①2つのアイテムをもつ新しい辞書を作成し、変数a_dictに代入します。どちらも、キーと値のペアで、アイテムの組は波括弧{}で囲まれています。

②キー'server'に紐づけられた'db.diveintopython3.org'は、a_dict['server']で参照されます。

③キー'database'に紐づけられた'mysql'は、a_dict['database']によって参照されます。

④キーを使って値を取り出すことはでき値を使ってキーを取り出すことはできません。つまり、a_dict['server']は'db.diveintopython3.org'を返しますが、a_dict['db.diveintopython3.org'']は例外を出します。db.diveintopython3.org'はキーではないからです。

2.7.2. 辞書の編集


辞書には、あらかじめ決められたサイズ制限は一切ありません。いつでも新しいキーと値のペアを追加できますし、今あるキーの値を書き換えることもできます。先ほどの例からみると・・・
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> a_dict['database'] = 'blog'  # ①
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'blog'}
>>> a_dict['user'] = 'mark'      # ②
>>> a_dict                       # ③
{'server': 'db.diveintopython3.org', 'user': 'mark', 'database': 'blog'}
>>> a_dict['user'] = 'dora'      # ④
>>> a_dict
{'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}
>>> a_dict['User'] = 'mark'      # ⑤
>>> a_dict
{'User': 'mark', 'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}

①辞書の中で同じキーを複数作ることはできません。存在するキーに値を代入すると、古い値に上書きすることになります。

②新しいキーと値のペアをいつでも追加することができます。この構文は存在する値を書き換えている操作と同じです。

③辞書の新しいアイテム(キー:'User'、 値:'mark')が中央に現れています。実は、最初の例で要素が並んでいた順は単なる偶然なので、今は順番が変わっていますが理由は特にありません。

④存在するキーに値を入れると、単純に新しい値を古い値と置き換えます。(①と同じ)

⑤このようにすると、キーuserの値がmarkに戻るでしょうか?答はノーです。キーをよく見てみると・・・新しいUserではUが大文字になっています。辞書のキーは大文字小文字を区別するので、この記述では新しいキーと値のペアを作ることになり、既存のものを上書きすることはありません。似ているように見えるかもしれませんが、Pythonから見ると全く違うのです。

2.7.3. データ型の混ざった辞書

辞書に入れることができるのは、文字列だけではありません。型は何でもよく、整数、ブール値、任意のオブジェクトを辞書に入れることができます。また、辞書を他の辞書に入れてもよいのです。1つの辞書の中で値の型が揃っている必要もありません。好きなように混ぜたり、揃えたりするのも自由です。辞書のキーにはもう少し制限がありますが、文字列、整数、他いくつかの型が使えます。キーの型も辞書の中で混ぜても揃えても自由です。

実は、「はじめてのPythonプログラム」で見ていたのは、文字列でないキーや値の例だったのです。
SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
            1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}

対話型シェルで部分的に見ていきましょう。
>>> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
...             1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
>>> len(SUFFIXES)      # ①
2
>>> 1000 in SUFFIXES   # ②
True
>>> SUFFIXES[1000]     # ③
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
>>> SUFFIXES[1024]     # ④
['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
>>> SUFFIXES[1000][3]  # ⑤
'TB'

①リスト、セットのときのように、len()関数は辞書のキーの数を与えます。

②リスト、セットのときのように、in演算子を使って特定のキーが辞書の中で定義されているかどうかを調べることができます。

③1000はSUFFIXES辞書のキーであり、その値は8要素の(正確には8つの文字列の)リストです。

④同様に、1024はSUFFIXES辞書のキーで、これもまた8要素のリストです。

⑤SUFFIXES[1000]がリストなので、このリストの中の個々の要素を0から始まるインデックスで取り出すことができます。

2.7.4. ブール値コンテクストでの辞書


辞書をif文のようなブール値コンテクストの中で使うことができます。
>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true({})             # ①
no, it's false
>>> is_it_true({'a': 1})       # ②
yes, it's true

①ブール値コンテクストでは、空の辞書はFalseです。

②ひとつでもキーと値のペアをもっている辞書はTrueです。

2.8. None

NoneはPythonの特殊関数で、Null値です。NoneはFalseと同じではありません。Noneは数値0、空の文字列でもありません。NoneとNone以外とを比較すると、すべてFalseが返ります。

NoneはPythonで唯一のNull値であり、特有のデータ型(NoneType)をもっています。Noneはどの変数にも代入されますが、他のNoneTypeオブジェクトを作ることはできません。値がNoneであるすべての変数は互いに等しくなります。
>>> type(None)

>>> None == False
False
>>> None == 0
False
>>> None == ''
False
>>> None == None
True
>>> x = None
>>> x == None
True
>>> y = None
>>> x == y
True

2.8.1. ブール値コンテクストでのNone


ブール値コンテクストでは、NoneはFalseでnot NoneはTrueです。
>>> def is_it_true(anything):
...   if anything:
...     print("yes, it's true")
...   else:
...     print("no, it's false")
...
>>> is_it_true(None)
no, it's false
>>> is_it_true(not None)
yes, it's true

0 件のコメント:

コメントを投稿