現在地: ホーム Dive Into Python 3

難易度: ♦♦♢♢♢

ネイティブデータ型

疑問はすべての哲学の礎であり、探求はその前進であり、無知はその終わりだ。
— ミシェル・ド・モンテーニュ

 

飛び込む

データ型だ。初めてのPythonプログラムのことはしばらく忘れて、データ型について話そう。Pythonではすべての値がデータ型を持っているにもかかわらず、変数のデータ型を宣言する必要はない。これはどういう仕組みで動いているのだろう? Pythonはそれぞれの変数について、もとの代入がなされたときに何の型であるかを把握し、それを内部的に追跡しているのだ。

Pythonは数多くのネイティブデータ型を持っている。重要なものは以下の通りだ:

  1. ブール値は、TrueまたはFalseのどちらかを表す。
  2. 数値は、整数(12)を表したり、浮動小数点数(1.11.2)を表したり、分数(1/22/3)を表したりできる。複素数さえも表せる。
  3. 文字列はUnicode文字のシーケンス(並び)だ。例: HTMLドキュメント。
  4. バイト列バイト配列。例: JPEG画像。
  5. リストは値のシーケンスだ。要素は順序づけされる。
  6. タプルも値もシーケンスだが、リストと違ってイミュータブルだ(後述)。要素は順序づけされる。
  7. 集合は値を詰めこんだ袋だ。要素は順序づけされない。
  8. 辞書はキーと値のペアを詰めた袋だ。要素は順序づけされない。

もちろん、ここで取り上げたものよりももっと多くの型が存在する。Pythonではあらゆるものがオブジェクトなので、モジュール関数クラスメソッドファイル、そしてコンパイルされたコードといった型すら存在する。これらのうちのいくつかは既に見ている: モジュールは名前を持つ関数はdocstringsを持つ、などなど。クラスについてはクラスとイテレータで学ぶことになり、ファイルについてはファイルで学ぶ。

文字列やバイトはかなり重要で、そしてかなりやっかいなので、専用の章を設けている。まずはそれ以外のものから見ていこう。

ブール値

ブール値は真または偽のどちらかを表す。Pythonは、True, Falseと分かりやすく命名された2つの定数を持っており、これらはブール値を直接代入するために使うことができる。式を評価した結果もブール値になりうる。いくつかの箇所(たとえばif文)では、式を評価した結果はブール値であることが要求される。このような箇所はブール値のコンテクストと呼ばれる。そこでは事実上どんな式でも使うことができ、Pythonはその式の真偽値を決定しようと試みる。ブール値のコンテクストにおいて、どのような規則に基づいて値が真/偽と評価されるかは、データ型によってまちまちである(何を言っているか分からないかもしれないが、この章に登場する具体例をいくつか見れば、意味はよく分かるだろう)。

例として、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

Python 2から引き継がれた古い問題のために、ブール値を数値として扱うこともできる。True1で、False0だ。

>>> True + True
2
>>> True - False
1
>>> True * False
0
>>> True / False
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: int division or modulo by zero

ああ、ああ気持ち悪い! これはやってはいけない。言及はしたけれど、忘れてほしい。

数値

数値というのは素晴らしい。非常にたくさんのなかから値を選ぶことができる。Pythonは、整数浮動小数点数の両方をサポートしている。この2つを区別するための型宣言は存在せず、Pythonは小数点が存在するかしないかで両者を区別する。

>>> type(1)                 
<class 'int'>
>>> isinstance(1, int)      
True
>>> 1 + 1                   
2
>>> 1 + 1.0                 
2.0
>>> type(2.0)
<class 'float'>
  1. 値や変数の型を調べるにはtype()関数が使える。ご推察の通り、1intだ。
  2. 同様に、値や変数の型が特定の型かどうかを調べるには、isinstane()関数が使える。
  3. intintに加えるとintが生み出される。
  4. intfloatに加えるとfloatが生み出される。Pythonはintfloatに型強制 (coercion) してから加算を行い、その結果としてfloatを返すのだ。

整数から浮動小数点数への型強制、およびその反対

いま見た通り、いくつかの演算子(加算など)は必要に応じて整数を浮動小数点数に型強制する。自分でこの型強制を行うことも可能だ。

>>> float(2)                
2.0
>>> int(2.0)                
2
>>> int(2.5)                
2
>>> int(-2.5)               
-2
>>> 1.12345678901234567890  
1.1234567890123457
>>> type(1000000000000000)  
<class 'int'>
  1. float()関数を呼び出すことで、明示的にintfloatに型強制できる。
  2. 当然ながら、int()関数を呼び出すことで、明示的にfloatintに型強制できる。
  3. int()関数は四捨五入ではなく切り捨てを行う。
  4. int()関数は、負数を0の方向へ向けて切り捨てる。これは正しい切り捨て関数であり、床関数 (floor function) ではない。
  5. 浮動小数点数は小数第15位まで正確だ。
  6. 整数はどんな大きさでも対応できる。

Python 2はintlongを区別していた。intデータ型が表現できる数はsys.maxintに制限されていて、この値はプラットフォームごとに異なるが通常は232-1だった。Python 3はただ一つの整数型を持っており、これはPython2のlong型とほぼ同様に振る舞う。詳細はPEP 237を見てほしい。

一般的な数値演算

数値があれば、どんなことでもできる。

>>> 11 / 2      
5.5
>>> 11 // 2     
5
>>> −11 // 2    
−6
>>> 11.0 // 2   
5.0
>>> 11 ** 2     
121
>>> 11 % 2      
1
  1. /演算子は浮動小数点数の除算を行う。たとえ分子と分母の両方がint型であっても、この演算子はfloat型を返す。
  2. //演算子はひねくれた整数除算を行う。結果が正の数の場合は、整数への切り捨て(四捨五入ではない)とみなすことができるが、これについては注意が必要だ。
  3. 負の数を整数除算する場合、//演算子は最も近い整数に繰り「上げる」。数学的に言えば、−6−5より小さいので「繰り下げ」というべきだが、−5に切り捨てられることを期待していると足もとをすくわれる。
  4. //演算子は常に整数を返すわけではない。分子や分母のどちらか一方でもfloatの場合は、なお結果を最も近い整数に丸めてくれるのだが、実際の戻り値はfloatで返される。
  5. **演算子は「べき乗」を意味する。つまり112121だ。
  6. %演算子は整数除算の余りを返す。112で割ると5余り1になるので、ここでの結果は1となる。

Python 2では、通常、/演算子は整数除算をなすものであったが、コードに特別な命令を入れれば浮動小数点数除算の演算子にすることもできた。Python 3では、/演算子は常に浮動小数点数の除算を行う。詳細はPEP 238を見てほしい。

分数

Pythonが扱えるのは整数や浮動小数点数だけではない。高校で習って、その後すぐに忘れたしゃれた数学も、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 "<stdin>", line 1, in <module>
  File "fractions.py", line 96, in __new__
    raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
ZeroDivisionError: Fraction(0, 0)
  1. 分数を使うために、fractionsモジュールをインポートする。
  2. 分数を定義するには、Fractionオブジェクトを作って分子と分母を渡す。
  3. 分数をつかった普通の数学的演算ならなんでもできる。演算結果は新しいFractionオブジェクトとして返される。2 * (1/3) = (2/3)
  4. Fractionオブジェクトは自動的に約分される。(6/4) = (3/2)
  5. Pythonは、分母がゼロの分数が作成されないようにうまくやってくれる。

三角法

Pythonでは基本的な三角法も行える。

>>> import math
>>> math.pi                
3.1415926535897931
>>> math.sin(math.pi / 2)  
1.0
>>> math.tan(math.pi / 4)  
0.99999999999999989
  1. mathは、円周率π(円の周りの長さと直径の比)のために定数を用意している。
  2. mathには基本的な三角関数が全部入っている。例えばsin(), cos(), tan()といったものや、asin()のような派生形も入っている。
  3. とはいえ、Pythonは無限の精度を持たないことに注意しよう。tan(π / 4)1.0を返すべきなのに、0.99999999999999989が返ってきてしまう。

ブール値のコンテクストでの数値

数値は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
  1. Python対話シェルの中で、独自の関数を定義できることは知っていただろうか? 各行の終わりでENTERキーを押すだけで良い。空行でENTERを押すと完了だ。
  2. ブール値のコンテクストでは、ゼロでない整数は真で、0は偽だ。
  3. ゼロでない浮動小数点数は真であり、0.0は偽だ。これには注意しなければならない! わずかな丸め誤差が発生した場合(前の節で見たように、これは起こりうる)、Pythonは0ではなく0.0000000000001を評価することになり、その結果Trueが返されてしまう。
  4. 分数もブール値のコンテクストで使うことができる。Fraction(0, n)はすべての n について偽になる。それ以外の分数は真だ。

