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

2933

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

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



問11.
次のような結果を得たい場合、コードの【A】に入る適切なものはどれか。


[ 実行結果 ]
1,-2,-5,-8,

[ コード ]
for i in range(【A】):
    print(i, end=",")

・1, -8, -3
・1, -8, -2
・1, -10, -3
・4, -8, -2
・4, -10, -2

解説:
range関数の使い方の問題ですね。
range関数は色々な指定が出来ます。
まず基本となる「range(5, 10)」みたいに()の中の値(引数)が2つの場合を理解しましょう。
引数が2つの場合は「range(△から, □まで)」という意味になるので「range(5, 10)」なら「5から10まで(10は含めない)」という意味です。
つまり「5 6 7 8 9」ですね。これが基本です。
次に「range(10)」みたいに引数が1つだけの場合、「range(0から〜□まで)」という意味になり「0 1 2 3 4 5 6 7 8 9」という意味です。
最後に今回の選択肢のように3つの引数の場合です。
3つ引数がある場合は「range(△から, □まで、☆ごとに)」という意味になります。
たとえば「range(0, 10, 2)」なら「0 2 4 6 8」になります。
負の数も使えるので「range(0, -10, -2)」なら「0 -2 -4 -6 -8」です。
今回は、「1,-2,-5,-8,」と出力させたいので、「1から、-10まで、-3ずつ」の上から3つ目の選択肢が正解になります。
ちなみに、一番上の「1から、 -8まで、 -3ずつ」だと-8自体は含まれないため「1,-2,-5,」までしか出力されません。

余談:
Pythonには「〇〇回繰り返す」という書き方がないので、for文とrange関数で「〇〇回繰り返す」を作ることが普通です。
例えば「for num in range(1000):」で、1000回繰り返すという意味になります。
range関数はかなり使いますので、使い方をしっかり覚えておきましょう。


問12.
次の結果を得たい場合、コードの2行目以降を代替するものとして正しいものはどれか。なお各選択肢の次の行には「 print(i, Zen[i]) 」が記述されるものとする。


[ 実行結果 ]
0 Simple
1 is
2 better
3 than
4 complex

[コード]
Zen = ['Simple','is','better','than','complex']
for i, v in enumerate(Zen):
    print(i, v)

・for i,v in range(len(Zen)):
・for i,v in range(Zen[0:5]):
・for i in range(Zen[:]):
・for i in range(Zen[0:5]):
・for i in range(len(Zen)):


解説:
なんかわかりにくい問題ですが、enumerate(イニューマレイト?個人的にはイーナムレイト)関数の理解が問われている問題です。
リストなどの内容を表示させたい時に、番号とセットで表示させたいケースがよくあるので、そういう時にインデックス番号も一緒に取り出してくれるのがenumerateです。
たとえば「for i, v in enumerate(Zen):」だと「i」に番号が入り、「v」に要素が入ります。
そのため、「print(i, v)」とすると「0 Simple」が表示されます。
つまりenumerate関数は値を2つずつ同時に取り出せます。ここポイントです。
これと同じように作るには、どうしたら良いか?という問題ですが、こういう処理は結構多いですし、ある程度経験ある人ならとても簡単な問題ですね。
問題文に「なお各選択肢の次の行には「 print(i, Zen[i]) 」が記述されるものとする。」と書いてありますので、これ込みで考えなければいけません。
そこで「i」に注目します。「iにどんな値が入っていたら良いか?」を考えます。
例えば最初の繰り返しで「0 Simple」と出力させるためには「 print(0, Zen[0]) 」にすれば良いので、「iには0が入っていれば良い」という事がわかります。
次の繰り返しでは「1 is」を出力させたいので、「 print(1, Zen[1]) 」→「Iには1が入っていれば良い」ということがわかります。
つまり「iは0から4までのイテラブルオブジェクト」であれば良い事がわかります。

選択肢を眺めて見ると、すべてrange関数が使われています。
range関数は()に数字を指定しないといけませんでした。
でも選択肢の中で、数字を指定しているのは1つ目と、5つ目の「len(Zen)」だけです。
len()はそのリストの要素数を返してくれる関数でしたね。
つまり今回の場合は「range(5)」と同じ意味になります。
2つ目、3つ目、4つ目の選択肢はどれもリストZenの要素をスライスしていますので、返り値は数字ではなくリストです。
この時点で選択肢は1つ目か5つ目に絞られます。

