12

This method searches for the first group of word characters (ie: [a-zA-Z0-9_]), returning the first matched group or None in case of failure.

def test(str):
    m = re.search(r'(\w+)', str)
    if m:
        return m.group(1)
    return None

The same function can be rewritten as:

def test2(str):
    m = re.search(r'(\w+)', str)
    return m and m.group(1)

This works the same, and is documented behavior; as this page clearly states:

The expression x and y first evaluates x; if x is false, its value is returned; otherwise, y is evaluated and the resulting value is returned.

However, being a boolean operator (it even says so on the manual), I expected and to return a boolean. As a result, I was astonished when I found out (how) this worked.

What are other use cases of this, and/or what is the rationale for this rather unintuitive implementation?

NullUserException
  • 83,810
  • 28
  • 209
  • 234
  • 1
    Actually quite a few languages have this, it certainly predates Python. It was traditionally used as a substitute for the ternary conditional, back before we had `if...else` expressions, and it can still be used like C#'s null-coalescing operator `??`. Personally I would try to avoid all but small trivial uses of this, both because of the potential issues with ‘falsy’ values, and because “explicit is better than implicit”. `return m and m.group(1)` is pretty much OK, but if you go further than that, `m is not None and...` might be clearer. – bobince Sep 29 '10 at 23:51

5 Answers5

6

What are other use cases of this,

Conciseness (and therefore clarity, as soon as you get used to it, since after all it does not sacrifice readability at all!-) any time you need to check something and either use that something if it's true, or another value if that something is false (that's for and -- reverse it for or -- and I'm very deliberately avoiding the actual keywords-or-the-like True and False, since I'm talking about every object, not just bool!-).

Vertical space on any computer screen is limited, and, given the choice, it's best spent on useful readability aids (docstrings, comments, strategically placed empty lines to separate blocks, ...) than in turning, say, a line such as:

inverses = [x and 1.0/x for x in values]

into six such as:

inverses = []
for x in values:
    if x:
        inverses.append(1.0/x)
    else:
        inverses.append(x)

or more cramped versions thereof.

and/or what is the rationale for this rather unintuitive implementation?

Far from being "unintuitive", beginners regularly were tripped up by the fact that some languages (like standard Pascal) did not specify the order of evaluation and the short-circuiting nature of and and or; one of the differences between Turbo Pascal and the language standard, which back in the day made Turbo the most popular Pascal dialect of all times, was exactly that Turbo implemented and and or much like Python did later (and the C language did earlier...).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 1
    Isn't that supposed to be `x and 1.0/x for ...`? – NullUserException Sep 30 '10 at 03:50
  • @Null, **oops**, you're right -- see, you _do_ find the construct readable despite my horrid typo!-) Tx, editing to fix. – Alex Martelli Sep 30 '10 at 04:41
  • Completely off topic; is !-) a smiley? – poke Sep 30 '10 at 08:55
  • @poke: No, it's an artifact of markdown's handling of the accent grave. – S.Lott Sep 30 '10 at 12:00
  • @poke, yes it is a smiley, but with an eye-patch. – Geoffrey Oct 21 '10 at 20:26
  • 1
    The unintuitive part isn't the evaluation order or the short circuiting; the unintuitive part is boolean operators returning non-booleans. It makes a lot more sense with the knowledge that at the time Python's `and` and `or` were defined, Python didn't actually have a dedicated boolean type. – user2357112 Dec 13 '18 at 00:05
  • @AlexMartelli I strongly disagree with the idea that `inverses = [x and 1.0/x for x in values]` is readable, because you should use the ternary operator `inverses = [1/x if x != 0 else float("nan") for x in values]` and explicitely write the condition you are cheching (instead of truthness) and the default value. Plus, your approach forces your default value to be whatever the falsey value is which might lead to silent None, False, 0, [], "" in your list of inverses. You are actively approving to silently write 1/0 = 0 in your list with your approach. – Guimoute Dec 27 '22 at 21:53
  • "if != ... else ..." ranks right next to "if not ... else ..." in my own list of personal readability peeves -- when I meet such contortions in a code review, I'll block the changelist's progress until the conditional is rewritten *readably* ("if == ..." &c, switching the if and else cases). Compared to THAT, I assess the choice between "x and y" vs "x if x else y" as a minor issue -- I definitely prefer the `and` formulation, but won't block a changelist based just on that preference. "What's the inverse of 0" is a separate, domain-dependent issue. – Alex Martelli Dec 28 '22 at 22:36
4

What are other use cases of this,