リスト

リストはPythonの主戦力となるデータ型だ。私が「リスト」と言うとき、あなたはこう考えるかもしれない。「サイズをあらかじめ宣言する必要がある配列で、同じ型の要素だけを含むことができる」と。しかし、そう考えてはならない。リストはそれよりもずっとクールなものだ。

PythonのリストはPerl 5の配列に似ている。Perl 5では、配列を格納する変数は常に@という文字から始まる。Pythonでは、変数はどんな名前でもよく、Pythonが内部的にデータ型を追跡してくれる。

PythonのリストはJavaの配列を遙かに超えたものだ(本当に望むなら、配列のように使いつづけることもできるが)。もっと良いアナロジーはArrayListクラスだろう。これは任意のオブジェクトを格納できるし、新しい要素を追加するとサイズが動的に拡張される。

リストを作る

リストを作るのは簡単だ: カンマで区切って並べた値を、角括弧で包めばいい。

>>> 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'
  1. 初めに、5つの要素を持ったリストを定義する。要素が元の順番を保持していることに注意しよう。これは偶然ではない。リストは順序づけられた要素の集合なのだ。
  2. リストはインデックスが0から始まる配列のように扱うことができる。空でないリストの先頭の要素は常にa_list[0]だ。
  3. リストのインデックスは0から始まるので、この5つの要素を持つリストの最後の要素はa_list[4]だ。
  4. 負のインデックスは、配列の後ろ側から逆順に数えて要素にアクセスする。空でないリストの最後の要素は常にa_list[-1]だ。
  5. 負のインデックスが理解しにくいのなら、こう考えてみよう: a_list[-n] == a_list[len(a_list) - n]。つまりこのリストでは、a_list[-3] == a_list[5 - 3] == a_list[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']
  1. 2つのインデックスを指定することで、リストの一部を取得できる。これは「スライス」と呼ばれる。戻り値は、1つ目のスライスインデックス(この例ではa_list[1])から、2つ目のスライスインデックス(この例ではa_list[3])の直前までを含む新しいリストで、順序も保持されている。
  2. スライスは、スライスインデックスの一方または両方が負の数でも動作する。これは次のように考えると良いかもしれない: リストを左から右へ読み、1つ目のスライスインデックスで欲しい最初の要素を指定し、2つ目のスライスインデックスは欲しくない最初の要素を指定する。戻り値はその間に含まれるすべてだ。
  3. リストの添え字は0から始まるので、a_list[0:3]はリストの最初の3つの要素を返す。これはa_list[0]から始まり、a_list[3]の直前までを含む。
  4. 左側のスライスインデックスが0のときは、これを省略できる。つまり、a_list[:3]は、始点のインデックスが暗黙に0とみなされるので、a_list[0:3]と同じだ。
  5. 同様に、右側のスライスインデックスがリストの長さと同じである場合は、これを省略できる。つまりa_list[3:]は、このリストが5つの要素から構成されているので、a_string[3:5]と同じになる。ここには気持ちの良い対称性がある。この5つの要素を持つリストでは、a_list[:3]は最初の3要素を返し、a_string[3:]は最後の2要素を返す。実際に、リストの長さにかかわらず、a_list[:n]は常に最初の n 個の要素を返し、a_list[n:]はその残りを返す。
  6. 両方のスライスインデックスが省略された場合は、リストのすべての要素が含まれる。しかし、これは元の a_list 変数とは異なる。これは、たまたますべて同じ要素を持った新しいリストなのだ。だから、a_list[:]はリストの完全なコピーを作るための簡易な方法として使える。

要素をリストに追加する

要素をリストに追加する方法は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', 'Ω']
  1. +演算子はリストを結合して新たなリストを作る。リストは要素をいくつでも含むことができ、(利用可能なメモリ量以外に)サイズの制限はない。しかしながら、メモリの使用量が問題となるような場合には、このリストの結合によって新たなリストがメモリ上に作成されることに注意すること。この場合、その新しいリストはすぐに既存の変数a_listへ代入される。つまり、この行は実際には結合と代入という2つのステップから成り立っており、巨大なリストを扱うときには(一時的に)大量のメモリを消費する可能性があるのだ。
  2. リストは任意のデータ型の要素を含むことができ、1つのリストの要素がすべて同じ型である必要はない。実際に、このリストには文字列と浮動小数点数と整数が入っている。
  3. append()メソッドはリストの末尾に要素を1つ追加する(今、リストには4つの異なるデータ型がある!)。
  4. リストはクラスとして実装されている。リストの「作成」は実際にはクラスのインスタンス化だ。ゆえに、リストは自身を操作するためのメソッドを持っている。extend()メソッドは1つの引数としてリストをとり、引数として与えられたリストの各要素を元のリストへ追加する。
  5. insert()メソッドは1つの要素をリストに挿入する。1つ目の引数は、挿入によって位置がずらされる最初の要素のインデックスだ。リストの各要素の値は、リストの中で唯一の値である必要はない。。例えば、現在このリストには2つの'Ω'があり、リストの最初(a_list[0])と最後(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']
  1. extend()メソッドは単一の引数として常にリストを取り、そのリストの各要素をa_listへ追加する。
  2. 3つの要素を持つリストを、別の3つの要素を持つリストで拡張 (extend) すると、6つの要素を持つリストになる。
  3. 一方で、append()メソッドも引数を1つだけとるが、その引数はどんなデータ型でもいい。ここでは、3つの要素を持つリストを引数としてappend()メソッドを呼び出している。
  4. 6つの要素を持つリストに対してリストを加える (append) と、結果は…… 7つの要素を持つリストになる。なぜ7つなのだろうか? なぜなら最後の要素(今加えたもの)はリスト自体だからだ。リストはどんなデータ型でも含むことができるので、リストが他のリストを含むこともできる。これは望む結果かもしれないし、そうでないかもしれない。だがどうであれ、この操作をすれば、このような結果が返ってくるのだ。

リストの値を検索する

>>> 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 "<interactive input>", line 1, in ?
ValueError: list.index(x): x not in list
  1. もうお分かりだと思うが、count()メソッドは指定された値がリストの中に何回出現するかを数える。
  2. リストの中に特定の値があるかどうかを知りたいだけの場合は、count()メソッドよりもin演算子の方がわずかながら処理が早い。in演算子は、常にTrueまたはFalseを返す。リスト中にその値が何回現われるのかは教えてくれないのだ。
  3. in演算子もcount()メソッドも、値がリストのどこに現れるのかは教えてくれない。値がリストのどこにあるのかを知る必要がある場合は、index()メソッドを呼び出そう。デフォルトでは、このメソッドはリスト全体を検索するが、2番目の引数で検索を開始するインデックス(0を起点とする)を指定することや、さらに3つ目の引数で検索を止めるインデックス(0を起点とする)を指定することもできる。
  4. index()メソッドはリスト中で最初に出現したものだけを検出する。この例だと、'new'がリストの中に2回出現していて、a_list[2]a_list[4]の2箇所に存在しているが、index()メソッドは1つ目のインデックスだけを返す。
  5. 予想していなかっただろうが、値がリストから見つからない場合、index()メソッドは例外を送出する。

待った、何だって? そう、index()メソッドは、値をリストから見つけ出せなかった場合に例外を送出するのだ。これは他の多くの言語と際だって異なる点だ。他の多くの言語はここで無効なインデックス(例えば-1)を返すだろう。始めのうちはこれを煩わしく思うかもしれないが、次第にこの良さが分かるようになると思う。こうすれば、後の方で密やかに奇妙な振る舞いを引き起こすのではなく、問題の根源でクラッシュしてくれるからだ。-1がリストの有効なインデックスだということを思い出してほしい。もし仮にindex()メソッドが-1を返していたとしたら、あまり楽しくないデバッグ作業に連れていってくれただろうね!

リストから要素を取り除く

リストは自動的に伸張したり収縮したりできる。伸張については既に見ている。リストから要素を取り除くのにもいくつかの方法がある。

>>> 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'
  1. 特定の要素をリストから削除するには、del文が使える。
  2. インデックス1を削除した後に、インデックス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 "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
  1. remove()メソッドでもリストから要素を取り除ける。remove()メソッドはを受け取り、それで最初に見つかったものをリストから取り除く。ここでもやはり、削除される要素の後ろにあるすべての要素は「隙間を埋める」ために位置がずらされる。リストは決して隙間を持たないのだ。
  2. remove()メソッドは好きなだけ呼び出すことができるが、リストの中に存在しない値を取り除こうとすると、このメソッドは例外を送出する。

リストから要素を取り除く: おまけ

別の面白いリストのメソッドとしてpop()がある。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 "<stdin>", line 1, in <module>
IndexError: pop from empty list
  1. リストのpop()メソッドを引数なしに呼び出すと、リストの最後の要素が削除されて、削除された値が返される
  2. 任意の要素をリストからpopできる。これにはインデックスをpop()メソッドに渡すだけでいい。すると、要素が取り除かれ、「隙間を埋める」ために後ろにある要素がずらされ、取り除かれた値が返される。
  3. 空リストのpop()メソッドを呼び出すと、例外が送出される。

引数無しでのpop()の呼び出しは、Perlのpop()関数に似ている。これはリストから末尾の要素を取り除き、その取り除かれた値を返す。Perlは他にshift()という関数を持っている。これはリストの先頭の要素を取り除いて、その値を返すものだが、Pythonではa_list.pop(0)で同じことができる。

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

リストは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
  1. ブール値のコンテクストでは、空のリストは偽だ。
  2. 要素を1つでも持つリストは真だ。
  3. 要素を1つでも持つリストは真だ。要素の値は関係ない。

タプル

タプルはイミュータブル(後述)なリストだ。いったん作成されたタプルは、どんな手段によっても変更できない。

>>> 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')
  1. タプルはリストと同じような方法で定義するが、要素の集合の全体を角括弧ではなく丸括弧で包む点が異なる。
  2. タプルの要素はリストと同様に決められた順序を持つ。タプルのインデックスはリストと同様に0から始まるので、空でないタプルの最初の要素は常にa_tuple[0]だ。
  3. リストと同様に、負のインデックスはタプルの後ろ側から数えられる。
  4. リストと同様に、スライスすることもできる。リストをスライスすると新しいリストが得られるように、タプルをスライスすると新しいタプルが得られる。

タプルとリストの大きな違いは、タプルは変更ができないという点だ。専門用語ではこれを、タプルはイミュータブル (immutable) であると言う。実際的な言葉で言えば、タプルはタプルの変更を許すようなメソッドを持っていないということだ。リストはappend(), extend(), insert(), remove(), pop()などのメソッドを持っているが、タプルはこれらのメソッドを持っていない。それでも、タプルをスライスすることはできるし(新しいタプルが作られるから)、タプルが特定の値を持っているかどうかを確かめることもできるし(タプルを変更しないから)、そして…… まぁこれくらいにしておこう。

# 前の例から続く
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
>>> a_tuple.append("new")               
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'append'
>>> a_tuple.remove("z")                 
Traceback (innermost last):
  File "<interactive input>", line 1, in ?
AttributeError: 'tuple' object has no attribute 'remove'
>>> a_tuple.index("example")            
4
>>> "z" in a_tuple                      
True
  1. タプルに要素を追加することはできない。タプルはappend()extend()というメソッドを持っていない。
  2. タプルから要素を取り除くことはできない。タプルはremove()pop()というメソッドを持っていない。
  3. タプル中の要素を検索することはできる。これはタプルを変更しないからね。
  4. in演算子を使って、要素がタプルに含まれているかどうかを調べることもできる。

で、タプルは何の役に立つのだろうか?

タプルはリストに変換できるし、その逆もできる。組み込みのtuple()関数はリストを引数にとって、同じ要素を持ったタプルを返す。またlist()関数はタプルを引数にとってリストを返す。実質的には、tuple()はリストの凍結を行い、list()はタプルの解凍を行うと考えることができる。

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

タプルは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))              
<class 'bool'>
>>> type((False,))
<class 'tuple'>
  1. ブール値のコンテクストでは、空のタプルは偽だ。
  2. 要素を1つでももつタプルは真だ。
  3. 要素を1つでももつタプルは真だ。要素の値は関係ない。しかし、このカンマは何をしているのだろう。
  4. 1つの要素からなるタプルを作るには、値の後にカンマを置かなくてはならない。もしこのカンマがないと、Pythonは余分な括弧があるだけだと見なしてしまう。これはエラーにはならないが、タプルは作成されない。

