Pythonのオブジェクトの同一性と、is演算子や==演算子による比較の違い、str関数とrepr関数で得られる文字列表現の違いなどについて取り上げる。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
* 本稿は2019年7月16日に公開された記事を、Python 3.12.0で動作確認したものです(確認日:2023年11月10日)。
前回は、Pythonのオブジェクトがどんなものかについて簡単に見た。今回はオブジェクトの同一性、その比較の仕方、それから文字列表現について見ていく。
前回はオブジェクトは「型」と「値」に加えて「同一性」を持つと述べた。同一性は、コンピュータのメモリ上に作成されたオブジェクトのアドレスのようなものであり、オブジェクトがメモリから取り除かれるまで、変わることはない*1。つまり、オブジェクトが作成されると、それに固有な一意の値が割り当てられる。以下ではこれを「アイデンティティー」と呼ぶことにしよう。
*1 あるオブジェクトが削除された後に、別のオブジェクトが作成されると、それらのアイデンティティーが同じになる可能性はある。が、あるオブジェクトが存在している間は、アイデンティティーを同一にする他のオブジェクトは存在しない。
オブジェクトのアイデンティティーを調べるには、組み込みのid関数が使える。
id(obj)
objのアイデンティティーを示す値を返す。
パラメーター | 説明 |
---|---|
obj | アイデンティティーを調べたいオブジェクト |
id関数のパラメーター |
使用例を以下に示す。
mystr = 'Hello'
yourstr = 'Hello'
otherstr = ''.join(['H', 'e', 'l', 'l', 'o']) # リストから文字列を作成
print(id(mystr)) # 変数mystrが参照している文字列のアイデンティティー
print(id(yourstr)) # 変数yourstrが参照している文字列のアイデンティティー
print(id(otherstr)) # 変数mystr/yourstrが参照している文字列とは別のもの
print('-----')
mylist = [1, 2, 3] # リストを作成して、変数mylistに代入
yourlist = mylist # そのリストを変数yourlistに代入
otherlist = list([1, 2, 3]) # mylistと同じ要素を持つリストを作成
print(id(mylist)) # 変数mylistと変数yourlistが参照している
print(id(yourlist)) # オブジェクトは同じなので同じ値になる
print(id(otherlist)) # 同じ要素を持つが別のオブジェクトなので違う値になる
実行結果を以下に示す。
変数mystrに代入した文字列'Hello'のアイデンティティーと、変数yourstrに代入した文字列のアイデンティティーを調べた例では同じ値が表示されている。これは2つの文字列オブジェクトと「同一」であることを意味している。一方、変数otherstrには「H」「e」「l」「l」「o」を要素とするリストから文字列を作成したものが代入されている。リストからの文字列の作成により、最初の文字列'Hello'とは別のオブジェクトが作成されたので、変数otherstrが参照している文字列のアイデンティティーは異なるものになっている。
リストのアイデンティティーの例も同様だ。まず変数mylistに「[1, 2, 3]」というリストを代入している。その後、変数mylistの値を変数yourlistに代入しているので、これらは同一のオブジェクトを参照している。よって、そのアイデンティティーは同一になる。これに対して、変数otherlistに代入されているのは、変数mylist/yourlistが参照しているリストと同じ内容だが、新規に作成したリストだ。そのため、変数otherlistが参照しているリストのアイデンティティーは別のものになっている。
上の例から分かるのは、値は同じだが、アイデンティティーが異なるオブジェクトが存在するということだ。以下では、これを踏まえて、オブジェクトの比較の方法について見ていこう。
オブジェクトを比較する際には、その「アイデンティティー」(同一性)と「値」が重要になってくる。簡単にいえば、2つの変数が同じアイデンティティーを持つオブジェクトを参照していれば、当然のようにそれらは「等しく」なる。だが、アイデンティティーが異なっていても、その「値」が等しいこともある。そのため、Pythonでは次の2種類の比較が行えるようになっている。
このうちの同一性を比較するために使われるのが、is演算子とis not演算子の2つだ。前者は2つのオブジェクトが同じアイデンティティーを持っていれば(同じオブジェクトであれば)Trueとなり、後者はその逆だ。
一方、等価性を比較するのに使われるのが、これまでにも見てきた「==」演算子と「!=」演算子だ。前者は2つのオブジェクトの値が同じであればTrueとなり、後者はその逆だ。
実際に試してみよう。6つの変数には、先ほどの例と同じ手順で代入をしているので、変数mystr/yourstrは内容もアイデンティティーも同じ、変数otherstrは値だけが同じ。変数mylist/yourlistは内容もアイデンティティー同じ、変数otherlistは値だけが同じとなっている。
mystr = 'Hello'
yourstr = 'Hello'
otherstr = ''.join(['H', 'e', 'l', 'l', 'o'])
mylist = [1, 2, 3]
yourlist = mylist
otherlist = list([1, 2, 3])
print('mystr is yourstr?', mystr is yourstr)
print('mystr is otherstr?', mystr is otherstr)
print('mystr == yourstr?', mystr == yourstr)
print('mystr == otherstr?', mystr == otherstr)
print('mylist is yourlist?', mylist is yourlist)
print('mylist is otherlist?', mylist is otherlist)
print('mylist == yourlist?', mylist == yourlist)
print('mylist == otherlist?', mylist == otherlist)
実行結果を以下に示す。
上に述べたように、同じアイデンティティーを持つオブジェクト同士(変数mystr/yourstr、変数mylist/yourlist)ではis演算子による比較も、==演算子による比較もTrueとなっているのが分かる。一方、アイデンティティーが異なるが値は等しいオブジェクト同士(変数mystr/otherstr、変数mylist/otherlist)ではis演算子による比較はFalseに、==演算子による比較はTrueになっている。
なお、「==」演算子はデフォルトでオブジェクトが同一かどうか(同一性)を比較する。組み込み型の一部では、この挙動を置き換えて、値が同じかどうかを比較するようになっている。
例えば、数値の比較では整数と浮動小数点数でも数学的に正しい比較結果が得られるようになっている。つまり、「1 == 1.0」はTrueとなる(「1 is 1.0」はFalseとなる)。同じ要素で構成される(が、アイデンティティーが異なる)リスト同士を==演算子で比較したときに、Trueとなるのも同様だ。
なお、文字列やリスト、タプルなどの比較においては、「オブジェクトが同じ型」「要素数が同じ」「同じインデックス位置にある要素同士が同じ」場合に==演算子による比較結果がTrueとなる。
オブジェクトの比較の詳細については、Pythonのドキュメント「比較」などを参考にしてほしい(本稿では省略したが、順序比較などについても掲載されている)。
本連載ではここまで、オブジェクトの値を画面に表示するときには、何も気を使うことなく、以下のようなコードを書いていた。
mystr = 'my number is'
mynum = 42
print(mystr, mynum)
mylist = [1, 2, 3]
print(mylist)
もちろん、実行結果は次のようになる。
このときにprint関数はカンマ区切りで並べられたオブジェクトを「文字列化」してから、それを画面に表示している。1つ目のprint関数呼び出しでは、文字列オブジェクトと整数オブジェクトをカンマで区切っているが、これを1つの文字列として表示するなら次のようにしていたところを、これと似たことをprint関数は自動的に行っていてくれたということだ(半角の空白文字はprint関数が引数を出力するときに自動的に補ってくれていたが、それも自分で追加する必要がある)。
print(mystr +' ' + str(mynum))
第5回「文字列の基本」の「文字列と数値の変換:str/int/float関数」では、整数(int型)や浮動小数点数(float型)の値を文字列に変換するための仕組みとして、str関数を紹介したが、実際にはこれはオブジェクトの「文字列表現」(オブジェクトを文字列化したもの)を返す関数だ。
str(obj)
objの文字列表現を返す。
パラメーター | 説明 |
---|---|
obj | 文字列化したものを得たいオブジェクト |
str関数のパラメーター |
print関数のドキュメントには「キーワードなしの引数はすべて、 str() がするように文字列に変換され」とある。つまり、print関数にオブジェクトを渡すと、それらをstr関数で文字列化したものが表示されるということだ。よって、「print([1, 2, 3])」のようにリストをprint関数に渡すと、リストそのものではなく、それを文字列化したものが表示される。
しかし、Pythonにはオブジェクトを文字列化するもう1つの手段がある。それが組み込みのrepr関数だ。
repr(obj)
objをPythonにとって意味のある文字列表現として返す。
パラメーター | 説明 |
---|---|
obj | Pythonにとって意味のある文字列表現を得たいオブジェクト |
repr関数のパラメーター |
試しに幾つかのオブジェクトをstr関数とrepr関数で文字列化してみよう。
print('str(1):', str(1))
print('repr(1):', repr(1))
print('str([1, 2, 3]):', str([1, 2, 3]))
print('repr([1, 2, 3]):', repr([1, 2, 3]))
print('str("Hello"):', str("Hello"))
print('repr("Hello"):', repr("Hello"))
この結果は次のようになる。
結果はほぼ変わらないが、最後の2つの出力にだけ微少な(しかし大きな)違いがある。str関数で文字列'Hello'を文字列化(これは、実際には文字列そのものが戻り値となる)したものにはシングルクオート「'」がないが、repr関数の方の出力ではシングルクオートが付加されている点がそうだ。
そこで文字列表現を得るこれら2つの関数の差について次に考えてみよう。
Copyright© Digital Advantage Corp. All Rights Reserved.