1. ホーム
  2. python

[解決済み] and "と "or "はブール値以外ではどのように作用するか?

2022-05-14 01:19:44

質問

私はpythonを学ぼうとしていて、いくつかのコードに出くわしました。

という文脈でした。

def fn(*args):
    return len(args) and max(args)-min(args)

何をやっているかはわかりますが、なぜpythonはこのようなことをするのでしょうか。つまり、True/Falseではなく、値を返すのでしょうか。

10 and 7-2

は5を返します。同様に、andをorに変更すると、機能が変更されます。そのため

10 or 7 - 2

10を返します。

これは合法的で信頼できるスタイルですか、それともこれには何か問題があるのでしょうか?

どのように解決するのですか?

TL;DR

まず、2つの論理演算子の挙動をまとめることから始めます。 andor . これらの慣用句は、以下の議論の基礎となります。

and

Falsyの値があれば最初の値を返し、なければ式の最後の の値を返します。

<ブロッククオート

or

真実の値があれば最初の値を、そうでなければ式の最後の の値を返す。

また、この挙動をまとめたものが ドキュメント で、特にこの表にまとめられています。

オペランドに関係なくブール値を返す唯一の演算子は not 演算子です。


真偽判定と評価

ステートメント

len(args) and max(args) - min(args)

は非常に ピトニック という簡潔な(そして間違いなく読みにくい)言い方をします。 args が空でない場合、その結果を返します。 max(args) - min(args) を返し、それ以外の場合は 0 . 一般に、これは if-else をより簡潔に表現します。例えば

exp1 and exp2

と訳すべきでしょう(大雑把ですが)。

r1 = exp1
if r1:
    r1 = exp2

あるいは、同等に

r1 = exp2 if exp1 else exp1

同様に

exp1 or exp2

と訳すべきでしょう(大雑把ですが)。

r1 = exp1
if not r1:
    r1 = exp2

あるいは、同等に

r1 = exp1 if exp1 else exp2

ここで exp1exp2 は任意のpythonオブジェクト、または何らかのオブジェクトを返す式です。論理的な andor の演算子は、真偽値を操作したり、返したりすることに限定されないことを理解しています。真偽値を持つあらゆるオブジェクトをここでテストすることができます。これには int , str , list , dict , tuple , set , NoneType およびユーザー定義オブジェクト。短絡的なルールも同様に適用されます。

しかし、真理性とは何でしょうか?

条件式で使われたときに、オブジェクトがどのように評価されるかを指します。パトリック・ハウ(@Patrick Haugh)は、真理性を次のようにうまくまとめています。 この投稿 .

<ブロッククオート

以下の値を除き、すべての値は "truthy" とみなされます。 "falsy"です。

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] - 空の list
  • {} - 空の dict
  • () - 空の tuple
  • '' - 空の str
  • b'' - 空の bytes
  • set() - 空の set
  • range のように range(0)
  • オブジェクト
    • obj.__bool__() が返す False
    • obj.__len__() リターン 0

真実の値("truthy")は if または while といったステートメントがあります。 と区別するために "truthy" と "falsy" を使用します。 bool という値で区別しています。 TrueFalse .


どのように and の働き

OPの質問をもとに、このような場合にこれらの演算子がどのように作用するかについて議論します。

という定義の関数があるとします。

def foo(*args):
    ...

ゼロ個以上の引数を持つリストにおいて、最小値と最大値の差分を返すには の差を返すにはどうしたらよいでしょうか?

最小値と最大値を見つけるのは簡単です(内蔵の関数を使いましょう!)。ここでの唯一の難点は、引数リストが空である可能性があるというコーナーケースを適切に処理することです (たとえば foo() ). のおかげで一行で両方できるようになりました。 and 演算子のおかげで一行で済みます。

def foo(*args):
     return len(args) and max(args) - min(args)

foo(1, 2, 3, 4, 5)
# 4

foo()
# 0

以降 and が使われている場合、2つ目の式も評価されなければなりません。 True . 最初の式が真であると評価された場合、返り値は 常に の結果である。 二番目の式 . 最初の式がFalsyと評価された場合、返される結果は最初の式の結果である。

上の関数では、もし foo は一つ以上の引数を受け取ります。 len(args) よりも大きい 0 (正の数)であるため、返される結果は max(args) - min(args) . 乙は、引数が渡されない場合は len(args)0 であり、ファルシーである 0 が返されます。

この関数の別の書き方は、次のようになることに注意してください。

def foo(*args):
    if not len(args):
        return 0
    
    return max(args) - min(args)

あるいは、もっと簡潔に

def foo(*args):
    return 0 if not args else max(args) - min(args)

もちろん、これらの関数は型チェックを一切行わないので、提供された入力を完全に信頼しない限りは を使用しないでください。 はこれらの構成の単純さに頼ってはいけません。


どのように or の働き

の動作を説明します。 or の動作を、同じような方法で工夫した例で説明します。

という定義の関数が与えられると

def foo(*args):
    ...

どのように foo を超える全ての数を返すために 9000 ?

私たちは or を使っています。私たちは foo としています。

def foo(*args):
     return [x for x in args if x > 9000] or 'No number over 9000!'

foo(9004, 1, 2, 500)
# [9004]

foo(1, 2, 3, 4)
# 'No number over 9000!'

foo を超える全ての数字を保持するために、リストに対してフィルタリングを行います。 9000 . もしそのような数があれば、リスト内包の結果は空でないリストで Truthy なので、それが返されます (ここでは短絡的に動作しています)。そのような数が存在しない場合、リスト内包の結果は [] であり、これはFalsyです。したがって、2番目の式が評価され(空でない文字列)、返されます。

条件式を使うと、この関数を次のように書き直すことができます。

def foo(*args):
    r = [x for x in args if x > 9000]
    if not r:
        return 'No number over 9000!' 
    
    return r

前述と同様に、この構造はエラー処理の面でより柔軟性があります。