Strange use of the "and" / "or" operator

I am trying to learn python and came across some nice and short code but it doesn't make sense

context:

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

I get what it does, but why does python do it - that is, will return a value, not True / False?

 10 and 7-2 

returns 5. Similarly, a change and / or will result in a change in functionality. So

 10 or 7 - 2 

Would return 10.

Is this a legitimate / reliable style, or are there any mistakes?

+82
operators python logical-operators short-circuiting
Oct. 30 '17 at 3:16
source share
8 answers

TL; DR

We begin by summarizing the two behaviors of the two logical operators and and or . These idioms will form the basis of our discussion below.

and

Return the first Falsy value, if any, otherwise return the last value in the expression.

or

Return the first value of Truthy, if it is, otherwise return the last value in the expression.

The behavior is also summarized in the documents , especially in this table:

enter image description here

The only operator that returns a boolean regardless of its operands is the not operator.




Grades "Truth" and "Truth"

Statement

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

This is a very pythonic concise (and perhaps less readable) way of saying "if args not empty, return the result max(args) - min(args) ", otherwise return 0 . In general, this is a more concise representation of the if-else . For example,

 exp1 and exp2 

Should (approximately) translate to:

 r1 = exp1 if not r1: r1 = exp2 

Or, equivalently,

 r1 = exp1 if exp1 else exp2 

Where exp1 and exp2 are arbitrary python objects or expressions that return some object. The key to understanding the use of logical and and or operators here is the understanding that they are not limited to working or returning logical values. Any object with a truth value can be checked here. This includes int , str , list , dict , tuple , set , NoneType and user-defined objects. Short circuit rules still apply.

But what is truthfulness?
This refers to how objects are evaluated when used in conditional expressions. @Patrick Haugh describes the truth well in this post .

All values ​​are considered "true", except for the following, which are "false":

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] - empty list
  • {} - empty dict
  • () - empty tuple
  • '' - empty str
  • b'' - empty bytes
  • set() - empty set
  • empty range e.g. range(0)
  • objects for which
    • obj.__bool__() returns False
    • obj.__len__() returns 0

A true value will satisfy the test performed by if or while if . We use "truth" and "false" to distinguish between bool True and False .




How and works

We are building on the OP question as a transition to a discussion about how these operators are in these cases.

Given a function with a definition

 def foo(*args): ... 

How to return the difference between the minimum and maximum value in the list from zero or more arguments?

Finding the minimum and maximum is easy (use the built-in functions!). The only catch here is to properly handle the corner case where the argument list may be empty (for example, call foo() ). We can do it in one line, thanks to the use of and operator:

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

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

Since and used, the second expression should also be evaluated if the first is True . Note that if the first expression evaluates to true, the return value is always the result of the second expression. If the first expression evaluates to false, then the return result is the result of the first expression.

In the above function, if foo takes one or more arguments, len(args) greater than 0 (a positive number), so the return result is max(args) - min(args) . OTOH, if no arguments are passed, len(args) is 0 which is false, and 0 returned.

Note that an alternative way to write this function is:

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

Or, more briefly,

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

If, of course, none of these functions performs any type checking, so if you do not fully trust the input provided, do not rely on the simplicity of these constructions.




How or works

I explain the work of or in a similar way with a far-fetched example.

Given a function with a definition

 def foo(*args): ... 

How would you complete foo to return all numbers over 9000 ?

We use or to handle the corner case here. We define foo as:

 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 filters the list to save all numbers over 9000 . If such numbers exist, the result of understanding the list is a non-empty list, which is Truth, so it returns (short circuit in action here). If such numbers do not exist, then the result of the comp list will be [] is Falsy. Thus, the second expression is now evaluated (non-empty string) and returned.

Using conditional expressions, we could rewrite this function as

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

As before, this structure is more flexible in terms of error handling.

+116
Oct 30 '17 at 3:28
source share

Quote from Python Docs

Note that neither and nor or limit the meaning of and type , they return to False and True , but rather return the last evaluated argument . This is sometimes useful, for example, if s is a string that should be replaced with the default value, if it is empty, the expression s or 'foo' gives the desired value.

So, here is how Python was designed to evaluate boolean expressions, and the above documentation gives us an idea of ​​why they did it.

To get a boolean, just enter it.

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

Why?

Short circuit.

For example:

 2 and 3 # Returns 3 because 2 is Truthy so it has to check 3 too 0 and 3 # Returns 0 because 0 is Falsey and there no need to check 3 at all 

The same thing happens for or , that is, it will return an expression that is Truthy as soon as it finds it, because evaluating the rest of the expression is redundant.

Instead of returning True or False hardcore, Python returns Truthy or Falsey, which in any case will be evaluated using True or False . You can use the expression as is, and it will work anyway.




To find out what Truthy and Falsey are, check out Patrick Hog’s answer

+14
Oct 30 '17 at 3:23
source share

and and or perform logical logic, but when compared, they return one of the actual values. In use, and values ​​are evaluated in a boolean context from left to right. 0, '', [], (), {}, and No are false in a Boolean context; everything else is true.

If all values ​​are true in a boolean context, and returns the last value.

 >>> 2 and 5 5 >>> 2 and 5 and 10 10 

If any value is false in a boolean context, and returns the first false value.

 >>> '' and 5 '' >>> 2 and 0 and 5 0 

