This goes for if you make a library as well, your library will be easier to use if you are less strict about the inputs you take, since that allows your user to work in a more naturally dynamic way. I love static types, but I have worked on making python libraries and there accepting a wide range of inputs is an important part of usability.
Each to their own liking, I prefer knowing what argument types a function accepts so I don't need to think about it, and focus on writing business logic. If the function could accept more types, Id just improve it.
That is for library code, maybe it would be too cumbersome to try to do that for code with less reuse. I have never worked on a large python codebase that wasn't a library so I'm not sure what is best there.
For beginners, and in toy examples, it's kind of neat when code bends over backwards to work. Take this example:
>>> def all_uppercase(lst):
... return [s.upper() for s in lst]
...
>>> all_uppercase('hi')
['H', 'I']
>>>
Kind of neat, right? It's almost like a joke in code. Ha ha, iterating over a string gives you strings! But the charm of finding cute things to do with unexpected inputs doesn't scale. What is a helpful attitude at a small scale translates to "errors should manifest as far away as possible from the programming mistake that caused them" at a large scale. 99 times out of 100, if your code expects a list and somebody passes a tuple, they want a stack trace, not a return value.> I have worked on making python libraries and there accepting a wide range of inputs is an important part of usability
I work with a large Python codebase at work, and this is a frequent source of frustration. I frequently track down bugs and find that on some untested code path our code passes nonsensical values of the wrong type to a third-party library, and the library just... finds some way of interpreting it.
Even if all the code paths get tested, they can't be tested with every possible input. Property-based testing seems like overkill for our application, and our tests are already almost slow enough to be annoying. And what if the third-party library is side-effecting in a way that's hard to test? It gets mocked out. And I find that the mocks are configured to expect the nonsensical values, because the original programmer found that they "work."
All because libraries don't want to make an unfriendly impression by throwing a stack trace.
If there is a bug and somebody passes invalid type to my function, I would just fix it and move on.
Very often both None and empty list are not an interesting case and I return early. Thus `not users` makes sense.
There are cases, though, when None means a sane default should be used instead and you can't use default argument values due to mutability. In these cases `users is None` makes perfect sense.
There are also cases I explicitly check for True and False - tests. In such cases I wouldn't rely on truthy/falsy values and assert True and False values by reference.
Having said that, its all subject ive. You like this style, some one else likes other style. What ultimately matters are two things: automatic formatters and consistency.
> if not users:
> an improvement in any of these cases?
Function iterates over the input. User provides a list,
if users == ():
test fucks up because lists and tuples are never equal.Literally no gain, only pain.
Wouldn't this just be the developer using the wrong comparison for the types the function is expecting(hence more reason to be explicit instead of using the implicit false)?
Because for most functions that's not a relevant or useful distinction, in Python a tuple is an immutable list, both are sequences.
By mis-handling empty tuples you're just unnecessarily constraining the caller. Not only that, but you might also create an inconsistency which is hard for the caller to notice if your function only fucks up on empty collections.
If we use Python as a strongly typed language, it makes no difference which one you use.
If we don't (i.e. use Python as it is: a dynamically-typed language), then this is just a preference.
Using (or exploiting, depends on how you think) Truthiness this way is actually an intentional choice in lots of case, especially if you have "else" condition.
Think it this way: you're going to split the conditions into two: `users` is non-empty, which is the "good" condition; and `users` is empty, which is the "bad" condition.
Then you have unexpected condition that "users" is something that shouldn't be, most commonly being None. In most of cases, this is a "bad" condition. So it makes sense it's grouped together with `users == []`.
If `users` is "True" or "False" as you said (which you should ensure to not happen in other ways anyway), then indeed it will not be captured by `users == []`, but it would still be broken/unmanaged in "else" side.
Nitpick: Python is strongly typed, it's also dynamic. The strongly-weakly typed axis is different from the static-dynamic axis.
In a lot of cases though, an empty collection isn't a "bad" condition at all, e.g. it's a valid collection to apply filters/maps to.
Similarly when people get used to doing "if not i" for ints, but then forget about the times that zero is a valid value.
It's true that dynamic coercion is a feature of the language, but coding conventions generally are often about enforcing "least surprise" to remove a burden from the person reading the code.
Then you don't need to check if it's empty to begin with.
An empty list could be valid(e.x. a search of users providing no results) so you still need to differentiate between "special cases that need special logic" and "bad input".
Lumping the two together in one `if` block makes any code less readable imo, because they're not the same thing.
Is an empty list being routed to the else branch because it is an error in this instance or because it's an error in 90% of the codebase so the author forgot to handle it explicitly here?
Or is the author always expecting users will be a full or empty list and that other falsy values will never occur?
The generalization of this is to code against as generic an api as possible, you wouldn't do `list.__eq___(x, y)` in your code, but you're suggesting almost exactly that.
(granted you can still run into this kind of issue if foo is a generator, but that's a less common way to explode).
The style guide does tell you to use explicit `x is None`, instead of implicit bool when checking noneness, specifically to disambiguate between binary and ternary values, but usually that's not what you want.
if users == []:
Nor is this: if (users == []) == True:
Nor is this: if ((users == []) == True) == True:
.../s
Others have mentioned that the comparison to say a tuple will also fail. If the intent is to ensure a list instance, use isinstance, instead.
The first option:
a) obfuscates the type of users on first reading
b) evaluates to True if users is None (or LOADS of other things?!) which can lead to hard-to-find bugs.
Granted, type-checking can help here but purely from a readability perspective the second option seems way more friendly and for almost no downside. The same holds true for all of the "False-y" objects:
Why is the implicit: an improvement in any of these cases? !!!Why not just: