Pythonの基本的な組み込み型である、数値型、シーケンス型、テキストシーケンス型、集合型、マッピング型、およびその一般論についてまとめてみました。半分くらいが一般論になってしまいました。
値と参照の違いでハマりやすいところ、イテラブルなオブジェクト、浅いコピー、ハッシュ可能なオブジェクト、といったようなややこみいった話や、ちょっとした便利な技についても適宜まとめましたので、基本的な型なんかもう知ってるんだけど、という方にも役立てるところがあるかと思います。
- Pythonのバージョンについては、3.3以降ならこの内容で大丈夫だろうと思います
- Pythonの環境構築の方法については、著者別記事の「[覚え書き] Pythonの環境構築について」を是非ご覧下さい
目次
- 一般論
-
数値型 (
int
,float
,complex
,bool
) -
シーケンス型・テキストシーケンス型 (
list
,tuple
,str
) -
集合型 (
set
,frozenset
) -
マッピング型 (
dict
)
参考文献
一般論
型の分類
型には、ミュータブル(変更可能)なものとイミュータブル(変更不可能)なものがある。
- 混乱しやすいところについてメモ: Pythonでは、変数は全てオブジェクトへの参照を格納していて、オブジェクト自体を格納しているわけじゃない。なので、オブジェクトがイミュータブルであろうと、変数の値、つまり「どのオブジェクトを参照するか」は変更できる
- 例。代入文
x = 1.5
においては、float
オブジェクト1.5
が作られて、変数x
がそのオブジェクトを参照するようになる。そのあとでx = 2.5
とするときは、float
オブジェクト2.5
が作られて、x
の参照はそっちに移る。float
オブジェクト1.5
自体はイミュータブルで変更できないけれど、変数x
がどのオブジェクトを参照するかは変更できる
- 例。代入文
シーケンス型、集合型、マッピング型のように、複数のオブジェクトをひとまとめにできる型をコレクション型とかコンテナ型と言う。テキストシーケンス型は含まない。(たぶん、文字列はなにか複数のオブジェクトをひとまとめにして作ってるわけじゃないのだろう。実際、他の言語におけるchar
のような、1文字を表す型はPythonにはない。)
ただし、コンテナ型はオブジェクトの列ではなく変数の列、つまりオブジェクトへの参照の列だ。
- なので、イミュータブルなコンテナでは、「コンテナの各要素がどのオブジェクトを参照しているか」を変更できないというだけで、参照されているオブジェクト自体を変化させることは自由:
a = ([1],[2])
a[0].append(3)
a
Out[1]:
([1, 3], [2])
イミュータブルなはずのタプルa
が一見変化して見える。けれど実際は、要素、つまり「どのオブジェクトを参照しているか」は変化していない。ミュータブルなオブジェクト[1]
は[1, 3]
に変化したけれど、オブジェクトとしては同一だからだ。
- 変数(オブジェクトへの参照)を変更することと、オブジェクト自体を変化させることは違う。これを意識していると、変なところでハマらずに済む
型について
Pythonでは全てがオブジェクトであり、int
やstr
といった型自体もtype
型のオブジェクトだ(type
の型もtype
だ。ウケる)。これらは型変換する関数としても使える。例えば:
- 数字による文字列
s
をint
型に変換する:int(s)
-
int
型のi
をfloat
型に変換する:float(i)
- オブジェクト
x
をstr
型に変換する:str(x)
-
print(x)
では、x
は暗黙のうちにstr
型へ変換されている
-
-
type(x)
でx
の型を取得できる("type
型への型変換"とも思える)
Pythonでは全てがオブジェクトであり、クラスすらオブジェクトである。とくに、型はクラスである。1
がint
型だというのはつまり、1
がint
クラスのインスタンスだということだ。type
はtype
クラスのインスタンスだ。そんなことできるのか。すさまじい。
isinstance(type, type)
Out[1]:
True
比較演算
-
x == y
,x != y
は値が同じか違うかどうか、x is y
,x is not y
はオブジェクトとして同じか違うかどうかの真偽値を返す。違うオブジェクトでも値は同じになりえる- Pythonでは、演算子の多くは何らかのメソッドの実行の略記である(これによって演算子のオーバーロードが簡単にできる)。
x == y
では、実際はメソッドx.__eq__(y)
を実行している。「値が同じかどうか」というのはつまり、そのオブジェクトが属すクラスでメソッド__eq__()
がどう実装されているかによる - 全てのオブジェクトは識別値(identity)と呼ばれる整数値を持っていて、
id(x)
で取得できる。x is y
はid(x) == id(y)
と等価だ。つまり、(オブジェクトx, yが同時に存在するときだけど)、オブジェクトとして同じであるかどうかは識別値が同じかどうかを見ても分かる - 例えば
None
かどうかの判定では、x == None
よりx is None
を使うほうが良い。==
の結果はメソッド__eq__()
の実装によるからだ。あと速いらしい
- Pythonでは、演算子の多くは何らかのメソッドの実行の略記である(これによって演算子のオーバーロードが簡単にできる)。
x = (1, 2, 3)
y = (1, 2, 3)
x == y, x is y
Out[1]:
(True, False)
xとyは違うオブジェクトだけど、値は同じである。
x = y = (1, 2, 3)
x == y, x is y
Out[1]:
(True, True)
変数x, yは同じオブジェクトを参照している。値はもちろん同じだ。
- 比較演算は、
a < b <= c > d != e
,a is b is None
のようにいくらでも連鎖できる -
<
とか>=
みたいな不等号は、複素数complex
とマッピングdict
を除いて、どの型でも基本的に使える- シーケンス型は辞書式で比較する
- 集合型では包含関係として比較する。全順序じゃなくて半順序($x \le y$でも$x \ge y$でもないような、比較不能な場合がある)なので、ソートはできない
コンテナ型・テキストシーケンス型について
len(s)
: s
の要素数。
-
len(s)
は、メソッドs.__len__()
を呼び出して、結果がint
かチェックしてから返す、ということをしている
x in s
, x not in s
: s
がx
を要素として含むかどうか、含まないかどうかの真偽値。
max(s)
, min(s)
: s
の最大、最小の要素。
- 同名の可変引数関数もあって、
max(1, 2, 3)
といった使い方もできる
s.copy()
: コンテナs
の浅いコピーを生成して返す。
- 単なる代入文
x = s
では、変数x
がs
と同じオブジェクトを参照するようになるだけだ。一方、浅いコピーは、元のオブジェクトとは別に新しく作られたオブジェクトになっている。つまり、x = s.copy()
としたあとにs
の要素を追加・削除しても、x
は影響を受けない - ただし、コンテナは変数(オブジェクトへの参照)の列だから、各要素が参照しているオブジェクトは同じだ。参照されているオブジェクト自体を変化させたら、一見
s
とx
が連動しているように見える - 各要素が参照しているオブジェクトまでも新しく作るには、深いコピー
copy.deepcopy(s)
を使う -
s.copy()
はなぜかtuple
ではできない。そうか、イミュータブルだからコピー作る必要もとくにないってことかなあと思ったら、同じくイミュータブルなfrozenset
ではできる。謎。
s.clear()
: ミュータブルなコンテナs
の全要素を消去する。
- 具体的には、
list
,set
,dict
型で使える
イテラブル
イテラブルを説明するにはまず、イテレータについて説明する必要がある。
イテレータ(イテレータ型のオブジェクト)とは、データの繰り返しの概念を抽象化したもので、__next__()
メソッドが実装されているようなオブジェクトのこと。このメソッドは、呼び出されるたびになにかオブジェクト(への参照)を吐き出す。吐き出されるオブジェクトは、一般には呼び出されるたびに違う。吐き尽くしたらStopIteration
例外を上げて、以降は何度呼び出してもStopIteration
例外を上げる。
- イテレータを進めるには普通は
next(it)
と書く。これはメソッドit.__next__()
を呼び出す
実はfor
文for x in s
がやっているのは、s
に対してiter()
関数を適用してイテレータiter(s)
を作り、このイテレータが吐き出すオブジェクトをx
として使うことをStopIteration
例外が上がるまでくり返す、ということ。iter(s)
でイテレータを作れるオブジェクト、つまりin
のあとに置けるようなオブジェクトs
はイテラブルであるという。
- コンテナ、文字列は全てイテラブル。
iter(s)
は、s
の要素を1個ずつ返すようなイテレータを作る - イテレータ
it
はiter
で自分自身を返す(iter(it) is it
)ので、自明にイテラブル -
iter(s)
は、新たにイテレータを作るメソッドs.__iter__()
がもしあれば呼び出し、もし無ければ、要素にアクセスするメソッドs.__getitem(key)__
(x = s[key]
とするときに呼び出されるメソッド)を使って新しくイテレータを作る、ということをしている - イテレータは使い捨てであることに注意。データを取っておきたい場合はコンテナに保存しておく必要がある。例:
x = map(int, '192.168.0.0'.split('.'))
print(list(x))
print(list(x))
[192, 168, 0, 0]
[]
map
関数はジェネレータというイテレータ型のオブジェクトを返すので、一度list()
にかけると使い切ってしまう。この場合は、例えばx = tuple(map(int, '192.168.0.0'.split('.')))
のようにタプルに変換しておけば何度でも呼び出せる。
他の例:
square_of_odds = (x ** 2 for x in range(10) if x % 2) #奇数の2乗の列
print(25 in square_of_odds)
print(list(square_of_odds))
True
[49, 81]
丸括弧()
による内包表記もジェネレータというイテレータ型のオブジェクトを返す。in
演算子で要素が入っているか調べると、イテレータをそこまで消費してしまう。要素を保存しておきたいなら、この場合も例えばtuple(x ** 2 for x in range(10) if x % 2)
のようにタプルに変換しておけばよい。
内包表記
コンテナを構成するには、外延表記[a, b, c, ...]
による方法の他に、内包表記[y for x in iterable]
による方法があり、これがとても便利。
- 後置
if
で条件を追加できて、これも便利:[y for x in iterable if b]
-
for
はネストできる:[z for x in s for y in t]
数値型
int
(整数)、float
(浮動小数点数)、complex
(複素数)があり、全てイミュータブル。標準ライブラリには、他にもfraction
(分数)やdecimal
(十進小数)がある。
bool
(真偽値)はint
の派生クラスで、1
に対応するTrue
という定数と、0
に対応するFalse
という定数がある。
-
True > False
とかTrue * 2 == 2 + False
とかよく分からないことが起きるので、このbool
の実装は良くないんじゃないかと思う。bool
をint
として使わないようにしよう
割り算
-
x / y
: 普通の意味での割り算で、int
型同士の割り算がfloat
型になりうる -
x // y
: 整数としての割り算の商(Python2では、int
に対して/
も//
もこれになる。普通の意味での割り算にするには、int
はfloat
に型変換する必要があった) -
x % y
: 整数としての割り算の余り -
divmod(x, y)
: 商と余りのペア(x // y, x % y)
値を丸める
-
round(x, n=0)
: n桁目で偶数丸め(Python2ではただの四捨五入だった) -
math.ceil(x)
: 切り上げ、つまり天井関数$\lceil x \rceil$ -
math.floor(x)
: 切り捨て、つまり床関数$\lfloor x \rfloor$
その他
-
pow(x, y)
,x ** y
: べき乗 -
abs(x)
: 絶対値
真理値判定
どんなオブジェクトもbool
型に変換できる。とくに、if
やwhile
といったbool
を求める構文や、and
, or
, not
といったbool
に対する演算に何かオブジェクトを入れると、そのオブジェクトは暗黙のうちにbool
へ型変換される。
基本的にはTrue
に変換されるけど、以下の場合にはFalse
に変換される:
- 定数
None
- 0に対応する数字:
0
,0.0
,0j
,Decimal(0)
,Fraction(0)
- 空のコンテナや文字列:
[]
,()
,''
,{}
,set()
,range(0)
オブジェクトが0かどうか、空かどうかチェックしたいときは結構あるので、そういうときに便利。
-
bool(x)
はメソッドx.__bool__()
がもしあれば呼び出して、無ければ代わりにメソッドx.__len__()
を呼び出して0でないかどうか調べる、ということをしている
シーケンス型・テキストシーケンス型
シーケンス型にはミュータブルなlist
(リスト)とイミュータブルなtuple
(タプル)があって、テキストシーケンス型にはイミュータブルなstr
(文字列)がある。
共通の機能
要素へのアクセス
-
s[i]
: i番目の要素 -
s[i:j]
: i番目からj-1番目までのスライス -
s[i:j:k]
: i番目からj-1番目までのk飛びでのスライス
これらは浅いコピーと同じく、s
とは異なるオブジェクトを新しく作って返している。つまり、a = s[i:j]
としたあとにs
の要素を追加・削除しても、a
は影響を受けない。
番号について:
- 0番目から数え始める。終端は
len(s)-1
番目 - 終わりから逆に数えて何番目かを表すには、引数i, jを負の数にする。終端は-0番目ではなく-1番目であることに注意。-0は単に0だ
- シーケンスが輪になってると思えば、終端が-1番目なのは始端の0番目の1個前だからと思える
- iやjは、省略するか
None
を入れることで勝手にシーケンスの端になる。どちらの端になるかはkの符号による- 例えば、
s[::-1]
で順番をひっくり返したシーケンスを作れる
- 例えば、
ネストされたシーケンスでは、s[2][:][:7]
などと角括弧[]
を連鎖できる。
和と積
-
s + t
: シーケンスの結合 -
s * n
,n * s
: 同じシーケンスをn個結合
コンテナは変数(オブジェクトへの参照)の列なので、和や積においてはなにかオブジェクトが増えているわけではなく、ただ参照が増えている。あるオブジェクトを変更すると、そのオブジェクトを参照している全ての要素が影響を受けて見えることに注意。例えば、こういう挙動が起きる:
a = [[]] * 3
b = a + [[]] * 2
a[0].append(1)
print(a, b, sep='\n')
[[1], [1], [1]]
[[1], [1], [1], [], []]
a[0].append(1)
では、変数a[0]
が参照しているオブジェクト[]
を[1]
へ変化させているので、そのオブジェクトを参照している他の変数達も[1]
を返すようになる。
一方、a[0].append(1)
をa[0] = [1]
に書き換えてみると、同じじゃないかと思うかもしれないけれど、違う挙動になる:
a = [[]] * 3
b = a + [[]] * 2
a[0] = [1]
print(a, b, sep='\n')
[[1], [], []]
[[], [], [], [], []]
代入a[0] = [1]
では、変数a[0]
の参照先をオブジェクト[]
から別の新しいオブジェクト[1]
へ移している。元のオブジェクトは変化させず、ある変数がどのオブジェクトを参照するかを変更しているだけなので、元のオブジェクト[]
を参照している他の変数達は依然として元のオブジェクトを参照し続けることになる。
その他
-
s.count(x)
:s
に何回x
が現れるか -
s.index(x[, i[, j]])
: (i番目からj-1番目の範囲で)x
がs
の何番目で初めて見つかるか。x
が見つからないとき、ValueError
を上げる
list
(リスト)
構成方法
- 空のリスト:
[]
- 外延表記:
[a]
,[a, b, c]
- 内包表記:
[y for x in iterable]
- 型変換:
list(iterable)
浅いコピーはs.copy()
でもs[:]
でも作れる。
リストはミュータブルなので、以下のようにシーケンスを変化させるような演算ができる。
書き換え
-
s[i] = x
: i番目の要素をx
に入れ替える -
s[i:j] = t
: i番目からj-1番目を、イテラブルなオブジェクトt
に置き換える -
s.append(x)
:x
をs
の最後に付け加える -
s.insert(i, x)
: i番目にx
を挿入 -
s.reverse()
: 順番をひっくり返す -
s.sort(key=None, reverse=False)
: (キー関数key
の返り値で)ソート- 新しいオブジェクトを作って返すなら
sorted(s)
を使う。これなら任意のイテラブルs
で使える
- 新しいオブジェクトを作って返すなら
除去
全消しs.clear()
という共通の操作以外には:
-
del s[i]
: i番目の要素を除去 -
del s[i:j]
: i番目からj-1番目を除去-
s[i:j] = []
とも書ける
-
-
s.pop(i)
: i番目の要素を返すと同時に除去 -
s.remove(x)
: 最初に現れたx
を除去
tuple
(タプル)
構成方法
- 空のタプル:
()
- 外延表記:
a, b, c
または(a, b, c)
- 要素が1個の場合は、
a,
または(a,)
と書く。タプルを作るのは丸括弧()
ではなくカンマ,
だからなのだそうだけど、空のタプルを()
と書くルールと整合してない気がする -
f((x, y))
のようにタプルが関数の引数になっているときは、引数が複数個の場合f(x, y)
と区別するために丸括弧()
が必要。もし同じ記号を使っていたとしたら、可変引数関数はどちらと解釈して良いか困っていただろう
- 要素が1個の場合は、
- 型変換:
tuple(iterable)
- 内包表記
(y for x in iterable)
はtuple
ではなくジェネレータを返す。タプルにしたい場合は、型変換してtuple(y for x in iterable)
とする
- 内包表記
その他
- イテラブルなオブジェクトを変数のタプルへ代入できる。
x, y = divmod(3, 2)
やx, y, z = 1, 2, 3
,x, y, z = 'dog'
のような同時代入文が書けて便利 -
return x, y
のように多値を返す関数が作れる - イテラブル
t
の要素がタプルなら、for x, y in t
のような多値に渡るfor
文が書ける- 例えば、ループ
for x in y
で今何ループ目かi
の情報も欲しいときは、for i, x in enumerate(y)
とする
- 例えば、ループ
str
(文字列)
str
では、in
演算子を部分シーケンスの判定にも使える。
'京都' in '東京都'
Out[1]:
True
構成方法
- シングルクォートで囲む:
'piyo'
- ダブルクォートで囲む:
"piyo"
- シングルクォートかダブルクォートの三重引用符で囲む:
'''piyo'''
,"""piyo"""
- 三重引用符文字列は複数行に分けて書くことができる。複数行のコメントアウトや、クラスや関数にコメント(docstringという)を書くのに使われる
操作
大文字に変更s.upper()
とかアルファベットか判定s.ispalpha()
とか、部分文字列を探すs.find(sub)
とか置換するs.replace(old, new)
とか、文字列に対して行いたい操作としてすぐ思いつくメソッドはだいたい用意されている。とくに、以下はとても便利
-
s.join(iterable)
:iterable
から出てくる文字列達を、間にs
を挟みながら結合した文字列を返す。s
として便利なのは空白,
とか改行\n
とか。普通に結合したいときは+
でつなげばよい
'と'.join(('俺','お前','大五郎'))
Out[1]:
'俺とお前と大五郎'
-
s.split(sep=None)
: 文字列s
を区切り文字sep
で分割する。sep
が指定されてないときは、任意の長さの空白で分割する:
'俺とお前と大五郎'.split('と'), ' a b c'.split()
Out[1]:
(['俺', 'お前', '大五郎'], ['a', 'b', 'c'])
-
s.format(*args, **kwargs)
: 書式化操作。長くなりそうなので、また別の場所に改めてまとめる予定。
集合型
ハッシュ可能なオブジェクトの重複なし順序なしコレクションで、ミュータブルなset
とイミュータブルなfrozenset
がある。
重複がない・ハッシュ可能とは
- 「重複がない」というのは、
x == y
となるx
とy
が同時に含まれることはないということ。x
とy
が違うオブジェクト(x is not y
)であっても、値が同じ(x == y
)なら重複と見なされる。x == y
かどうかを一個ずつまともに判定するのは大変なので、要素のオブジェクトはハッシュ可能なものに限っている - ハッシュ可能なオブジェクトとは、生きている間ずっと変わらないハッシュ値を持っていて(
__hash__()
メソッドを持っていて)、他のオブジェクトと==
で比較できる(__eq__()
メソッドを持っている)オブジェクトのこと - ハッシュ値というのは、オブジェクトが持っているかもしれない整数で、
hash(x)
で取得できる(__hash__()
メソッドが呼び出される)。ハッシュ値の定義としては、同値x == y
なオブジェクトのハッシュ値は同じhash(x) == hash(y)
でなければならない。なので、ハッシュ可能なオブジェクトなら、コンテナs
の各要素y
に対してx == y
かどうかを全て調べ上げる操作が効率的にできる。まずハッシュ値が一致する要素があるか調べて、一致したものだけx == y
かどうか調べればよいからだ - 組み込み型では、イミュータブルなもの(数値型、
tuple
,str
,frozenset
)はハッシュ可能だけど、ミュータブルなもの(list
,set
,dict
)はハッシュ不可能になっている。ユーザー定義のクラスのインスタンスは、デフォルトではハッシュ可能
set
の構成方法
- 空集合:
set()
-
{}
だと空の辞書になる
-
- 外延表記:
{a, b, c}
- 内包表記:
{y for x in iterable}
- 型変換:
set(iterable)
frozenset
は型変換で作る。
基本的操作
-
x | y
: 和集合 -
x & y
: 積集合 -
x - y
: 差集合 -
x ^ y
: 対称差(排他的論理和に対応)
set
はミュータブルなので、以下のような変更操作もできる。
-
x.add(a)
: 要素a
の追加 -
x.remove(a)
: 要素a
を除去。含まれていなければKeyError
を上げる -
x.discard(a)
: 要素a
がもし含まれていれば除去 -
x.pop()
: 要素をどれか1個返すと同時に除去。空集合ならKeyError
を上げる
マッピング型
ハッシュ可能なオブジェクト(キーという)から任意のオブジェクト(値という)へのマップで、ミュータブルなdict
がある。キーと値の組(項目という)の、キー重複なしの順序なしコレクションとも言える。
- 同値なキーが複数含まれることはない。集合と同じく、同値かどうかを一個ずつまともに判定するのは大変なので、キーはハッシュ可能なオブジェクトに限っている。キーでなく値なら、重複はいくらあってもよいし、ハッシュ可能である必要はない。
dict
型に対しては、in
やnot in
演算子は、キーが含まれているかいないかを判定する。
list()
やset()
などで別のコンテナに型変換すると、キーのコンテナが作られる。
構成方法
- 空の辞書:
{}
- 外延表記:
{k1:v1, k2:v2, k3:v3}
- 内包表記:
{key:value for x in iterable}
- 型変換:
dict([(k1,v1), (k2,v2), (k3,v3)])
-
zip
関数を使えばdict(zip([k1,k2,k3], [v1,v2,v3]))
とも書ける - キーワード引数を使った、
dict(k1=v1, k2=v2, k3=v3)
というやり方もある
-
基本的操作
-
d[key]
: キーkey
に対する値を返す。key
がキーになければKeyError
を上げる -
d.get(key, default=None)
: キーkey
に対する値を返す。key
がキーになければdefault
を返す -
d[key] = value
: キーkey
に対して値value
を対応させる項目を追加する。キーkey
が既にあったら値が上書きされる
除去
-
del d[key]
: キーkey
の項目を除去する。key
がキーになければKeyError
を上げる -
d.pop(key[, default])
: キーkey
があればその値を返してその項目を除去する。key
がキーになければdefault
を返すけれど、default
が設定されてなかったらKeyError
を上げる -
d.popitem()
: 項目(key, value)
をどれか1個返すと同時に除去
ビューオブジェクトの取得
ビューオブジェクトとは、自身に要素を保持せずに元のオブジェクトへの参照を保っているだけの、イテラブルなオブジェクト。
-
d.items()
:d
の全項目からなるビューオブジェクトを返す -
d.keys()
:d
の全キーからなるビューオブジェクトを返す -
d.values()
:d
の全値からなるビューオブジェクトを返す
以上です。お読み頂いてありがとうございます。
どこか間違っているところ、怪しいところ、用語の使い方が変なところなどあるかもしれません。指摘、コメント、質問、感想など、何でもお待ちしております。
もし良いなと感じたら、いいねを押して頂けるととても嬉しいです。他の記事も書いてみようというモチベーションになります。