To me personally, testing for 'truthy' and 'falsy' values, or relying on exceptions rather than checking values in advance, feels like sloppy and imprecise programming.
A string being empty or not, or an array having items or not, or a boolean being true or false, are all qualitatively totally different things to me -- and just because Python can treat them the same, doesn't mean a programmer should take advantage of that fact. Sometimes it's possible to over-simplify things in a way that obfuscates instead of clarifying.
When I read:
if name and pets and owners
I have no intuitive idea of what that means, of what's going on in the program. When I read
if name != '' and len(pets) > 0 and owners != {}
I understand it exactly.
But by this point, I've come to understand that, for a lot of people, it seems to be the opposite. It seems to be more of a philosophical difference, not right/wrong.
It's a dynamic-typing thing. In some hypothetical static-strong-non-duck version of Python,
if name != '' and len(pets) > 0 and owners != {}
would tell you that name is a non-empty string, pets has values, and owners is a non-empty dict (except it doesn't work, as pdonis noticed).
But Python allows immoral implicit conversions, so that's not what that line means! If name is a function, pets is the value '7' and owners is a list, the test passes.
if name and pets and owners
Would pass as well, but it has the advantage of not implying it does more than it does: all you can infer from the test passing is that none of name,pets,owners are special falsy values.
If you actually wanted to test what the longer line is implying, you'd write something like
if isinstance(name, str) and isinstance(pets, list) and isinstance(owners, set) and name and pets and owners
No, it doesn't tell you that. {} denotes an empty dict, not an empty set; and an empty set will return True for owners != {}, not False. As I noted in another post upthread, you would need to write
len(owners) > 0
to get the correct semantics, making owners indistinguishable from pets even if they are different container types. If you really wanted to make all the types clear, you would need to include the isinstance tests.
Sure, if you view the code as a conversation between the reader and the compiler, but as a conversation between the reader and the writer, I would hope that those inferences (e.g. name is a non-empty string) would be reasonable.
> it's possible to over-simplify things in a way that obfuscates instead of clarifying
While this is true, I think it's important to keep in mind that a big benefit of being able to handle a situation programatically in different ways is that you, as a programmer, are more able to accurately describe intent.
For example, I think it's foolish to always catch exceptions instead of checking beforehand (look-before-you-leap). However, being able to do either allows me to more accurately represent the intent of a statement. Blindly following rules is almost always sure to lead to poor decisions.
If I'm retrieving an element from an array and I know that for the majority of possible application states there will be an element there, I can just access it, and handle an exception (because it's an "exceptional" case). However if I'm expecting that it could go either way in typical circumstances, then I might more explicitly check the index in the array to illustrate my intent that yes, there are two distinct paths here and that is how the program's control flows naturally.
Of course all of these things are only worthwhile if the reader can see and interpret them as purposeful decisions rather than coincidence. Conveying that is something else entirely.
I think the point of the article, and of idioms in general, is that they make code "better" (e.g. some combination of clearer, shorter, cleaner, etc.) for the community of coders who are familiar with the idioms. The obvious downside of idioms is that a programmer needs to learn the idioms to reap these advantages. So I suppose whether you should encourage idioms in your code base would depend on who will be working on the code base. I suspect most traditional software companies (even startups) will employ programmers who will either know the idioms of the language being used, or will be willing and able to learn them.
Nonetheless, you should reconsider from time whether those idioms are actually helpful for those in the community. I personally dislike 'if name and pets and owners:' because it removes the information of what is happening at this line of code and I have to look up what type name, pets and owners actually are.
it removes the information of what is happening at this line of code and I have to look up what type name, pets and owners actually are
My response to this is, why do you care what their types are? What the statement is saying, conceptually, is "if there's a name and there's pets and there's owners, do this". Why should I have to explicitly tell the language how to tell whether there's a name and there's pets and there's owners, when it already knows that? To me that's just extra verbosity for no good reason. Do you really care that name is a string but pets and owners are containers? (And even if you do, isn't that evident from the variable names anyway? Good naming conventions can do a lot of the work of clarifying what's going on while keeping the code compact.)
That's a good point. In the example they give it's not really an issue, since the variables are clearly defined right before the if statement. I still think it's reasonable and intuitive in most cases, once you're familiar with the "truthiness" of basic data types (e.g. empty arrays, empty strings, empty dicts, and numbers equal to zero are "falsy").
I've personally run into problems when I don't do exact comparisons with True and False. For example, I've forgotten to return a value in one path of a function/method, and then tried to use the result in an if statement in the style recommended by the OP (e.g. if fcall(): do something). After being bitten several times by this, I always do explicit comparisons.
In this particular situation, there was no else. I probably added an else clause with an assert return_value == False, as the function call was a virtual dispatch that could have many implementations. Of course, I wouldn't do that for every if/then/else statement in my code. In general, I'd prefer a stricter language that only permitted a boolean as a condition in the IF statement, avoiding this problem altogether.
If you are using truthy/falsey values, I think it can be a code smell that you are not doing enough to catch invalid values up front or should normalize the values closer to their creation point.
But the discussion is about truthy values and inexact comparisons - avoiding inexact comparisons and having an else means:
if something==True:
do_this()
elif something==False:
do_that()
else:
raise UserWarning("Shouldn't be here")
Which is a code smell to me. What I would do is:
assert isinstance(something, boolean), "Shouldn't happen"
do_this() if something else do_that()
(replace assert with something else if you want it not to be optimized away with -O; assert is a debug-only construct in Python. Or just drop the assert altogether. In most places, I would - there's no end to the amount of validation you could do, and most of it is unnecessary)
Checking for empty strings can be done with len(mystring)==0 for this reason. In many other languages this method is standard and recommended practice.
Relying on implicit conversions is just sloppy. What if that variable was never supposed to be None in the first place. Better with an exception than continuing with corrupt data.
Remember another python motto: Explicit is better than implicit.
> `len` blows up on None, so this blows up completely instead of just failing
That's the whole point! As I said, Better with an exception than continuing with corrupt data. Or maybe I should say unsupported data type rather than corrupt data.
At the point where you are doing your validation (or where you are doing the actual work with the variable) you can make that decision.
It's entirely possible that upstream code is content with any type as long as it's coercible. A lot of Python code just wants an iterable, for example, or something that can be coerced to a string.
It's definitely possible to have a situation where you need a string specifically (or an integer specifically), but it's generally better to have your code coerce the data whenever possible so as to allow for more logical code.
e.g. 's.send(str(object))' (if object has a relevant __str__()) makes perfect sense in code. You don't necessarily need a string, you just need something that can behave as a string in a sensible manner.
You also end up with scenarios where None is a 'valid value' in e.g. the SQL sense of 'value not specified'; for example, company_name might be '' (empty string provided) or None (no value provided); either way company_name is Falsey, and your logic should work the same.
If you need to differentiate between the empty string and None, you can, but in most situations the difference isn't important. E.g., would you ever actually display the values differently? Exaggerated explicitness can be misleading. E.g., if a year from now someone is making some changes, should they really be distracted by the fact that you've used 3 different if/else cases for the same semantic result?
See my response as well, but it depends on your intent.
None and '' are two completely different things, and could mean different thing sin the context of the application. It's quite likely that choosing either is a conscious choice of the programmer.
The computer sees None and '' as different. But the users of my database at work just see a blank entry. As far as they are concerned, they are the same. For that reason it can be useful to have the code treat them the same.
Note that 'is' in Python is kind of tricky. None is None, and any value==None is also None. 1 is 1, and 100 is 100; 256 is 256 but 257 is not 257, and "test" is not "test".
The reason for this is that 'is' tests to see if both operands are the same object; it's not a test of equivalence on any level.
There is only ever one instance of None, and you simply get or create references to it. Likewise, there is only one instance of integers up to 256 (presumably a performance optimization for small loops, indexes, etc.). Integers after 256 get separate instances each time, so 'is' fails after that point.
A lot of newcomers to Python come across the 'is None' construct and make assumptions about what 'is' does; the worst part is that they're often correct, and the behaviour becomes difficult to track down when it changes.
Really? Containers of different types have a len method; which type of container is pets? The line you wrote doesn't tell you. And is owners supposed to be a set? If so, your comparison to an empty dict will give the wrong semantics if owners is an empty set (owners != {} will return True for an empty set, not False). You would have to write
len(owners) > 0
to get the correct semantics. Which, of course, already obscures the type of owners, just as the type of pets is obscured.
In short, your suggested "improvement" over idiomatic Python still obfuscates rather than clarifying.
To me personally, testing for 'truthy' and 'falsy' values, or relying on exceptions rather than checking values in advance, feels like sloppy and imprecise programming.
A string being empty or not, or an array having items or not, or a boolean being true or false, are all qualitatively totally different things to me -- and just because Python can treat them the same, doesn't mean a programmer should take advantage of that fact. Sometimes it's possible to over-simplify things in a way that obfuscates instead of clarifying.
When I read:
I have no intuitive idea of what that means, of what's going on in the program. When I read I understand it exactly.But by this point, I've come to understand that, for a lot of people, it seems to be the opposite. It seems to be more of a philosophical difference, not right/wrong.