複数の値を一度に代入する

クールな技をお見せしよう: Pythonでは、タプルを使って複数の値を一度に代入できるのだ。

>>> v = ('a', 2, True)
>>> (x, y, z) = v       
>>> x
'a'
>>> y
2
>>> z
True
  1. v は3つの要素を持つタプルで、(x、y、z)は3つの変数を持つタプルだ。一方をもう一方へ代入すると、vの各々の値が、順番通りに各々の変数へ代入される。

これにはあらゆる使い道がある。仮に、連続する数値に名前を付けたいとしよう。組み込みのrange()関数と多値代入を使うことで、連続する値を素早く代入することができる。

>>> (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)  
>>> MONDAY                                                                       
0
>>> TUESDAY
1
>>> SUNDAY
6
  1. 組み込みのrange()関数は、整数のシーケンスを構築する(技術的に言うと、range()関数はリストやタプルではなく、イテレータを返すのだが、この違いについては後で学ぶ)MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAYが、ここで定義している変数だ(この例はcalendarモジュールから持ってきた。これは小さくて愉快なモジュールで、UNIXcalプログラムのようにカレンダーを表示する。calendarモジュールは各曜日ごとに整数の定数を定義している)。
  2. 各変数は値を持った。MONDAY0TUESDAY1、などなど。