次に、ポイントになるのがrange関数からは1つずつしか値を取り出せない、ということです。
enumerate関数では2つずつに取り出せましたが、range関数は1つずつです。
複数の値が入っていても、1回の繰り返しごとに1つずつ取り出されます。
1つ目の選択肢は「for i,v in」というように、2つの受け取るための変数を用意しています。
この場合、vに入れる値がないので、エラーになります。
つまり選択肢の1つ目も間違いとわかります。
「for i in range(len(Zen)):」が正解です。


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


i = 1
i = 2

def f(arg = 3):
    i = 4
    i = 5
    print(arg)

f(i)

・1
・2
・3
・4
・5

解説:
変数のスコープに関しての問題ですかね。
「f(i)」を実行した時に、このiに何が代入されているか?がポイントで、関数の中で「i = 5」などありますが、
関数の中の変数iと関数の外の変数Iは異なる変数として扱われますので「f(i)」のiには2が代入されています。
そのため関数fの引数argには2が代入され、出力結果も2になります。
ちなみに「(arg = 3)」の部分は引数のデフォルト値を設定しています。
(デフォルト引数とも言います)
デフォルト値は引数が指定されずにf関数が呼び出された時にargに代入される値ですが、今回は引数の値が指定されて呼び出されていますので、特に関係しません。



問14.
次のコードに関し、【A】の行の出力として正しいものはどれか。


def culc(a, b, squares=[], cubes=[]):
    squares.append(a ** 2)
    cubes.append(b ** 3)
    return squares, cubes

print(culc(4, 1))
print(culc(3, 2))
print(culc(2, 3)) 【A】
print(culc(1, 4))


・([4], [27])
・([16,1], [9, 8], [4, 27])
・([1, 8, 27], [16, 9, 4])
・([8, 6, 4], [3, 6, 9])
・([16, 9, 4], [1, 8, 27])


解説:
関数と引数に関しての問題って感じですかね。
printを4回実行するコードが書かれていますが、問題としては3回目の出力時点での出力結果が問われています。
(じゃあ4回目のprintいらんでしょって気がしますが笑)
関数culcには4つの引数がありますが、呼び出す時には毎回2つの値しか与えていません。
この場合、引数aとbにだけ値が渡されます。「culc(4, 1)」だとaが4、bは1という感じです。
残りの引数squaresとcubesはデフォルト値が設定されているので、エラーにはならず、デフォルト値が代入されます。
どちらも「[]」という記号だけが代入されていますが、pythonにおいての「[]」はリストを作成する記号のため、
「中身が空っぽのリスト」が代入されることになります。
関数の中では、そのリストに「a ** 2」などの結果を追加しています。
(「append」は末尾に追加するメソッドです)
最終的にreturnで、関数を呼び出した場所にそれぞれのリストが返されていますので、2つのリストが出力されます。

ポイントとしては、「リストの中身は追加されて行くのか?」それとも「関数を呼び出す度に空っぽのリストになるのか?」というところです。
関数は定義の時の1回のみしか、引数の初期化(デフォルト値の代入)が行われません。
関数の呼び出しの度に「squares=[]」がされるのではなく、これは1度だけ行われるということです。
つまり「リストの中身は追加されていく」のです。
以上のことから3度目のprintの時には、それぞれのリストには3つの要素が入っているはずなので、1つ目と2つ目の選択肢はありえません。
1つ目は要素が1つずつしかない、2つ目は3つのリストになっているからです。
後は普通に計算するだけです。
1回目の「culc(4, 1)」では「squares.append(a ** 2)」で16が追加され、「cubes.append(b ** 3)」で1が代入されます。
(**は累乗の演算子です)
選択肢の中で、それぞれのリストの先頭に16と1があるのは5つ目の選択肢だけなので、これが正解ですね。


問15.
次の関数を呼び出す際に、引数の指定として正しいものはどれか。


def location(city, state='NewYork', country='USA'):
    print("I live in", country, ".")
    print("My company is located in",city,",",state,".")

・location(state='Tokyo', 'chiyoda')
・location('San Francisco', country='USA', state='California')
・location('Jakarta', 'Cikini', latitude = '-6.1753942')
・location('Singapore', city='Marina Boulevard')
・location()

