Python3 エンジニア認定基礎試験の認定模擬問題(PRIME STUDY)を解説③

197

前回の続きで「Python3 エンジニア認定模擬問題の解説」です。
問21-30までです。

PRIME STUDY様の認定模擬問題のリンクはこちらです→https://study.prime-strategy.co.jp/


問21.
次のような結果を得たい場合に、コードの2行目(★印の行)を代替するものとして正しいものはどれか。


[ 実行結果 ]
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]

[コード]
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
power = [[row[i] for row in matrix] for i in range(3)] …★

print(power)

・power = zip[*matrix]
・power = zip(*matrix)
・power = zip(list(matrix))
・power = list(zip(*matrix))
・power = list(zip(matrix))

解説:
先に言うと選択肢の中に結果が[[1, 4, 7], [2, 5, 8], [3, 6, 9]]になる選択肢は存在しないので問題が間違っていますね。
一番近いものとしては4つ目の選択肢で、一応解答もこれが正解になっています。
ただ、4つ目の選択肢だと結果は[(1, 4, 7), (2, 5, 8), (3, 6, 9)]になりますので、リストの要素がタプルになってしまいます。
そのことには目をつぶって、とりあえず[(1, 4, 7), (2, 5, 8), (3, 6, 9)]が正解だとしましょう。

ポイントになるのはzip関数と「*」の意味です。
zip関数は複数のイテラブルオブジェクトの要素を、同時に取り出して使います。
例えば、

a = [1, 2, 3]
b = [4, 5, 6]
という2つのリストがある場合「zip(a, b)」とすると「(1, 4), (2, 5), (3, 6)」というように、それぞれの要素から1つずつ要素を取り出してまとめてくれます。
これは3個以上のリストでも可能で「zip(a, b, c)」みたいに「,」で区切って、複数のイテラブルオブジェクトを指定できます。
ただ、このままだとzipオブジェクトという特殊なデータになっていますので、「list(zip(a, b))」みたいにしてリストに変換して使ったり、

for c in zip(a, b):
    print(c)

このようにして、中身だけまとめて出力させるときに使います。

選択肢を見ると、1つ目、2つ目、3つ目はリストにせずにzipデータのままなので、このままでは<zip object at 0x〜>としか出力できません。
リストにしているのは4つ目、5つ目のどちらかが正解とわかります。
次のポイントとして「*」の意味です。
これはアンパックと言って「中身だけを取り出す」書き方です。
つまり「matrix」なら[[1, 2, 3], [4, 5, 6], [7, 8, 9]]ですが、
「*matrix」なら[1, 2, 3], [4, 5, 6], [7, 8, 9]となります。
※外側の[]がない、つまりリストの要素だけ。
つまり、「zip(*matrix)」と書いている場合「zip([1, 2, 3], [4, 5, 6], [7, 8, 9])」となって、zip関数の第1引数に[1, 2, 3]、第2引数に [4, 5, 6]、第3引数に[7, 8, 9]という指定が出来ます。
(リストに名前がついていないだけで、上の例の「zip(a, b)」などと同じ形になります)
こうすることによって、それぞれのリストから要素がまとめて取り出されて、まとめられるので「(1, 4, 7), (2, 5, 8), (3, 6, 9)」というまとまりが出来ます。
これがlist関数でリストに変換されることで実行結果は[(1, 4, 7), (2, 5, 8), (3, 6, 9)]になります。

余談:
ちなみに問題文の実行結果[[1, 4, 7], [2, 5, 8], [3, 6, 9]]と同じにするには、「list(map(list, zip(*matrix)))」にすればOKです。
これは上記のzip関数で作った塊(1, 4, 7)などを、map関数を使って、それぞれをリストに変換しています。


問22.
次のコードの実行結果として正しいものはどれか。


Zen = 'NowIsBetterThanNever'
print(Zen[1:19:4])

・Beav
・BtTnv
・oBeav
・oBene
・ostraer

解説:
スライスの方法についてですね。
前の問題でも[○:△]というスライス記法は出てきましたが、ここでは[1:19:4]と3つの値が指定されています。
この場合は[1から:19まで(19自体は含まない):4ずつ]という意味になります。
(range関数と同じですね)
なので、下のようになります。

「1から」→"o"
「19まで(19自体は含まない)」→末尾から2番めの"e"まで、
「4ずつ」→"o"と"B"と"e"と"a"と"v"

という意味になります。
つまり3つ目の選択肢ですね。


問23.
データ構造に関する次の記述のうち誤っているものはどれか。


・集合の生成には中カッコ{}またはset()関数を使用する。ただし空の集合を生成するには、{}ではなくset()を使う必要がある。例えば「empty = {}」とすると空のディクショナリが生成される。
・ディクショナリに対する帰属性判定演算子「in」「not in」による判定において、「含まれるかどうか」の判定の対象は「値」ではなく「キー」である。
・リストと集合は変更可能(mutable)、タプルは変更不能(immutable)である。
・ディクショナリは変更可能(mutable)であるが、キーの型は変更不能(immutable)であり、その値は一意でなければならない。
・ディクショナリにループをかけるときにenumerate()関数を使うと、キーとそれに対応した値を同時に得られる。

解説:
リスト型と同じように、ディクショナリ型(辞書型)にもenumerate関数が使えますが、取り出せるのは「要素の番号と、キー(key)」です。
値(value)は取り出せません。
つまり5つ目の選択肢の「キーとそれに対応した値を同時に得られる」が誤りです。

余談:
「リストはインデックス番号に値が紐付いていて、辞書型はインデックス番号はなく、その代わりにキーに値が紐付いている」というように理解している場合が多いと思うのですが、
enumerate関数では、なぜか辞書型でもインデックス番号が登場してしまいます。
元々、辞書型は番号がないので順序自体も保証しない、っていう仕様だった(3.7ぐらいから保証するように変わった)こともあり、このenumerate関数の動きはとてもモヤモヤします。


問24.
対話モードで入力したときに「True」が返されるものは次のうちどれか。


・(1, 2, 5, 20, 30) > (2, 3, 4, 5)
・'PHP' > 'Perl' > 'Python'
・'Matplotlib' > 'NumPy' > 'pandas' > 'scikit-learn'
・(2,3,('aa','ab')) < (2,3,('abc','a'),5)
・(-1, -10, -2, -5) > (-1, -2, -5)

解説:
イテラブルオブジェクト同士の不等号を使った比較演算の問題ですね。
Pythonではイテラブルオブジェクト同士の比較の場合、それぞれの1つ目の要素から順番に取り出して比較します。
例えば1つ目の選択肢「(1, 2, 5, 20, 30) > (2, 3, 4, 5)」の場合、それぞれの1つ目の要素は1と2なので、「1 > 2」という比較がされます。
結果はFalseなので、この時点で評価が終わり「(1, 2, 5, 20, 30) > (2, 3, 4, 5)」は「False」という結果になります。
つまり、要素の合計数や平均数などではないため、2つ目の要素以降にどんな大きい数字があっても、先頭の要素が負けてしまうと終わりです。
もし、1つ目の要素が同数の場合は、2つ目の要素を取り出して比較します。
2つ目でも決着がつかない場合は3つ目へ、という感じで処理されます。

また、文字列の比較の場合は、ちょっとややこしく、Unicodeのコードポイントというもので比較されます。
(すべての文字や記号は、それぞれ番号が設定されている、ということです)
ただし、'a'は何番か?などと覚えて置く必要はなく、基本的に「'a'よりも'b'が大きく、'b'よりも'c'が大きい」というように、アルファベット順に大きくなります。

また、要素の中にイテラブルオブジェクトがある場合は、またそのイテラブルオブジェクトの1つ目の要素が取り出されて比較されます。

以上のことから、4つ目の選択肢が正解です。


問25.
モジュールに関する次の記述のうち、正しいものはどれか。


・sys.pathが初期化されている場所は、PYTHONPATHとインストールごとのデフォルトであり、入力スクリプトのあるディレクトリは含まれない。
・あるモジュールがインポートされるときにインタープリタが検索する順序は、まずビルトインモジュール、次にsys.path変数で得られるディレクトリ、そしてシンボリックリンクを置いてあるディレクトリである。
・モジュール読み込みの高速化のため、Pythonはコンパイル済みのモジュールを「__python_cache__」ディレクトリにmodule.バージョン名.pyの名前でキャッシュする。
・モジュールの中では、グローバル変数「__modname__」の値としてモジュール名(文字列)がセットされている。
・実行中のスクリプトのあるディレクトリは、検索パスの最初、標準ライブラリのパスよりも前方に置かれる。

解説:
今までの文章問題と違って、正しいものを選ぶ問題ですので注意です。
正解は5つ目の選択肢です。
他の選択肢の間違い箇所にも触れておきましょう。
1つ目は「入力スクリプトのあるディレクトリは含まれない」というところが誤りで、sys.pathは入力スクリプトのあるディレクトリも含まれます。
(sys.pathとはインポートするモジュールを検索するパスのリスト)
2つ目は「そしてシンボリックリンクを置いてあるディレクトリである」の部分が誤りで、シンボリックリンクを置いてあるディレクトリはモジュール検索パスには入りません。
3つ目は「コンパイル済みのモジュールを「__python_cache__」ディレクトリにmodule.バージョン名.pyの名前でキャッシュする」が誤りです。
まず、「__python_cache__」ではなく、「__pycache__」ディレクトリだし「module.バージョン名.py」ではなく「module.バージョン名.pyc」です。
4つ目は、「グローバル変数「__modname__」」が誤りで、グローバル変数「__name__」です。


