Python
python3

Pythonの基本的な組み込み型とその一般論まとめ

Pythonの基本的な組み込み型である、数値型、シーケンス型、テキストシーケンス型、集合型、マッピング型、およびその一般論についてまとめてみました。半分くらいが一般論になってしまいました。

値と参照の違いでハマりやすいところ、イテラブルなオブジェクト、浅いコピー、ハッシュ可能なオブジェクト、といったようなややこみいった話や、ちょっとした便利な技についても適宜まとめましたので、基本的な型なんかもう知ってるんだけど、という方にも役立てるところがあるかと思います。

  • Pythonのバージョンについては、3.3以降ならこの内容で大丈夫だろうと思います
  • Pythonの環境構築の方法については、著者別記事の「[覚え書き] Pythonの環境構築について」を是非ご覧下さい

目次

参考文献


一般論

型の分類

型には、ミュータブル(変更可能)なものとイミュータブル(変更不可能)なものがある。

  • 混乱しやすいところについてメモ: 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では全てがオブジェクトであり、intstrといった型自体もtype型のオブジェクトだ(typeの型もtypeだ。ウケる)。これらは型変換する関数としても使える。例えば:

  • 数字による文字列sint型に変換する:int(s)
  • int型のifloat型に変換する: float(i)
  • オブジェクトxstr型に変換する: str(x)
    • print(x)では、xは暗黙のうちにstr型へ変換されている
  • type(x)xの型を取得できる("type型への型変換"とも思える)

Pythonでは全てがオブジェクトであり、クラスすらオブジェクトである。とくに、型はクラスである。1int型だというのはつまり、1intクラスのインスタンスだということだ。typetypeクラスのインスタンスだ。そんなことできるのか。すさまじい。

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 yid(x) == id(y)と等価だ。つまり、(オブジェクトx, yが同時に存在するときだけど)、オブジェクトとして同じであるかどうかは識別値が同じかどうかを見ても分かる
    • 例えばNoneかどうかの判定では、x == Noneよりx is Noneを使うほうが良い。==の結果はメソッド__eq__()の実装によるからだ。あと速いらしい
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: sxを要素として含むかどうか、含まないかどうかの真偽値。

max(s), min(s): sの最大、最小の要素。

  • 同名の可変引数関数もあって、max(1, 2, 3)といった使い方もできる

s.copy(): コンテナsの浅いコピーを生成して返す。

  • 単なる代入文x = sでは、変数xsと同じオブジェクトを参照するようになるだけだ。一方、浅いコピーは、元のオブジェクトとは別に新しく作られたオブジェクトになっている。つまり、x = s.copy()としたあとにsの要素を追加・削除しても、xは影響を受けない
  • ただし、コンテナは変数(オブジェクトへの参照)の列だから、各要素が参照しているオブジェクトは同じだ。参照されているオブジェクト自体を変化させたら、一見sxが連動しているように見える
  • 各要素が参照しているオブジェクトまでも新しく作るには、深いコピーcopy.deepcopy(s)を使う
  • s.copy()はなぜかtupleではできない。そうか、イミュータブルだからコピー作る必要もとくにないってことかなあと思ったら、同じくイミュータブルなfrozensetではできる。謎。

s.clear(): ミュータブルなコンテナsの全要素を消去する。

  • 具体的には、list, set, dict型で使える

イテラブル

イテラブルを説明するにはまず、イテレータについて説明する必要がある。

イテレータ(イテレータ型のオブジェクト)とは、データの繰り返しの概念を抽象化したもので、__next__()メソッドが実装されているようなオブジェクトのこと。このメソッドは、呼び出されるたびになにかオブジェクト(への参照)を吐き出す。吐き出されるオブジェクトは、一般には呼び出されるたびに違う。吐き尽くしたらStopIteration例外を上げて、以降は何度呼び出してもStopIteration例外を上げる。

  • イテレータを進めるには普通はnext(it)と書く。これはメソッドit.__next__()を呼び出す

実はforfor x in sがやっているのは、sに対してiter()関数を適用してイテレータiter(s)を作り、このイテレータが吐き出すオブジェクトをxとして使うことをStopIteration例外が上がるまでくり返す、ということ。iter(s)でイテレータを作れるオブジェクト、つまりinのあとに置けるようなオブジェクトsはイテラブルであるという。

  • コンテナ、文字列は全てイテラブル。iter(s)は、sの要素を1個ずつ返すようなイテレータを作る
  • イテレータititerで自分自身を返す(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の実装は良くないんじゃないかと思う。boolintとして使わないようにしよう

割り算

  • x / y: 普通の意味での割り算で、int型同士の割り算がfloat型になりうる
  • x // y: 整数としての割り算の商(Python2では、intに対して///もこれになる。普通の意味での割り算にするには、intfloatに型変換する必要があった)
  • 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型に変換できる。とくに、ifwhileといった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番目の範囲で)xsの何番目で初めて見つかるか。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): xsの最後に付け加える
  • 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)と区別するために丸括弧()が必要。もし同じ記号を使っていたとしたら、可変引数関数はどちらと解釈して良いか困っていただろう
  • 型変換: 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となるxyが同時に含まれることはないということ。xyが違うオブジェクト(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型に対しては、innot 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の全値からなるビューオブジェクトを返す

以上です。お読み頂いてありがとうございます。
どこか間違っているところ、怪しいところ、用語の使い方が変なところなどあるかもしれません。指摘、コメント、質問、感想など、何でもお待ちしております。
もし良いなと感じたら、いいねを押して頂けるととても嬉しいです。他の記事も書いてみようというモチベーションになります。