解説:
これも関数と引数の問題ですね。
ポイントとなるのは、関数を呼び出す時の、値の指定方法です。
関数locationには「city, state, country」という3つの引数があり、「city」だけはデフォルト値が設定されていません。
つまり、必ず最低1つは値を指定しないといけない、ということがわかります。
選択肢をさらっと見ると、5つ目の選択肢は一つも値を設定していないので誤りとわかります。
残りのポイントとしては「キーワード引数」です。
キーワード引数とは、たとえば1つ目の選択肢「location(state='Tokyo', 'chiyoda')」の「state='Tokyo'」のところです。
関数を呼び出す時に、どの引数に与える値なのか?を指定する書き方で、「state='Tokyo'」は「引数stateに'Tokyo'を入れる」という意味になります。
※デフォルト引数とごっちゃになりがちなので、注意してください。キーワード引数は関数を呼び出す時の指定方法です。
このキーワード引数とは逆に、「location('Tokyo', 'chiyoda')」みたいに値だけを書く方法のことを「位置引数」と言います。
大事なポイントとしては「キーワード引数は、必ず位置引数より後(右側)に書かないといけない」ということです。
つまり、1つ目の選択肢の「location(state='Tokyo', 'chiyoda')」はキーワード引数が、位置引数より前に書かれていますので、エラーになります。
ただし、キーワード引数同士の位置は自由に配置できます。
また、存在しない引数名をキーワード引数として指定してもエラーです。
3つ目の選択肢の「 latitude」という引数は存在しないので、これもエラーです。
存在する引数であっても、2回指定するのもエラーです。
4つ目の「location('Singapore', city='Marina Boulevard')」は最初に位置引数として「'Singapore'」が指定されています。
この時点で引数cityには'Singapore'が渡されました。
その後に「city='Marina Boulevard'」で、また引数cityを指定しているので、これもエラーになります。
もし、「location(city='Singapore', city='Marina Boulevard')」というように2回ともキーワード引数で指定したとしてもエラーです。
最終的に2つ目の「location('San Francisco', country='USA', state='California')」だけになりましたね。これが正解です。
関数定義では「city, state, country」という引数の並びなのに、「country='USA', state='California')」というように順番が逆になっていますが、
キーワード引数同士の位置に制限はないので、これは問題ありません。

余談:
キーワード引数とか、デフォルト引数とかって、見た目も同じで名前も似てるからすごく分かりづらいですよね。
そもそも引数には2種類あって、関数の定義側で設定している引数を「仮引数」で、関数を呼び出す時に指定する値を「実引数」と呼びます。
関数の定義時点では値が決まってないので「仮引数」、呼び出す時に決定されるので「実引数」ってことですね。
ただ、大体の本などには定義側の「仮引数」のことを「引数」と呼んで、呼び出し側の「実引数」は「値」とだけ言うことが多いです。
別にこれは間違っているわけではないですが、今回の「キーワード引数」が出てくると意味不明になりがちです。
だって、今まで「値」としていた部分に「キーワード引数」という「引数」が出てくるんですから。
だから「キーワード引数」って呼び方変えた方が良いと思ってます。
「キーワード指定記法」とか「キーワード実引数」とかなら、まだわかりやすいと思うんですがどうでしょう?
プログラミングの世界はこのような「その名前変じゃね?」や「人によって意味合いが異なる言葉」が大量にあります。
カチッと決まっているように思えて、実はすごくあやふやな業界なのです。


問16.
次の結果を得たい場合に、コード【A】に入るものとして適切なものはどれか。


[ 実行結果 ]
[(2, 'a'), (3, 'b'), (1, 'c')]

[ コード ]
pairs = [(3, 'b'),(1, 'c'),(2, 'a')]
pairs.sort(key =【A】)
print(pairs)

・lambda arg : arg[0]
・lambda arg[0] : arg
・lambda arg[1] : arg
・lambda arg : arg[1]
・lambda arg : arg

解説:
sortメソッドと、ラムダ式に関する問題ですね。
かなり実務的なコードですが、なれていないと理解しにくい処理です。
先に正解を言っておくと4つ目の「lambda arg : arg[1]」です。
pairs.sort(key = lambda arg : arg[1])とすると
[(3, 'b'),(1, 'c'),(2, 'a')]

[(2, 'a'), (3, 'b'), (1, 'c')]
と並び替えられます。

このコードはPythonで「リスト内のタプルの並び替え」によく使われている方法です。
でも、なんでこうなるのか理解していない人も多いようなコードです。
ここでは、詳細な動きまであえて踏み込んで解説したいと思います。