問26.
あるディレクトリを、パッケージを含むものとして扱わせるために必要とされるファイルは次のうちどれか。


・__sub__.py
・__dir__.py
・__init__.py
・__package__.py
・__directory__.py

解説:
3つ目の選択肢が正解です。
他のやつもちょっと触れておくと、
「__sub__.py」→「__sub__」は引き算を行う時に内部で呼び出される関数です。
「__dir__.py」→「__dir__」は指定したモジュールなどに含まれるメソッドやプロパティを返す関数です。
「__package__.py」→「__package__」はパッケージ名を返す関数です。
「__directory__.py」→dir関数の「dir」が「directory」なので、そのひっかけのためだけのものですね。


問27.
入出力に関する次の記述のうち、誤っているものはどれか。


・文字列オブジェクトのrjust()メソッドは、文字列の左側にスペースを追加して、指定の幅に右揃えするものである。
・文字列オブジェクトのzfillメソッドは、プラスとマイナスの記号も含めて指定文字数となるように、数字の文字列の左側をゼロでパディングするものである。
・標準モジュールjsonは、Pythonのデータ階層構造を取って文字列表現にコンバートすることができる。このプロセスを「シリアライズ」という。シリアライズで文字列表現されたオブジェクトは、「デシリアライズ」という。
・open()はファイルオブジェクトを返す関数である。open関数は第1引数にファイル名を、第2引数にモードを与えて使う。モードはファイルを読み込み専用で開くなら「r+」、書き出し専用なら「w」、追加なら「r」、読み書き療養なら「a」を指定する。
・値を書き出す方法には、print()関数やwriteメソッドなどがある。出力のフォーマット方法には、文字列スライシングと連結操作で行う方法や、formatメソッドを利用する方法などがある。

解説:
3つ目の選択肢の「モードはファイルを読み込み専用で開くなら「r+」、書き出し専用なら「w」、追加なら「r」、読み書き療養なら「a」を指定」のところが誤りです。
正しくは「モードはファイルを読み込み専用で開くなら「r」、書き出し専用なら「w」、追加なら「a」、読み書き両用なら「r+」を指定」が正しいです。


問28.
次のスクリプトを実行して「整数a:」に「3」、「整数b:」に「b」を入力した場合の正しい結果はどれか。なお選択肢中の「, 」は改行に読み替えること。


try:
    int_a = int(input('整数a:'))
    int_b = int(input('整数b:'))
    print(int_a ** 2)
    print((int_a ** 2) / int_b)
except(ZeroDivisionError) :
    print('C')
except(ValueError) :
    print('D')
except:
    print('E')
else:
    print('F')
finally:
    print('G')

・D, E, F, G
・D, F, G
・E, F, G
・D, G
・G

解説:
例外処理に関しての問題です。
まず例外処理について理解しましょう。
この場合の「例外」とはエラーのことだと思ってください。
つまり例外処理とは、コードが正しく完了せず、エラーになった場合に別の処理を行わせる書き方です。
「try」の中に実行する処理を書き、もし、エラーが起こったら「except」の中の処理を実行します。
exceptは「except(ZeroDivisionError) :」のようにエラー名を指定すれば、特定のエラーの場合だけ、動く処理が書けます。
「except:」のようにエラー名を指定しない場合は、ワイルドカード指定といって、すべてのエラーで動きます。
また、except文は、どれかが実行されると、他のexcept文はスルーされます。
もし、tryのコードがエラーが起こらず完了した場合は「else」の中を実行します。
(if文のelseとは、ちょっと感覚が違うので注意です)
「finally」はエラーが起きても、起きなくても最後に必ず実行されます。
例外処理に関しては以上です。

次に、この問題のプログラムは、今までの問題と違って「キーボードなどからの入力を受付けて、それによって処理が変わる」というプログラムです。
具体的には「input」関数の部分なのですが、これがキーボードなどからの入力を受付ける部分です。
イメージとしては、パソコン画面に2つの入力欄が出てきて、そこに「整数a」と「整数b」が書かれているような感じです。
「あなたの年齢を入力してください」や「あなたの名前を入力してください」みたいな入力欄のことです。
問題文には「「整数a:」に「3」、「整数b:」に「b」を入力した場合」と書いてあるので、2つの入力欄に「3」と「b」が入力された場合の例外処理、ということですね。
input関数は入力された値に変化しますので、以下のように読み替えてください。

