リストの全要素が一定の条件を満たしているかどうかを判断したい? そんなコードをfor文で記述していませんか? そういうときには、アレを使うのがよいですよ!
以下はリストsomedataの要素が全て10より大きければ変数all_above_10の値をTrueに、1つでも10以下の要素があればFalseにするコードだ。要素が10以下かどうかとall_above_10の値のセットは(主に)for文の中で行っている。このコードは問題なく動作するが、Python的な書き方にはなっていない。Pythonの組み込み関数を使うと、これは1行で書ける。そうしたコードはどんなものになるかを考えてみよう。
somedata = [17, 13, 18, 20, 10, 3, 7, 4, 8, 16]
all_above_10 = True
for item in somedata:
if item <= 10:
all_above_10 = False
break
print(all_above_10) # False
どうもHPかわさきです。
今回も問題を考えているときには「答えは1つ!」と思ったんですが、実際はそうではなくって、記事中だけでも2つの答えを紹介することになってしまいました。他にも正解は幾らでもありそうです。思い付いた方は教えてくださいね。
正解のコード例を以下に示します。
somedata = [17, 13, 18, 20, 10, 3, 7, 4, 8, 16]
all_above_10 = all(item > 10 for item in somedata)
print(all_above_10) # False
Pythonに組み込みのall関数を使うことで、問題文のコードにあったfor文を1行で書けるようになります。なお、上に示したall関数には、全ての要素が10より大きいかどうかを判定するジェネレータ式を渡しています(リスト内包表記を渡しても構わないでしょう)。
なお、any関数を使っても次のように1行で記述可能です。この書き方ももちろん正解です。2つのコードについて後で考えてみることにしましょう。
somedata = [17, 13, 18, 20, 10, 3, 7, 4, 8, 16]
all_above_10 = not any(item <= 10 for item in somedata)
print(all_above_10) # False
Pythonに組み込みのall関数は引数として反復可能オブジェクトを受け取ります。受け取った反復可能オブジェクトの全ての要素が真と評価されるのであれば、all関数はTrueを返します。一つでも偽と評価されるものが含まれていれば、Falseを返します(要素がなければTrueを返します)。
以下に簡単な例を示します。
mylist = [True, 'deep', 42]
result = all(mylist)
print(result) # True
mylist = [True, 0, 'deep']
result = all(mylist)
print(result) # False
最初の例ではmylistの要素であるTrue/'deep'/42は全て、Pythonでは真と評価される値です。そのため、all関数はTrueを返します。次の例では、mylistの要素のうち、Trueと'deep'は真と評価されますが、0はPythonでは偽と評価されるので、all関数はFalseを返します。
さて、問題文のコードは次のようなものでした。
somedata = [17, 13, 18, 20, 10, 3, 7, 4, 8, 16]
all_above_10 = True
for item in somedata:
if item <= 10:
all_above_10 = False
break
print(all_above_10) # False
このコードのfor文(とif文)が意味するのは「その値が10以下の要素が1つでもあればall_above_10の値はFalseになる」ですが、これは「全ての要素の値が10より大きければall_above_10の値はTrueになる」と読み替えられることに注意してください(条件の反転)。
この「全ての要素が○○であれば」を評価するのに、先に紹介したall関数が役立つというわけです。既に述べたように、all関数は全ての要素が真となる反復可能オブジェクトを受け取るとTrueを返し、そうでなければFalseを返します。ここで次のように内包表記を使ってリストを作成してみましょう(その要素の値はTrue/Falseになります)。
tmp = [item > 10 for item in somedata]
これはsomedataの要素が10より大きければ対応する要素をTrueに、そうでなければFalseにする内包表記です。そして、見栄えが良くなるように以下のコードで書式を指定して、somedataとtmpの値を表示してみます。
print('[' + ', '.join(f'{x:5}' for x in somedata) + ']')
print('[' + ', '.join(f'{str(x):>5}' for x in tmp) + ']')
これを実行すると、結果は次のようになります。
見ての通り、somedataには10以下の要素も含まれているので、tmpにはTrueとFalseが含まれます。そして、これをall関数に渡せば、もちろんその結果はFalseになります。
all_above_10 = all(tmp)
print(all_above_10) # False
上ではリスト内包表記を使いましたが、正解例のコードではジェネレータ式を使っています。前回のクイズではメモリ効率を考えると関数の引数としてリスト内包表記を渡すよりもジェネレータ式を渡した方がよいかも? という話をしました。今回に関していえば、要素数が少ないのでジェネレータ式を使うほどのこともないのですが、ここでは前回のクイズの原稿を書きながら勉強したことをそのままアウトプットしている痛い筆者がいるとでも思ってください。
以上をまとめたのが、以下に再掲するコード例です。
somedata = [17, 13, 18, 20, 10, 3, 7, 4, 8, 16]
all_above_10 = all(item > 10 for item in somedata)
print(all_above_10) # False
では、もう一つの正解例のコードも見ておきましょう。
somedata = [17, 13, 18, 20, 10, 3, 7, 4, 8, 16]
all_above_10 = not any(item <= 10 for item in somedata)
print(all_above_10) # False
ここで使用しているany関数はall関数と対になるものですが、こちらは引数に受け取った反復可能オブジェクトの要素に真と評価されるものが一つでもあればTrueを、全てが偽と評価されるのであればFalseを返します(要素がなければFalseを返します)。
any関数を使うときには注意しなければならない点があります。any関数に渡す反復可能オブジェクトの要素に1つでも真と評価されるものがあれば、この関数はTrueを返すことです。つまり、all関数を使うコードと同様な「item > 10」を条件とするリスト内包表記やジェネレータ式を渡すと、要素のどれか1つでもこの条件を満たせばany関数の戻り値はTrueになってしまうのです。
ではどうすればよいでしょう。実は「全ての要素の値が10以下であることをチェックする」がその答えです。ちょっと試してみましょう。
testdata = [11, 12, 13, 14, 15]
tmp = [item <= 10 for item in testdata]
print('[' + ', '.join(f'{x:5}' for x in testdata) + ']')
print('[' + ', '.join(f'{str(x):>5}' for x in tmp) + ']')
このコードではtestdataの要素の値は全て10より大きいこと、それから上で述べた「全ての要素の値が10以下であることをチェックする」に合わせて、リスト内包表記では「item <= 10」を記述している点に注目してください。これを実行すると、次のような結果になります。
全ての要素の値が10より大きいので、もちろん得られるリストの要素の値は全てFalseになります。any関数は受け取った反復可能オブジェクトの要素の値が全て偽(False)であれば、Falseを返します。そこで、not演算子でFalseをTrueに反転させることで、「全ての要素の値が10より大きいときにTrue」という結果を得られます。つまり、「全ての要素の値が10より大きい」というのは「全ての要素の値が10以下であることの否定」として記述できるということです。
こうしたことから、any関数を使った場合は次のようなコードになるわけです。
somedata = [17, 13, 18, 20, 10, 3, 7, 4, 8, 16]
all_above_10 = not any(item <= 10 for item in somedata)
print(all_above_10) # False
条件がall関数のものと逆になっていることと、not演算子で真偽値を反転させていることに注目しましょう。
all関数を使ったコードとany関数を使ったコードのどちらが読みやすいかを考えると、今回についてはall関数を使ったコードの方が素直で読みやすいと筆者は感じています。全要素が特定の条件を満たすという場合はそうなるでしょう。でも、任意の要素が条件を満たすというときには、どちらを使えばよいかはまた変わってくるはずです。常にall関数を使えばよいわけではないことは覚えておきましょう。
all関数とany関数については以下の記事でも紹介しています。よろしかったら、読んでみてくださいね。
ホントはall関数とany関数の短絡評価の話とかも書きたかったんですが、ちょっと長くなったので今回はこれでおしまいとします。
Copyright© Digital Advantage Corp. All Rights Reserved.