まずsortメソッドはリストをソート(並び替え)するメソッドです。
「リスト名.sort()」と書くと、リスト内を昇順(小さい数字から順番に)に並べてくれます。
リストの要素が文字列であれば「'a','b','c'」みたいに並べてくれます。
(数字と文字列などデータ型が違うとエラーです)
今回の場合は「pairs = [(3, 'b'),(1, 'c'),(2, 'a')]」なので、リストの要素はタプルになっています。
このまま、普通に「pairs.sort()」とすると、「[(1, 'c'), (2, 'a'), (3, 'b')]」という結果が来ます。
つまり、タプルの場合は、そのタプルの中の要素の、数字データを元にソートします。
今回は「[(2, 'a'), (3, 'b'), (1, 'c')]」という結果にしたい(a, b, cの順にしたい)ので、つまりタプルの中の文字列でのソートをしたいということになります。
そのような時に使うのが「.sort(key = ○○)」という書き方です。

このkeyに指定するのは関数になります。
関数を指定すると、ソート対象のリストから、要素を1つずつ取り出して指定した関数に与えた上で、その関数を実行します。
そして、その返り値をソートのkey(元)とします。
実際に試してみましょう。

理解しやすくするために、以下のようなプログラムで考えます。

def x(a):
    return a * -1

numbers = [3,1,2]
numbers.sort(key=x)
print(numbers)


関数xは実行時に指定した値に-1をかけて返します。
−1をかけるということは、プラスはマイナスに、マイナスはプラスに、と符号だけを反転させるということです。
これによって「大きい数字ほど小さい数字に、小さい数字ほど大きい数字に変わる」ということです。
(この関数は例えとして作っただけなので、特に意味はありません)

numbersというリストには[3,1,2]という並びで要素が並んでいます。
これを「numbers.sort(key=x)」とした場合の動きを1つずつ確認しましょう。
「key=」には関数を指定するので、今回は自分で作った関数xを指定しています。
この関数xは引数があるので「x(1)」みたいに、なにかしらの値を指定しますが、keyの指定の際は「関数名だけ」じゃないといけません。
末尾の()も付けてはいけません。
すると、numbersリストの要素を一つ取り出して、勝手に引数aに入れて、関数xを実行してくれます。
「numbers.sort(key=x(3))」みたいな感じで実行してくれます。
関数xでは、aに-1が掛けられた数がreturnで返されますので、-3が返ってきて、これがソート対象の1つ目になります。
同じように今度は、numbersリストの要素の2つ目(つまり1)が取り出され、勝手に引数aに入れて、関数xを実行してくれます。
その結果-1が返ってくるので、これがソート対象の2つ目になります。
もう一度同じことをnumbersリストの要素の3つ目(つまり2)に行うので、最終的に「-3, -1, -2」というソート対象になります。
つまり「-3, -2, -1」に並び替えられます。
ただ、この関数xで変更された数字は、ソート対象として利用されるだけなので、実際の要素の数字は変わらず、順番だけが変わって[3, 2, 1]というように出力されることになります。
これがkeyの動きです。
(上記の理由から、keyに指定する関数は、必ず引数と返り値がないといけません)

次にラムダ式です。
「lambda〜」から始まる書き方のことで、無名関数(名前のない関数)を作る時に使われます。
普通の関数は「def」で関数を定義して、その後に呼び出して使いますが、1度しか使わないような関数だと、いちいち名前をつけたりするのが面倒な場合があります。
そういう時にlambdaを使います。
lambdaは基本的に「lambda 引数 : 処理(式)」の構造になっています。
処理には「a * -1」みたいな感じで書くと、その評価(結果)が返されます。
つまり「return a * -1」と書いてある感じです。
例えば、さきほど例として作った関数xをラムダ式で書くと「lambda a: a * -1」と書けます。
関数を定義しない分、全体のコードも、とても簡潔になります。

numbers = [3,1,2]
numbers.sort(key=lambda a: a * -1)
print(numbers)