No.

what is the rationale for this rather unintuitive implementation?

"unintuitive"? Really? I'd disagree.

Let's think.

"a and b" is falsified if a is false. So the first false value is sufficient to know the answer. Why bother transforming a to another boolean? It's already false. How much more false is False? Equally false, right?

So a's value -- when equivalent to False -- is false enough, so that's the value of the entire expression. No further conversion or processing. Done.

When a's value is equivalent to True then b's value is all that's required. No further conversion or processing. Why transform b to another boolean? It's value is all we need to know. If it's anything like True, then it's true enough. How much more true is True?

Why create spurious additional objects?

Same analysis for or.

Why Transform to Boolean? It's already true enough or false enough. How much more True can it get?


Try This.

>>> False and 0
False
>>> True and 0
0
>>> (True and 0) == False
True

While (True and 0) is actually 0, it's equal to False. That's false-enough for all practical purposes.

If it's a problem, then bool(a and b) will force the explicit conversion.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
1

I think that while this notation 'works' it represents a poor coding style that hides logic and will confuse more experienced programmers who will have the 'baggage' of knowledge how the majority of other languages work.

In most languages the return value of an active function is determined by the type of function. Unless its's been explicitly overloaded. Example a 'strlen' type function is expected to return an integer not a string.

In line functions such as the core arthritic and logic functions (+-/*|&!) are even more restrained because they also have history of formal math theory behind them. (Think about all the arguments about order of operations for these functions)

To have fundamental functions return anything but their most common data type (either logic or numeric) should be classified as purposeful obfuscation.

In just about every common language '&' or '&&' or 'AND' is a logic or Boolean function. Behind the scenes, optimization compilers might use short cutting logic like above in LOGIC FLOW but not DATA STRUCTURE Modification (any optimizing compiler that changed the value this way would have been considered broken), but if the value is expected to be used in a variable for further processing, it should be in the logic or boolean type because that's the 'formal' for these operators in the majority of circumstances.

Cosmo F
  • 11
  • 2
1

Basically a and b returns the operand that has the same truth value as the whole expression.

It might sound a bit confusing but just do it in your head: If a is False, then b does not matter anymore (because False and anything will always be False), so it can return a right away.

But when a is True then only b matters, so it returns b right away without even looking.

This is a very common and very basic optimization many languages do.

Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • I know about short-circuit evaluation, it's just the possibility of this expression having totally different return types that bothers me. – NullUserException Sep 30 '10 at 00:52
  • 1
    @NullUserException: Not "totally" different at all. The boolean equivalent value is the same. – S.Lott Sep 30 '10 at 12:02
0

I didn't find this surprising, and in fact expected it to work when I originally tried it.

While not all values are bools, note that in effect, all values are boolean--they represent a truth value. (In Python, a bool is--in effect--a value which only represents true or false.) The number 0 isn't a bool, but it explicitly (in Python) has a boolean value of False.

In other words, the boolean operator and doesn't always return a bool, but it always return a boolean value; one that represents true or false, even if it also has other information logically attached to it (eg. a string).

Maybe this is retroactive justification; I'm not sure, but either way it seems natural for Python's boolean operators to behave as they do.


When to use it?

In your example, test2 feels clearer to me. I can tell what they both do equally: the construction in test2 doesn't make it any harder to understand. All else equal, the more concise code in test2 is--marginally--more quickly understood. That said, it's a trivial difference, and I don't prefer either enough that I'd jump to rewrite anything.

It can be similarly useful in other ways:

a = {
    "b": a and a.val,
    "c": b and b.val2,
    "d": c and c.val3,
}

This could be rewritten differently, but this is clear, straightforward and concise.

Don't go overboard; "a() and b() or c()" as a substitute for "a()? b():c()" is dangerous and confusing, since you'll end up with c() if b() is false. If you're writing a terniary statement, use the terniary syntax, even though it's hideously ugly: b() if a() else c().

Glenn Maynard
  • 55,829
  • 10
  • 121
  • 131
  • I'd say that test2 does make it harder to understand if the reader isn't familiar with this topic. The fact that the topic is being discussed says to me that the explicit if/else is more understandable. – Russell Borogove Sep 29 '10 at 23:59
  • 1
    @Russell Borogove: For any given bit of syntax, there's somebody out there who doesn't understand it and yet more people discussing it. This is elementary in Python (and several other languages; Python didn't invent this), and unless you're writing tutorial code meant for the least common denominator of novice programmers, I think this is fine. – Glenn Maynard Sep 30 '10 at 00:06