多値代入は、複数の戻り値を返す関数を作るためにも使える。これをするには単純に、すべての戻り値を含むタプルを返せばいい。呼び出し元は、返されたタプルを1つのタプルとして扱うこともできるし、個別の変数に値を代入することもできる。次の章で学ぶosモジュールなどを含む、多くのPythonの標準ライブラリがこれを行っている。

集合

集合は一意な値を詰めた「袋」であり、その要素は順序づけされない。1つの集合の中には、イミュータブルなデータ型の値なら何でも含めることができる。2つの集合を作れば、それらを使って和集合・積集合・差集合といった標準的な集合演算を行うこともできる。

集合を作る

重要なことからやろう。集合を作るのは簡単だ。

>>> a_set = {1}     
>>> a_set
{1}
>>> type(a_set)     
<class 'set'>
>>> a_set = {1, 2}  
>>> a_set
{1, 2}
  1. 1つの値を持った集合を作るには、その値を波括弧 ({}) の中に入れればいい。
  2. 集合は、実際にはクラスとして実装されているが、今の段階では気にしないでおこう。
  3. 複数の値を持った集合を作るには、値をカンマで区切って波括弧で包めばよい。

リストから集合を作ることもできる。

>>> 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]
  1. リストから集合を作るには、set()関数を使えばよい(集合がどのように実装されているのかを知っているスノッブたちは、これが実は関数呼び出しではなく、クラスのインスタンス化であると指摘してくるかもしれない。この違いはこの本の後の章で学ぶことを約束する。現時点では、set()が関数のように機能して、集合を返すことだけを知っていればよい)。
  2. 前に述べたように、1つの集合はどんなデータ型でも含むことができる。そして、同じく前に述べたように、集合は順序づけされていない。この集合は、集合を作るために使ったリストの元の順序を記憶していない。仮に、この集合にいくつかの要素を追加したとしても、集合はそれらを追加した順序を覚えないのだ。
  3. 元のリストは変更されていない。

