[Pythonクイズ]2つある同じ代入文、1つにまとめられますか?:Pythonステップアップクイズ
同じ代入文がプログラムの中で複数回出てくるときってありますよね。それらを1つにまとめられるものならまとめてスッキリしたいと思うのがプログラマーの性(さが)です。どうすれば、まとめられるのか、考えてみましょう。ただし答えは1つじゃない?
【問題】
以下はユーザーの入力を受け取り、それが数値だったときにはそれを二乗した値を表示するプログラムである。このコードで気になるのは「num = input('Enter a number: ')」行が2カ所に書かれている点だ。これを1行で済ませるにはどうすればよいだろう。ただし、正解は1つとは限らない。
num = input('Enter a number: ')
while num: # 空文字列が入力されたら終了
if num.isdigit():
num = int(num)
print(num ** 2)
num = input('Enter a number: ')
2週間ぶりのご無沙汰です。Pythonクイズのお時間がやってまいりました。やー、やっちゃいました。「へなちょこPythonista」の「へなちょこ」っぷりが発揮されています、この問題。
正解が1つじゃないってクイズとしてはどうなの? という方もたくさんいるでしょう。もちろん、最初は正解が1つだけのつもりで問題を考えていたのですが、編集会議の途中で「これってソレでも解けるよね?」と言われるまで、全くもってソレのことを思い出さずにいたのです。
正解が1つになるように問題文で制限することも考えましたが、Pythonの関数だって複数の値を返せるんです(タプルとして)。だったら、Pythonクイズに正解が複数あったっていいじゃないですか。もしかしたら、この記事で書いている以外のやり方もあるかもしれません。HPかわさきのアカウントをX(旧Twitter)に作ったので、こんなやり方があるよって方はそちらまでお知らせくださいね。こんな問題どうよとか、カンタンすぎるとか、いろいろなご意見もお待ちしています。
【答え】
以下が答え(その1)のコード例です。
while num := input('Enter a number: '):
if num.isdigit():
num = int(num)
print(num ** 2)
このコード例では「代入式」を使うことでwhileループの条件部でユーザーの入力を受け取ると同時にそれが空文字列でないかどうかを判断するようになっています。これにより、whileループに入る前に必要だったinput関数呼び出しと、whileループの最後にあったinput関数呼び出しを1つにまとめられたというわけです。
そして、答え(その2)のコード例が以下です。
while True:
num = input('Enter a number: ')
if num == '': # 「if not num:」でも可
break
if num.isdigit():
num = int(num)
print(num ** 2)
こちらのコード例ではwhile文の条件部をTrueとすることで、「無限ループ」を形成しています。そして、無限ループのコードブロックの先頭でユーザー入力を受け付けて、それが空文字列なら無限ループを終了し、そうでなければ入力が数字のみの場合に二乗した値を表示するようになっています。
スッキリした……?
スッキリしたかどうかはともかくとして、以下では代入式と無限ループについてカンタンに解説していきましょう。
【解説:代入式】
代入式を使うと、「代入文を書けない場所」で変数に代入できるようになります(Python 3.8で追加)。代入文とは区別するために、代入式では「:=」演算子を使います(90度右に回転するとセイウチの目と牙のように見えるため、セイウチ演算子とか、ウォルラス演算子、Walrus演算子などと呼ばれます)。
Pythonのプログラムコードは文と式で構成されていて、文を書けるところには式を書けますが(これを「式文」と呼びます)、式しか書けないところに文は書けません。そしてPythonでは、変数への代入は通常は代入文を使います。そのため、Python 3.8で代入式が導入されるまでは式しか書けないところで代入はできませんでした。そして、式しか書けない場所の代表例が問題にあったwhile文の条件です(if文でも同様です)。
代入式はセイウチ演算子の右辺にある式の結果をその値とします。例えば、上のコードの「num := input('Enter number: ')」という代入式はinput関数を呼び出した結果(ユーザーからの文字列の入力)を変数numに代入するとともに、それを式の値として返します。ユーザーが何かのテキストを入力して[Enter]キーを押せば、その文字列が代入式の値です。何も入力せずに[Enter]キーだけを押せば空文字列が代入式の値になります。空文字列はFalseとして、それ以外の文字列はTrueとして判定されるので、while文の条件としてうまく扱えるようになっています。
優先順位には注意!
なお、代入式の優先順位は一番低いことには注意してください。以下はユーザーが'END'と入力したとき以外はwhileループを継続するように上のコードを修正した(つもりの)コードです。
while num := input('Enter a number: ') != 'END':
if num.isdigit():
num = int(num)
print(num ** 2)
以下はこのコードを実行し、プロンプトに「10」を入力したところです。
AttributeError例外が発生しています。これは今も述べた通り、代入式の優先順位が一番低いので「num := input('Enter number: ') != 'END'」という式において「:=」演算子の右側にある「input('Enter number: ') != 'END'」全体の評価結果が変数numに代入されているからです。
具体的には、input関数でユーザー入力を受け取り、それが'10'なので、「'10' != 'END'」という比較が行われて、その結果であるTrueが変数numの値となります。Trueはbool型のオブジェクトであって文字列ではないので。isdigitメソッドがありません。そのために、AttributeError例外が発生したのです。AttributeError例外のメッセージをよく読むと「bool型のオブジェクトにはisdigitという属性はない」と書いてあることからもこのことが分かるでしょう。
よって、このコードは次のように書くのが正解になります。
while (num := input('Enter a number: ')) != 'END':
if num.isdigit():
num = int(num)
print(num ** 2)
代入式をかっこで囲むことで優先順位を明確にすることで、動作するようになるはずです。
このように、代入式を使う際には、結構な頻度でそれをかっこで囲む必要があります。逆に代入式の使用がスマートである箇所では、かっこを無理に使う必要がないこともあります。うまく使って、簡潔で読みやすいコードを書けるようにしましょう。
【解説:無限ループ】
無限ループとは、例えばwhile文の条件を常に真とすることで、いつまでも終わらないようになっているループのことです。以下に答え(その2)のコード例を再掲します。このコードでは条件をダイレクトに「True」としています。
while True:
num = input('Enter a number: ')
if num == '': # 「if not num:」でも可
break
if num.isdigit():
num = int(num)
print(num ** 2)
無限ループといっても、いつまでもループを続けるわけにはいきません。そこで、ここではユーザーが[Enter]キーだけを押して、空文字列が入力されたときにはループを終了するようにしています。コードの行数が増えているので、スッキリしたようなスッキリしていないような気もするかもしれません。でも、行数が増えた分、何をしたいのかがプログラムコードとして明確に記述されていて、代入式を使ったコードよりも分かりやすくなっていると感じる人もいるでしょう。
代入式を使う方法と無限ループを使う方法。どちらがよいかの判断は筆者にはつきません。簡潔な記述がお好みなら代入式を使うでしょう。多少長くなってもプログラムの意図を明確にしたければ無限ループを使うでしょう。
というわけで、答えが1つじゃない問題の回でした(笑)。一度やっちゃったからには、こういう問題もアリだよねってことで、これからも答えが幾つか考えられる問題を出していくことになると思います。よろしくお願いします。
なお、代入式については「Python入門」の「if文による条件分岐」で説明しています。無限ループについては「Python入門」の「while文による繰り返し処理」や「解決!Python」の「while文やfor文で無限ループを記述するには」で説明しています。興味のある方はそちらもぜひ読んでくださいね。
Copyright© Digital Advantage Corp. All Rights Reserved.