int_a = int(input('整数a:'))
int_b = int(input('整数b:'))

の部分を、

int_a = int('3')
int_b = int('b')

このようになります。
(inputで受け取った入力データは必ず文字列型になります)

次にint関数がint型に変換しようとします。
「int('3')」に関しては問題なく「'3'」→「3」に変換できますが、「int('b')」は数字に変換することができません。
そのため例外(エラー)が発生します。
この場合に発生する例外名は「ValueError」になります。
ValueErrorは関数に指定する値がおかしい場合に発生します。
以上のことから、出力結果は4つ目の選択肢になります。

余談:
今回は使われていませんが「ZeroDivisionError」という例外は「0で除算」が行われた時のエラーです。
「10 / 0」みたいな式ですね。
この場合に「ZeroDivisionError」が発生します。
他にも例外はたくさんありますが、「ValueError」「ZeroDivisionError」などはかなりよく見かけるエラーなので、そういったメジャーなエラーは理解しておきましょう。


問29.
エラーと例外に関する次の記述のうち誤っているものはどれか。


・raise文を用いることで、指定の例外を意図的に発生させることができる。raiseの引数は送出する例外を示すものであり、例外インスタンスでも、Exceptionクラスの派生クラスであるクラス(例外クラス)でも構わない。
・発生した例外に値が付随することもあり、これを例外の引数と呼ぶ。except 節では、例外名の後に変数を指定することができる。この変数には例外インスタンスが結び付けられており、例外インスタンスには「__str__()」が定義してある。
・[Ctrl]+[c]キーなどでユーザーがプログラムに割り込みをかけると、KeyboardInterrupt例外が送出される。
・パーサ(構文解釈器)は違反のある行を表示し、最初にエラーが検知された点に下線が引かれる。エラーは矢印より前のトークンが原因である。
・例外のほとんどはプログラムでは処理されず、その結果はエラーメッセージにあらわれる。エラーメッセージの最終行には、NameError、TypeErrorなど例外の型が記されている。

解説:
4つ目の選択肢の「最初にエラーが検知された点に下線が引かれる」が間違いです。
正しくは「最初にエラーが検知された点に小さな矢印がつけられる」です。
その後の文章には「エラーは矢印より前のトークンが原因である」と、ちゃんと「矢印」と書かれていますので、妙な問題ですね笑


問30.
次のコードを実行した場合には適切な方法で、あるクリーンアップがなされる。具体的にはどのような処理がなされているか。


with open("file.txt") as f:
    for line in f:
        print(line, end="")

・file.closed()
・file.close()
・file.clean()
・f.closed()
・f.close()

解説:
ファイルの読み書きに関しての問題です。
問題としては簡単なのですが、こういうプログラムは、やったことがある人しか中々わからないですよね。
ポイントはいくつかあります。
まず「with」です。
これは「開始時と終了時に、何かしらの処理が必ず必要になる場合」に書く書き方です。
まぁほとんどの場合ファイルの読み書きの場合に使われます。
「ファイルの読み書き」って、どんな時の話なのか?というと、例えば「ゲームのセーブやロード」です。
セーブすると、そこまで進んだデータが「セーブデータのファイル」に書き込まれます。
そして、次に続きからプレイする時に「セーブデータのファイル」を読み込んで(ロード)して、続きからプレイできるわけです。

この「ファイルの読み書き」には必ず3ステップが必要です。

1、前処理:ファイルを開く
→1行目の「open("file.txt") as f:」部分が、このファイルを開く操作になります。
(file.txtという名前のファイルを開いています)
「ファイルを開く」とは、より正確にいうと「ファイルオブジェクト」を作ることです。
この時「f」がフォイルオブジェクトの名前となり、ファイルオブジェクトそのものになります。

2、本処理:ファイルに書き込む(セーブ)、またはファイルを読む(ロード)など、
→2行目、3行目の部分になります。
問題のコードでは、ファイルオブジェクト(f)から要素を取り出し、それを出力させているだけなので「読み」だけしかしてません。
(ファイル自体の中身は記載がないので、不明です)

3、後処理:ファイルを閉じる
→この部分はwithを使っていれば不要なので、書かれていません。
もしwithを使っていない場合は、ファイルを閉じて置かないと、無駄なメモリを食うし、セーブが正しく出来ないなど、何かしらのバグが発生する恐れがあります。

この3ステップのうち、「後処理」のことを「クリーンアップ」とも言い、with文を使っていればこのクリーンアップ処理を書かなくても済みます。
もし使っていない場合は「close()」メソッドで閉じることができます。
つまり、5つ目の「f.close()」が正解です。