まだ値を一つも持っていないって? 問題ない。空の集合を作ることができる。

>>> a_set = set()    
>>> a_set            
set()
>>> type(a_set)      
<class 'set'>
>>> len(a_set)       
0
>>> not_sure = {}    
>>> type(not_sure)
<class 'dict'>
  1. 空の集合を作るには、set()を引数なしに呼び出せばいい。
  2. 空の集合は少し変わった表現で表示される。もしかすると{}と表示されると予想していただろうか? それだと空の集合ではなく、空の辞書を表すことになってしまう。辞書については後でこの章で学ぶ。
  3. 変わった表現で表示されるが、これは集合であり……
  4. ……この集合は要素を持たない。
  5. 昔からある変な記法がPython 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
  1. add()メソッドは単一の引数(どんな型でもよい)をとり、与えられた値を集合に追加する。
  2. この集合は今3つの要素を持っている。
  3. 集合は一意な値を詰めた袋だ。既にその集合に含まれている値を追加しようとしても、何も起こらない。例外も送出されない。本当に何も起こらないのだ。
  4. 集合はまだ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}
  1. update()メソッドは1つの引数として集合をとり、その集合のすべての要素を元の集合に追加する。これは集合の各要素を引数としてadd()メソッドを呼び出すのと同じだ。
  2. 集合は重複した値を含むことができないので、重複した値は無視される。
  3. 実際にはupdate()メソッドは任意の数の引数と共に呼び出すことができる。2つの集合と共に呼び出すと、update()メソッドは各々の集合の各要素を(重複を除いて)元の集合に追加する。
  4. update()メソッドは、リストのようにいくつもの異なるデータ型から構成されるオブジェクトを受け取ることができる。リストと共に呼び出されると、update()メソッドはリストのすべての要素を元の集合に追加する。

