Python | 条件式の難しさ、数字と文字列 | Unicode

pythonの条件式

こんにちは、初めてプログラミングっぽいことをしたのが、HTML/CSSで作ったサイトにPHPやJavaScriptで、動きのあるサイトを作ったことだった思いますが、現在はPythonを触る日々の小幡です。

LaravelでMVCというアーキテクチャの概念に触れたわけですが、未だによく理解できているとは言えない状況のなかAndroidのMVVCなどのさらに上位アーキテクチャのようなものに触れてしまい、ある意味学習は後回しになっていり昨今です。

そんななか、Pythonで条件分岐を作っている時に、壁にぶつかったので、同じ過ちを繰り返さないように、備忘録として残しておきたいと思います。

簡単な条件分岐式

まずは簡単な条件分岐の式から、おさらいしていこうかと思います。(Python3.7)

pork_price = 150
chicken_price = 100
if pork_price > chicken_price :
    print('豚肉の方が高い')
else:
    print('鶏肉の方が高い')

以上のコードは、豚肉の値段が150、鶏肉の値段が100と、それぞれ変数に入れた後に、
IF文で条件分岐させています。
豚肉の値段が、鶏肉の値段より大きい場合は、「豚肉の方が高い」が標準出力されます。

次のコードはどうでしょうか?

pork_price = 150
chicken_price = 98
if pork_price > chicken_price :
    print('豚肉の方が高い')
else:
    print('鶏肉の方が高い')

以上のコードは、豚肉の値段150、鶏肉の値段98と、ちょっと安くなった鶏肉と豚肉を比較しています。これは問題なく、「豚肉の方が高い」が出力されます。

想定通りに処理がされ、何も言う事がなく問題ありません。

余談ですが、東京の物価は高いです。地元茨城や、7年近く住んでいた大阪の下町では最安値で鶏むね肉100g38円で売っていたのに、東京では68円より安いところを見たことがありません。どうしてこんなに高いのでしょうか・・・。さらに言うと、キャベツや玉ねぎの質が全く違うなぁという印象です。

地元で八百屋さんで野菜を買ったことが無かった(そもそも八百屋がなかった)のですが、こちら東京に来てからは、八百屋さんの偉大さを知りました。コンビニでも野菜を売っていることがありがたいなぁと思っていたりしますが、安くておいしいを求めるなら地方に住むのが良いなぁと思っていたりします。