So the code

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

returns the value max(args)-min(args) , when there is args else, it returns len(args) , which is 0.

+7
Oct 30 '17 at 3:33 on
source share

Is this a legitimate / reliable style, or are there any mistakes?

This is legal, this is a short circuit estimate when the last value is returned.

You are a good example. The function will return 0 if no arguments are passed, and the code should not check the special case without arguments.

Another way to use this parameter is the default None argument for a mutable primitive, for example an empty list:

 def fn(alist=None): alist = alist or [] .... 

If any non-truth value is passed so that the default alist has an empty list, a convenient way to avoid the if and the mutable default argument argument

+5
Oct 30 '17 at 3:26
source share

Gotchas

Yes, there are several gotchas.

fn() == fn(3) == fn(4, 4)

Firstly, if fn returns 0 , you cannot know if it was called without any parameter, with one parameter or with several equal parameters:

 >>> fn() 0 >>> fn(3) 0 >>> fn(3, 3, 3) 0 

What does fn mean?

Python is then a dynamic language. He does not indicate anywhere what fn does, what his input should be and how his output should look. Therefore, it is very important to correctly name the function. Likewise, arguments should not be called args . delta(*numbers) or calculate_range(*numbers) can better describe what the function should do.

Argument Errors

Finally, the logical operator and must prevent a function from failing when called without any argument. It still fails if some argument is not a number:

 >>> fn('1') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in fn TypeError: unsupported operand type(s) for -: 'str' and 'str' >>> fn(1, '2') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in fn TypeError: '>' not supported between instances of 'str' and 'int' >>> fn('a', 'b') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in fn TypeError: unsupported operand type(s) for -: 'str' and 'str' 

Possible alternative

Here you can write a function according to "It's easier to ask for forgiveness than permission." Principle :

 def delta(*numbers): try: return max(numbers) - min(numbers) except TypeError: raise ValueError("delta should only be called with numerical arguments") from None except ValueError: raise ValueError("delta should be called with at least one numerical argument") from None 

As an example:

 >>> delta() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 7, in delta ValueError: delta should be called with at least one numerical argument >>> delta(3) 0 >>> delta('a') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in delta ValueError: delta should only be called with numerical arguments >>> delta('a', 'b') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in delta ValueError: delta should only be called with numerical arguments >>> delta('a', 3) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in delta ValueError: delta should only be called with numerical arguments >>> delta(3, 4.5) 1.5 >>> delta(3, 5, 7, 2) 5 

If you really do not want to raise an exception when delta is called without any argument, you can return some value that is impossible otherwise (for example, -1 or None ):

 >>> def delta(*numbers): ... try: ... return max(numbers) - min(numbers) ... except TypeError: ... raise ValueError("delta should only be called with numerical arguments") from None ... except ValueError: ... return -1 # or None ... >>> >>> delta() -1 
+3
Oct. 30 '17 at 14:58
source share

Yes. This is the correct behavior and comparison.

At least in Python, A and B returns B if A is essentially True , including if A NOT Null, NOT None NOT an empty container (for example, an empty list dict , etc.). A IFF A returned, essentially False or None or Empty or Null.

On the other hand, A or B returns A if A is essentially True , including if A NOT Null, NOT None NOT an empty container (for example, an empty list dict , etc.), otherwise it returns B

It is easy not to notice (or ignore) this behavior, because in Python any non-null empty object is evaluated as True, considered as logical.

For example, all of the following will print "True"

 if [102]: print "True" else: print "False" if "anything that is not empty or None": print "True" else: print "False" if {1, 2, 3}: print "True" else: print "False" 

On the other hand, all of the following will print "False"

 if []: print "True" else: print "False" if "": print "True" else: print "False" if set ([]): print "True" else: print "False" 
0
Oct 30 '17 at 3:27
source share

Is this a legal / reliable style or is there any kind of error on this?

I would like to add to this issue that it is not only legal and reliable, but also ultra practical. Here is a simple example:

 >>>example_list = [] >>>print example_list or 'empty list' empty list 

Therefore, you can use it to your advantage. To be so, I see this:

Or operator

The Python Or statement returns the first Truth-y value or the last value and stops

And operator

The Python And statement returns the first False-y or the last value and stops

Behind the scenes

In python, all numbers are interpreted as True with the exception of 0. Therefore, saying:

 0 and 10 

matches with:

 False and True 

This is clearly False . Therefore, it is logical that it returns 0

0
Oct 30 '17 at 3:33 on
source share

understand in a simple way

And: if first_val is False return first_val else second_value

eg:

 1 and 2 # here it will return 2 because 1 is not False 

but,

 0 and 2 # will return 0 because first value is 0 ie False 

and => if someone is a lie, it will be a lie. if both are true, then only this will become true

OR: if first_val is False return second_val else first_value

the reason is that if first is false, check if 2 is true or not.

eg:

 1 or 2 # here it will return 1 because 1 is not False 

but,

 0 or 2 # will return 2 because first value is 0 ie False 

or => if someone is a lie, it will be true. therefore, if the first value is false, no matter what value 2 is assumed. therefore, it returns the second value as it may be.

if someone is true, then it will become true. if both are false, then it will become false.

0
Jun 19 '19 at 6:44
source share



All Articles