集合から値を取り除く

集合から値を取り除くには3つの異なる方法がある。始めの2つはdiscard()メソッドとremove()メソッドで、この2つには微妙な違いが1つある。

>>> 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 "<stdin>", line 1, in <module>
KeyError: 21
  1. discard()メソッドは引数として1つの値を受け取り、その値を集合から取り除く。
  2. 集合に存在しない値を引数としてdiscard()メソッドを呼び出したときは、何も行われない。エラーも起きない。本当に何も起きないのだ。
  3. remove()メソッドも同じく引数として1つの値を受け取り、その値を集合から取り除く。
  4. 違いはこれだ: 値が集合の中に存在しない場合は、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 "<stdin>", line 1, in <module>
KeyError: 'pop from an empty set'
  1. pop()メソッドは1つの値を取り除いてその値を返す。ただし、集合の要素は順序付けされていないので、集合には「最後の」値というものはなく、したがって、どの値が取り除かれるのかを制御する方法はない。どれが取り除かれるかは完全に不定だ。
  2. clear()メソッドはすべての値を集合から削除し、空の集合を残す。
  3. 空の集合からpopしようとすると、KeyError例外が送出される。

一般的な集合演算

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}
  1. ある値が集合に含まれているかを確認するには、in演算子を使えばよい。これはリストの場合と同様に機能する。
  2. union()メソッドは、どちらかの集合に含まれるすべての要素を含んだ新しい集合を返す。
  3. intersection()メソッドは、両方の集合に含まれるすべての要素を含んだ新しい集合を返す。
  4. difference()メソッドは、a_setには含まれるがb_setには含まれていないすべての要素を含んだ新しい集合を返す。
  5. symmetric_difference()メソッドは、どちらか一方だけの集合に含まれるすべての要素を含んだ新しい集合を返す。

以上のメソッドのうち、3つは対称的だ。

# 前の例から続く
>>> 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
  1. b_setからa_setの対称差 (symmetric difference) は、a_setからb_setの対称差とは異なるように見えるが、集合は要素が順序づけされないことを思い出して欲しい。2つの集合が同じ要素をすべて(残さず)含んでいれば等しいと見なされる。
  2. これが真実だ。Pythonシェルが表示した集合の表現に騙されてはならない。これらは同じ値を含んでいるので、これらは等しい。
  3. 2つの集合の和集合 (union) も対称的だ。
  4. 2つの集合の積集合 (intersection) も対称的だ。
  5. 2つの集合の差 (difference) は対称的ではない。これは理にかなっている。つまり、これはある数から他の数を引くことに似ている。演算対象の順番が問題となるのだ。

最後に、集合に尋ねることのできる事柄をいくつか紹介する。

>>> 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
  1. a_setb_set部分集合 (subset) だ。つまり a_set のすべての要素は set にも含まれている。
  2. 逆の言い方をすると、a_setのすべての要素はb_setの要素でもあるので、b_seta_set上位集合 (superset) だ。
  3. b_set に含まれていない値をa_setに追加すると、たちまち両方の結果はFalseになる。

ブール値のコンテクストでの集合

集合は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
  1. ブール値のコンテクストでは、空の集合は偽だ。
  2. 要素を1つでも持つ集合は真だ。
  3. 要素を1つでも持つ集合は真だ。要素の値は関係ない。

辞書

辞書は、キーと値のペアからなる順序付けされていない集合だ。辞書にキーを追加するときは、そのキーに対応する値も同時に追加しなければならない(この値はあとでいつでも変更できる)。Pythonの辞書は、既知のキーをもとに値を取得するのは効率的に行えるようになっているが、その逆方向はそうなっていない。

