1. 6
  1.  

  2. 3

    I disagree with the truthy/falsy slide. Only boolean values should be used in conditionals; otherwise it is unclear what you’re branching on. The example used is:

    if name and pets and owners: ...
    

    How is it clear what we’re actually asking about name/pets/owners? The only way to figure that out is by looking at some table of coercions. It’s like asking, “Is the weather?” and hoping to get an answer about whether it will rain. The “bad” example here is:

    if name != '' and len(pets) > 0 and owners != {}: ...
    

    But this is needlessly ugly. You could instead write:

    empty = lambda x: len(x) == 0
    if not empty(name) and not empty(pets) and not empty(owners): ...
    

    Or even, depending on your style:

    if all(not empty(s) for s in (name, pets, owners)): ...
    

    Here the intent is much clearer.

    1. 3

      I strongly prefer truthiness and it’s considered Pythonic.

      if name != '' and len(pets) > 0 and owners != {}:
          ...
      

      Your program will incorrectly enter the if block for the following reasons:

      • name = None or changed to a non-string (e.g. an object) or
      • owners = None or changed to a non-dictionary (e.g. a list)

      Your program crashes if pets is changed to anything that doesn’t support len().

      How is it clear what we’re actually asking about name/pets/owners?

      It’s up to each object and the object’s author to decide how it should be qualified as true or false. What if the code changes and and a Person object is passed in instead of a name? Are you going to update all the locations where you tested against an empty string?

      1. 5

        I can’t address “considered Pythonic” because it’s an appeal to tradition, not reason. As to the other points: it is impossible to write most functions in such a way that they won’t crash under some input. Consider:

        def add(x,y): return x + y
        

        I’ll crash the program by passing an input that doesn’t support +. This is a consequence of having an untyped/unityped language: there is no way to be precise about the domain of your function. In a typed language, I could at least restrict my function to only work on values that support +, or empty, and fail to compile otherwise. But in Python, a runtime crash is the best error reporting I have.

        As for if statements, the reason that we have:

        if p(x): <body>
        

        Is because we don’t necessarily know that p(x) is true outside of the conditional, but our guard ensures that it is true in the body. We know something about x inside the body of the statement that we didn’t know outside.

        But now consider the case where we trust some unknown implementation to provide truthiness for us:

        if x: <body>
        

        We don’t know what property we’re testing, so we can’t assume anything about x that we didn’t know outside of the statement. We haven’t gained anything; all we know inside of the body is that x has some unknown property. This is why the slide that immediately follows the one under question is a table of truth values. Without this knowledge, it is impossible to say anything useful about the guarded value.

        1. 1

          I can’t address “considered Pythonic” because it’s an appeal to tradition, not reason.

          Idioms acts as a “best practices” guide from the collective experience of the language’s community.

          def add(x,y): return x + y
          

          This is a consequence of having an untyped/unityped language: there is no way to be precise about the domain of your function. In a typed language, I could at least restrict my function to only work on values that support +, or empty, and fail to compile otherwise.

          If foo doesn’t support + and you call add(foo, bar) it throws a TypeError. What would the alternative be in a dynamically typed language? Would you return no input? Coerce foo into something that supports +?

          You can check typing at runtime via isinstance(), but that’s not used because Python emphasizes duck typing. Duck typing allows polymorphism without inheritance. You are not supposed to restrict your function to work with specific objects.

           if x: <body>
          

          We don’t know what property we’re testing, so we can’t assume anything about x that we didn’t know outside of the statement. We haven’t gained anything; all we know inside of the body is that x has some unknown property.

          We know that x.__bool__() has been evaluated true based on an evaluation of specific properties determined by x’s class. You are delegating boolean testing to a central location instead of duplicating property testing all over your codebase.

          1. 2

            My point was: yes, len(x) == 0 crashes for some values of x. So do most nontrivial functions of x, like lambda x: add(x, 1). I could make the name and pets and owners... code crash just by passing a name whose __nonzero__() method raised an exception. if x: ... is no more safe or general than if p(x): ....

            The difference is that, in one case, you’re actually branching on some query about the value, and in the other, you’re branching on some mystery predicate.

            1. 2

              I could make the name and pets and owners… code crash just by passing a name whose __nonzero__() method raised an exception.

              I can’t tell if you’re being intentionally obtuse or not.

               if name != ''
              

              ..you’re actually branching on some query about the value

              No, you’re not. You’re comparing if name is not equal to the empty string. That doesn’t imply that name is true.

              If the fact that name could be None, False, 0, [], {}, set() or any other instantiated object and still pass your guard statement doesn’t bother you, I’m not sure what else to say. I’m just going to end with Google’s own recommendations.

              1. 4

                I’m also at a loss here. Strings are not booleans. There is nothing intrinsically true or false about the empty string, except questions that you can ask about it (is the string empty? does it contain only ASCII characters?)

                I never recommended branching on name != '' in my original post. But it is at least a query that allows you to state that name is not the empty string within the body of the conditional. if x: ... is existential. By guarding against some unknown statement about x, all you know is that ∃ p. p(x). Unless you know something about the predicate, the guard gives you no extra information!

    2. 2

      Excellent summary of Python idioms.