ここまでOKでしょうか?
これでやっと、今回の問題の答え「lambda arg : arg[1]」が理解できます。
「arg」はただの引数で、名前自体に特に意味はありません。
問題文のリストpairsの中身は [(3, 'b'),(1, 'c'),(2, 'a')]」ですので、「pairs.sort(key = 」で最初に取り出される要素は(3, 'b')というタプルです。
これが「lambda arg : arg[1]」の引数argに渡されます。
つまり「lambda (3, 'b') : arg[1]」です。
そして、右側の処理が行われます。といっても「arg[1]」しか書いてありません。
つまり「lambda (3, 'b') : 'b'」ということです。
さて、先程も言いましたがこの処理の部分にはreturnが勝手についています。
なので「lambda (3, 'b') : return 'b'」です。
これで1つ目のソート対象が決まります。
同じように、次は(1, 'c')がlambdaに渡されて、「'c'」だけが返ってきます。
これで2つ目のソート対象も決まりました。
最後に(2, 'a')もlambdaに渡されて、「'a'」だけが返ってきます。
これでソート対象として['b', 'c', 'a']となりましたので、これを文字列の昇順でソートされます。
ただし、リストpairsの要素自体は変わらず、順番だけが変わりますので、[(2, 'a'), (3, 'b'), (1, 'c')]という結果になります。

余談:
ラムダ式は「x = lambda a: a * -1」みたいに、変数に代入しておけば、何度も使うことができます。
でも、基本的に「何度も使う可能性のある関数はdefでちゃんと定義するべき」となっていますので、変数に入れる方法は推奨されていません。
そんなこともあって、Pyhtonでlambdaを使う機会はsortやmapなど、引数に関数を指定する一部のメソッドの中だけがほとんどです。


問17.
次の記述のうち、誤っているものはどれか。


・PEP8では、クラスや関数には一貫した命名を行うべきであり、クラスには「CamelCase」を、関数やメソッドには「lower_case_with_underscores」を使うべきとされている。
・トリプルクート「"""」で関数内に記述されたdocstringの内容は、関数の__doc__属性に文字列として格納され、help関数でドキュメントとして表示させることができる。
・関数注釈(アノテーション)は関数の__annotations__属性にリストとして格納され、注釈の内容によっては関数のほかの部分に影響を与えることもある。
・PEP 8では、識別子に非ASCIIキャラクタを使うべきでないとされている。ASCII 範囲内で識別子として有効な文字は、大文字と小文字のアルファベット、アンダースコア、0 から 9の数字である。なお先頭文字は数字以外でなければならない。
・docstringの1行目は、常にオブジェクトの目的の短く簡潔な要約を記述し、2行目以降がある場合、2行目は空行としてようやくと他の記述を視覚的に分離すべきである。

解説:
どんどん解説が長くなっているので簡潔に。
誤っている部分は3つ目の選択肢の「注釈の内容によっては関数のほかの部分に影響を与えることもある」という部分。
アノテーションは関数自体に一切影響は与えない。

余談:
アノテーション(注釈)とは「この変数は○○やで」というように、何か解説を入れるようなものです。
書き方としては「def x(a: int)」(引数の場合)みたいに書くと、「この引数はint型やで」という意味になります。
普通の変数の場合は「x: str = 'こんにちは'」(strは文字列型)みたいな感じで、「この変数はstr型やで」となります。
似たようなものにコメントがあり、同じように「ここは○○である」とコンピュータではなく、人間に向けたメッセージを書く方法があるが、アノテーションはコメントよりも解説の意味合いが強く、
基本的には型の指定のために使われます。
例えば、Pythonでは変数や引数を型宣言しないため、他人の書いた関数などは「この引数には数字を入れたら良いのか?それとも文字列なのか?」と、わざわざ関数の中身を見て確認しないといけないことがありますので、
その手間を減らすためのものと思ってください。
大事なのは、アノテーションは強制できるものではないため「x: str = 100」と書いても特に問題なく変数にint型の100は入ってしまいます。
引数の場合も同じで、関数自体に何か影響を与えることはありません。

ただ、Pythonではいろんなところで「:」(コロン)が使われるので、個人的にはアノテーションにまでコロンを使うのは見にくく感じてしまいます。
if文やfor文など複合文の末尾にもコロン、ラムダ式でもコロン、辞書型の初期化でもコロン、スライスでもコロンです。
なので型アノテーションは何か別の書き方にしてほしかったのが本音です。


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


a = [1,3,4,6,3,5]
a.insert(3, -1)
a.pop(4)
a.remove(3)
print(a)

・[1, 4, -1, 5]
・[1, 4, -1, 3, 5]
・[1, 4, 6, 5, 3]
・[1, 6, 3, 5, 3]
・[1, 6, 5]

解説:
リストの3種類のメソッド「insert」「pop」「remove」の確認問題ですね。
「insert」は第1引数に指定した位置へ、第2引数に指定した値を挿入します。
つまり「a.insert(3, -1)」を実行すると、[1,3,4,6,3,5]は、[1,3,4,-1,6,3,5]になります。
「pop」は指定した位置の要素を削除します。(引数を指定しなければ末尾)
つまり「a.pop(4)」を実行すると、[1,3,4,-1,6,3,5]は、[1,3,4,-1,3,5]になります。
「remove」は引数に指定した値の要素を削除します。
複数ある場合は先頭の1つだけ削除します。
つまり「a.remove(3)」を実行すると、[1,3,4,-1,3,5]は、[1,4,-1,3,5]になります。
答えは2つ目の選択肢ですね。


問19.
コードAの1行目を代替するコードBがある。コードBの【A】~【C】のうち、【A】と【B】に入るものとして正しいものはどれか。


[ コードA ]
cubes = [ a ** 3 for a in range(5)]
print(cubes)

[ コードB ]
cubes = 【A】(【B】(【C】 a: a ** 3, range(5)))

・【A】set 【B】loop
・【A】dic 【B】loop
・【A】dic 【B】map
・【A】list 【B】loop
・【A】list 【B】map

解説:
すごくわかりにくい問題文ですが、コードAとコードBが同じ結果になるようにするので、まずはコードAを見ると、
「cubes = [ a ** 3 for a in range(5)]」と書いてあります。
これは「リストの内包表記」と言って、リストの要素を簡潔に作る書き方で、普通に書くと以下の書き方と同じになります。

cubes = []
for a in range(5):
cubes.append(a ** 3)

この出力結果は[0, 1, 8, 27, 64]となります。
さて、後は「cubes = 【A】(【B】(【C】 a: a ** 3, range(5)))」の部分を埋めて、これと同じ出力結果にしなければいけませんね。
出力結果がリストになるので、【A】には「list」が入ります。
「list」は引数に与えたものをリストにしてくれます。
後は、上のコードと同じように「range(5)から取り出した数字を、3の累乗をしていく」という部分が必要です。
イテラブルオブジェクトから、一つずつ取り出して処理するといえば「map」関数というものがあります。
map関数は第1引数に関数を指定し、第2引数にイテラブルオブジェクト(リストなど)を指定して使います。
「map(関数, イテラブルオブジェクト)」という感じです。
今回のケースであればイテラブルオブジェクトには「range(5)」が入ります。
つまりこの時点で「cubes = list(map(【C】 a: a ** 3, range(5)))」という事が確定します。
なので、答えは5つ目の選択肢です。

余談:
答えは出ましたが、ついでに【C】の正体も明らかにしましょう。
map関数の第1引数には、すでに「【C】 a: a ** 3」と書かれていますね。
本来map関数の第1引数には、何か処理を行う関数を指定しますが、なにやら式自体がそのまま書かれています。
この書き方から無名関数が書かれているのがわかります。
つまり【C】 はlambdaです。
すべてまとめると、コードBは「cubes = list(map(lambda a: a ** 3, range(5)))」となるということですね。


問20.
次の実行結果を得たい場合に、コード1行目~5行目を代替するものとして正しいものはどれか。


[ 実行結果 ]
[(3, 6), (3, 5), (2, 6), (2, 5), (1, 6), (1, 5)]

[ コード ]
combs = []
for x in [3,2,1]:
    for y in [6,5]:
        if x != y:
            combs.append((x, y))

print(combs)

・combs = [a,b for a in [3, 2, 1] for b in [6,5] if a != b]
・combs = ([a,b] for a in [3, 2, 1] for b in [6,5] if a != b)
・combs = [(a,b) for a in [6,5] for b in [3, 2, 1] if a != b]
・combs = [a,b for a in [6,5] for b in [3, 2, 1] if a != b]
・combs = [(a,b) for a in [3, 2, 1] for b in [6,5] if a != b]

解説:
これも「リストの内包表記」の問題です。
今回は、2つのネスト(入れ子)されたfor文を、内包表記が書き直せという事ですね。
「リストの内包表記」のコツとしては、for文が始まる前([]の中の一番左側)と、for文が始まってからの部分は分けて考えます。
1つ目の選択肢を例にすると、「a,b」までの部分と、その右側「for a in [3, 2, 1] for b in [6,5] if a != b」とで、分けるということです。
「a,b」までの部分は、最終的にリストの要素になる部分です。
今回はタプルの要素を作りますので、(a,b)と書かないといけません。
この時点で「combs = [(a,b) for〜]は確定します。
この(a,b)は、元のプログラムでいう「append()」の部分です。

そして、右側の部分は元のコードを単純に1行にして書いていくだけです。

for x in [3,2,1]:
    for y in [6,5]:
        if x != y:

の部分は「for a in [3, 2, 1] for b in [6,5] if a != b」になります。
(変数名は変わっていますが、それ以外はそのまま1行にしているだけですね)
これで元のコードと同じ動きになります。
なので、答えは5つ目の選択肢ですね。