Pythonの辞書はPerl 5のハッシュに似ている。Perl 5では、ハッシュを格納する変数は常に%という文字から始める。Pythonでは、変数はどんな名前でもよく、Pythonが内部的にデータ型を追跡する。

辞書を作る

辞書を作るのは簡単だ。構文は集合に似ているが、値の代わりにキーと値のペアが必要になる。辞書を作ったあとは、キーを指定することで値を取り出せる。

>>> 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 "<stdin>", line 1, in <module>
KeyError: 'db.diveintopython3.org'
  1. はじめに、2つの要素を持つ辞書を作り、それを変数a_dictへ代入する。各々の要素はキーと値のペアであり、要素の集合の全体は波括弧に包まれている。
  2. 'server'はキーだ。このキーに関連づけられた値はa_dict['server']によって参照でき、その値は'db.diveintopython3.org'だ。
  3. 'database'はキーだ。このキーに関連づけられた値はa_dict['server']によって参照でき、その値は'mysql'だ。
  4. 値はキーによって取得できるが、キーを値によって取得することはできない。つまり、a_dict['server']'db.diveintopython3.org'になるが、'db.diveintopython3.org'はキーではないので、a_dict['db.diveintopython3.org']は例外を送出する。

辞書を変更する

辞書には、事前に定められた上限サイズというものは一切ない。新しいキーと値のペアをいつでも辞書に追加できるし、既在のキーに対応する値もいつでも変更可能だ。前の例から続けよう:

>>> 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'}
  1. 辞書に重複したキーを持たせることはできない。既存のキーに値を代入すると、古い値が追い出される。
  2. キーと値のペアはいつでも追加できる。この構文は既存の値を変更するときとまったく同じだ。
  3. 新しい辞書の要素(キー'user'、値'mark')は中央に現れている。実際のところ、最初の例で要素が現れた順序は偶然であり、ここで順序が崩れたように見えるのも同じく偶然だ。
  4. 辞書の既存のキーに代入すると、古い値が新しい値に単純に置き換えられる。
  5. これはuserキーの値を "mark" に戻すだろうか? いや、違う! キーをよく見てほしい。"User" に大文字の U がある。辞書のキーについては大文字と小文字が区別されるので、この文は新たなキーと値のペアを作り、既存の値は書き換えない。似たように見えるかもしれないが、Pythonから見れば、これはまったく異なるものなのだ。

値が混在する辞書

辞書は文字列しか扱えないわけではない。辞書の値はどんなデータ型でもよく、例えば、整数型、ブール値、任意のオブジェクトを使うことができるし、他の辞書を値にすることだってできる。また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'
  1. リスト集合と同じように、len()関数は辞書に含まれるキーの数を教えてくれる。
  2. そしてリスト集合と同様に、特定のキーが辞書に含まれているかどうかを調べるには、in演算子が使える。
  3. 1000は辞書SUFFIXESのキーであり、対応する値は8つの要素(正確には8つの文字列)を持つリストだ。
  4. 1024は辞書SUFFIXESのキーであり、それに対応する値もまた8つの要素を持つリストだ。
  5. SUFFIXES[1000]はリストなので、このリストの個々の要素を0から始まるインデックスで指定できる。

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

辞書は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
  1. ブール値のコンテクストでは、空の辞書は偽だ。
  2. キーと値のペアを1つでも持っている辞書は真だ。

None

NoneはPythonの特別な定数で、これはnull値(無効値)である。NoneFalseではないし、0でもないし、空の文字列でもない。NoneNone以外の値と比較すると、常にFalseが返る。

Noneは唯一のnull値であり、Noneは自身のデータ型 (NoneType) を持っている。Noneは任意の変数へ代入できるが、他のNoneTypeのオブジェクトを作ることはできない。Noneを値として持つすべての変数は互いに等しい。

>>> type(None)
<class 'NoneType'>
>>> None == False
False
>>> None == 0
False
>>> None == ''
False
>>> None == None
True
>>> x = None
>>> x == None
True
>>> y = None
>>> x == y
True

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

ブール値のコンテクストでは、Noneは偽でnot Noneは真だ。

>>> 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

もっと知りたい人のために

© 2001– Mark Pilgrim
© Fukada, Fujimoto(日本語版)