文字列の比較(unicode

数字(int型)の比較は以上のコードは想定通りで上手く行きました。すべての条件分岐が、私たちが生活している感覚でうまく行ってくれれば、良いなぁと思ったりします(悪いこともあるわけですが・・・)

以下のコードは想定道理に処理されるでしょうか?

pork_price = '150'
chicken_price = '98'
if pork_price > chicken_price :
    print('豚肉の方が高い')
else:
    print('鶏肉の方が高い')

もっとも真面目に勉強した言語がPythonである私にとって、性的型付けということが不慣れなので気が付きませんでしたが、Javaなどでは以上の場合、しっかり型を付けるために、このように間違い探しのようなコードにはならないのではないでしょうか?

さきほどのコードとの違いは’150’となっており、int型だった150がstr型になっています。

150 と 98 はそのままなので、何も問題なく処理されそうですが、実行結果では、「鶏肉の方が高い」が出力されてしまいます。豚肉よりも鶏肉の方が安いところをみたことがありません。

想定外です。

なぜ「鶏肉の方が高い」が出力されてしまったのでしょうか?

それは大小比較しているものがstr型だからです。変数名にpriceやnumberなど、明らかに数値、int型と思われるようなものが提示されているために起こった悲劇とも言えます。

簡単に言うと、数値は1から順番に2,3,4・・・98・・・150・・・というイメージで増えていき、そのまま大小比較されているというイメージです。

その結果、1より2が大きい、98より150が大きい、というように、上で示した想定した結果が帰ってくることになります。

しかし、str型は違います。

まず、’98’と’150’を比較したときに、文字列は文字に分解されます。
つまり’150’は、’1’と’5’と’0’に分解されます。
’98’は、’9’と’8’に分解されます。

次にその文字列の先頭順から比較が始まります。
つまり最初の比較はpork_priceの’1’と、chicken_priceの’9’です。
すると・・・

if '1' > '9': # False

1は9より大きい、と言う条件は偽となった結果、想定外だった「鶏肉の方が高い」が出力されてしまったのでした。

さらに言うと、以上は簡略した比較をしましたが、正確にはそれぞれの文字コードで比較されます。
実行環境によって、変わったり、なんやかんやあったりして、結果が異なる可能性があります。

一般的な日本語文字列として認識されていれば1文字ずつunicodeとして比較されていると思います。

ウィキペディア:unicode参考

文字列は先頭行から比較される

とにかく、文字列は先頭行から比較されるというのが今回の学びです。

そこで気が付いたのが、郵便番号のような、7桁と最初から決まっている数値の文字列であれば、文字列型として比較されても、結果は想定したものが得られるということです。

つまり先ほどの豚肉と鶏肉も7桁固定で先頭に0を付与すれば、想定通りの結果が得られるということです。

if '0000150' > '0000098': # True

そもそもint型にキャスト(型変換)すればよいのですが、色々あって、以上のような妄想をするまでに至りました。というより、文字列には文字列の持つ仕組みや意味があるわけで、わたってきたものに対してもっと意識しなければならないなぁと思ったのでした。

今回は豚肉の値段と、鶏肉の値段という比較をしましたが、実際にぶつかった壁はもっと別の内容だったので、これからも数値と文字列でトラブルになることがあるのかもしれないと思っています。

Pythonの三項演算子

そして個人的にはコードが多少長くなっても、if と else は明示した方が良いなぁと思っているのですが、pythonではリスト内包表記だったり、if文の三項演算子だったりと、簡略化の記述表現の幅が広くて、覚えるのが大変だったりします。

PHPの三項演算子も初めてしってから、個人開発では一度も使うことがありませんでしたが、最近は三項演算子になれてきて、1行でかける良さを感じています。

def get_fruit_data(fruit_id: str) -> dict:
    # get_fruit_data は、外部からデータ参照など
    fruit_data = get_fruit_data(fruit_id
    # 三項演算子で返却
    return fruit_data if fruit_data else {}

get_fruit_dataという関数を定義しています。

この関数はstr型のフルーツIDを引数として受け取り、そのIDを元に外部からデータを参照します。
そして、もしIDに紐づくデータが存在すれば、そのデータを返却し、無ければ空の辞書型を返却します。

この関数のreturnでなぜ、三項演算子を使うのかを説明するために、以下のコードをご覧ください。

def get_fruit_data(fruit_id: str) -> dict:
    # get_fruit_data は、外部からデータ参照など
    fruit_data = get_fruit_data(fruit_id)
    # そのまま返却
    return fruit_data

以上のコードの何が問題なのか?

それは、fruit_dataを外部から参照した際、Noneが返却されている可能性があるということです。
すると、今回定義した関数を呼び出した側が、dict型以外にNoneが返却されてくる可能性を考慮しなくてはならなくなります。

もし、get_fruit_data関数を呼び出した後に、返却された中身を取り出すとしたら・・・

fruit_id = '001'
fruit_data = get_fruit_data(fruit_id)
fruit_name = fruit_data.get('name')

以上の様なコードが想像されます。
1行目で、フルーツIDに’001’を定義。
2行目で、そのIDに紐づいたデータを取得する。
3行目で取得したデータの名前を取り出そうとします。

この場合、example_1では、もし外部データからNoneが返却されていても最終的には空の辞書型{}が返却されていますので、エラーにはなりません。

一方でexample_2はエラーになります。

NameError: name 's' is not defined

関数に返却の型が明示されているだけに、この関数example_2を作ってしまうと、後々問題が起こってくるでしょう・・・。

という学びがありました。

まとめ 保守性、可読性の高いPython

個人開発では、これらの問題は些細なことだと思います。

むしろ自分ルールで変数名は付けるでしょうし、返却する型を明示させることは少ないと思います。

ですが、ちょっと気を使うだけで、保守性の高いPythonコードが実現できるのならば、その手間を惜しまずに、変数名や関数を作成することに注力するべきだと感じました。

私は型やNullをほとんど意識せずにいたため、ある意味Pythonを触った時に何も違和感なくスムーズにコーディングできているものかと錯覚していました。一歩身を引いて、注意深く変数名や返却値を見返してみることが、よりよいコードへの第一歩だと思います。

これからも、見られても恥ずかしくないコードが書けるように努力して行きたいところです。

この記事を書いた人

小幡 知弘

1990年茨城県神栖市生まれ
2013年大阪芸術大学卒業
Python×Webエンジニア