A false midnight
Interpreted, "duck typing" languages often have some idiosyncrasies in their definitions of "truth" and Python is no exception. But Python goes a bit further than some other languages in interpreting the True or False status of non-Boolean values. Even so, it often comes as a big surprise for programmers to find (sometimes by way of a hard-to-reproduce bug) that, unlike any other time value, midnight (i.e. datetime.time(0,0,0)) is False. A long discussion on the python-ideas mailing list shows that, while surprising, that behavior is desirable—at least in some quarters.
Python's notion of truth values defines False as various kinds of numeric zero (0, 0L, 0.0, 0j), "empty" objects ('', (), [], {}), the special value None, and, of course, False. User-defined classes can also have a False value, but many will not, which means that all objects of those classes are True. However, buried in the datetime module documentation for a time object is the following somewhat complicated explanation:
It all boils down to the idea that a time of 00:00 UTC (which could be something else entirely in the local time zone, of course) is False. If one were testing a variable meant to hold a time value as follows:
if current: do_something(current) # we have a valid current timeThe code will fail to do_something() if current is None or is set to a time value that is exactly at midnight UTC. One can argue (and several in the thread did) that the test is wrong and should be:
if current is not None: do_something(current) # we have a valid current timeBut "if var:" is a common idiom in Python code—it works just fine for most objects, as they always evaluate to True, so None can be used as a sentinel value. But it doesn't work for time objects, at least for one second per day.
The datetime module in the standard library is a fairly simple way to represent dates, times, and timestamps (using the datetime class). The date class does not have a time associated with it, while the time class (with resolution in seconds) is undated. Oddly, a time can have a time zone associated with it (even though time zones are often date-related). A datetime has both time and date (and can include a time zone as well).
A Python bug was opened in 2012 about midnight as False, but it was closed soon thereafter as "invalid". The time class was documented to work that way and someone had created a __nonzero__() (now __bool__()) method for time to implement that behavior. But, the behavior still didn't sit well with some. Nine months after the bug was closed, Danilo Bargen reported that the False interpretation caused problems with filters in Django templates. He asked that the bug be reopened, but nothing happened—and that's where things stood until recently.
That's when Shai Berger asked again for the bug to be reopened. That request was seconded by Andreas Pelme before moving to python-ideas at the request of Alexander Belopolsky. Berger posted a summary of the issue and asked for a reconsideration. That led to a large thread, even by python-ideas standards.
Berger's argument was twofold. First, paraphrasing Bargen and Pelme, that
the current behavior is "surprising and useless
" since users
generally don't want special behavior at midnight. Second, his personal
argument is that midnight "is not a 'zero value', it is just a
value
". He went on to say that midnight as False made as
much sense as datetime.date(1,1,1) (the minimum date
value) evaluating to False (and it doesn't).
While Paul Moore was unhappy with the
practice of using Boolean tests for date validity
(as it should be "is not None" in his mind),
he did think the current behavior was wrong. Oscar Benjamin went further, noting that if this was new code
being added, returning False for midnight in a Boolean context
would never pass muster. "The question is surely whether the issue is worth a backwards
compatibility break not whether the current behaviour is a good idea
(it clearly isn't).
"
On the other hand, Skip Montanaro is convinced that the problem is all in the application code and that the bug should remain closed. There weren't too many voices joining in with that sentiment, though Tim Peters and Belopolsky, who were both involved in the creation of the datetime module, were opposed to making any change. But Nick Coghlan posted an analysis of the current behavior that made it clear to many that it was completely inconsistent and not really tenable:
Coghlan had already reopened the bug, suggesting that anyone interested in seeing it fixed could post a patch to do so. The only question was how to handle deprecating the existing behavior. Python 3.4 is feature-frozen, so it is out of the picture; any change would come in 3.5 and subsequent releases. Coghlan's suggestion was to issue a DeprecationWarning whenever a time returns False in a Boolean context for 3.5, then to change the behavior so that all time values are True for 3.6. That means the change won't actually be released for roughly three years, which is too long for some.
One of those who might be in that "change it already" camp is BDFL Guido van Rossum. He posted an unequivocal statement that False time values were a mistake made a long time ago:
Furthermore, Van Rossum said, he would be inclined to fix the problem "immediately" (for Python 3.5) but would, perhaps, like to see a search for legitimate uses of the feature. Both Peters and Belopolsky noted that they had used the False midnight in programs, but nothing that was widely distributed; no other examples were mentioned in the thread. Meanwhile, multiple reports of problems stemming from the feature did come up.
While there is a danger that some legitimate programs might run afoul of the change, Van Rossum's intuition is probably right: there are likely few instances of real False midnight users, while the unexpected False problem is likely to be fairly common, with many if var: instances going undetected (or at least undiagnosed) for years. Backward compatibility is always a balancing act, but in this case it would seem that making a clean break from the existing behavior—and relatively soon—is probably the right approach.
Posted Mar 12, 2014 18:26 UTC (Wed)
by khim (subscriber, #9252)
[Link] (45 responses)
Posted Mar 12, 2014 19:14 UTC (Wed)
by rswarbrick (guest, #47560)
[Link] (2 responses)
http://docs.python.org/2/library/datetime.html#time-objects
(probably version 2.3, if I'm understanding the documentation correctly)
Posted Mar 12, 2014 19:32 UTC (Wed)
by khim (subscriber, #9252)
[Link] (1 responses)
Posted Mar 12, 2014 23:17 UTC (Wed)
by nikarul (subscriber, #4462)
[Link]
Posted Mar 12, 2014 19:19 UTC (Wed)
by zuki (subscriber, #41808)
[Link] (41 responses)
Posted Mar 12, 2014 19:35 UTC (Wed)
by khim (subscriber, #9252)
[Link] (39 responses)
Sorry. By “all that hoopla” I've meant discussions on mainling lists, bugs and so on. The fact that all these “filters in in Django templates” are used with python 2, not with the python 3 is obvious “elephant in the room” people are missing. Why would I care if it'll be fixed in python 3.5, 3.6, or 3.10 if I'm using python2 and thus need to deal with this problem for the foreseeable future?
Posted Mar 12, 2014 19:47 UTC (Wed)
by zuki (subscriber, #41808)
[Link] (6 responses)
Posted Mar 12, 2014 20:12 UTC (Wed)
by dlang (guest, #313)
[Link]
Posted Mar 12, 2014 20:29 UTC (Wed)
by mpr22 (subscriber, #60784)
[Link] (4 responses)
Posted Mar 13, 2014 0:56 UTC (Thu)
by zuki (subscriber, #41808)
[Link] (3 responses)
Posted Mar 13, 2014 4:33 UTC (Thu)
by dlang (guest, #313)
[Link]
how much stuff is there that will run on 2.7 that won't run on 3.3? I'm talking about stuff written for earlier 2.x versions that continues to run on 2.7, but won't run on 3.3.
Posted Mar 14, 2014 19:16 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link] (1 responses)
Posted Mar 15, 2014 12:58 UTC (Sat)
by edomaur (subscriber, #14520)
[Link]
Posted Mar 12, 2014 23:20 UTC (Wed)
by pboddie (guest, #50784)
[Link]
Posted Mar 13, 2014 11:46 UTC (Thu)
by moltonel (guest, #45207)
[Link] (30 responses)
If python2 is the only version in your forseable future, you're dealing with a lot of problems already so having to work around a gotcha that has always been there seems fair enough.
Posted Mar 13, 2014 17:55 UTC (Thu)
by pboddie (guest, #50784)
[Link] (29 responses)
Posted Mar 13, 2014 22:43 UTC (Thu)
by moltonel (guest, #45207)
[Link]
Of course if your environement is static and your program works fine, you can just keep calm and carry on... But in that case you shouldn't be interested in the fact that a particular gotcha will not be fixed in the version of python you use.
Posted Mar 13, 2014 22:51 UTC (Thu)
by kokada (guest, #92849)
[Link] (26 responses)
One gotcha that I had with Python 2.x is the way it handles divisions. If you try the following code on a Python 2.x interpreter:
3/4
You'd get:
0
While with Python 3.x you get:
0.75
Of course, the first example is C-like, while the second is what you'd expect from a non-typed language. To get the same result on Python 2.x you'd have to write:
3.0/4
Even this doesn't work:
float(3/4) #since the 3/4 is evaluated before the conversion
This actually made me waste 20 minutes debugging a program because the error just doesn't made any sense to me. I think this is the same gotcha as the article shows.
Posted Mar 14, 2014 10:28 UTC (Fri)
by khim (subscriber, #9252)
[Link] (1 responses)
Posted Mar 14, 2014 16:09 UTC (Fri)
by hummassa (subscriber, #307)
[Link]
Posted Mar 14, 2014 18:38 UTC (Fri)
by pboddie (guest, #50784)
[Link] (2 responses)
As is pointed out already here, integer division was "fixed" with the "future" division feature - the origin of "from future" if I remember correctly - and was one of the original "Python warts" that was fixed in Python 2, although I did argue for keeping thing as they are/were and adding a new operator to produce the "expected" result. (It is only "expected" depending on context and on the user's background, of course, and I remember pointing out that the case for the change could have been made more concisely and persuasively.)
Posted Mar 16, 2014 15:31 UTC (Sun)
by kokada (guest, #92849)
[Link] (1 responses)
Posted Mar 18, 2014 20:19 UTC (Tue)
by pboddie (guest, #50784)
[Link]
I was explicitly responding to the assertion... ...which, of course, it isn't. I'm very familiar with general complaints about Python - and more specifically, Python 2 - but most of those are regarded as concerning widely known and accepted, if not widely liked, behaviour. The topic of the article concerns something that divides the opinions even of those developing Python 3 as to whether the behaviour should be fixed or is even excusable.
Posted Mar 17, 2014 13:07 UTC (Mon)
by ikm (guest, #493)
[Link] (20 responses)
Python is strongly-typed. And I would actually expect 0 in the case of 3/4 since we're dividing two integers, not floats, and in all strongly-typed languages I know of the division operation doesn't change type.
Posted Mar 18, 2014 1:18 UTC (Tue)
by hummassa (subscriber, #307)
[Link] (5 responses)
Posted Mar 18, 2014 20:23 UTC (Tue)
by pboddie (guest, #50784)
[Link] (4 responses)
Posted Mar 19, 2014 0:29 UTC (Wed)
by hummassa (subscriber, #307)
[Link]
the whole "int/int -> float" thing came from Pascal/Modula via Algol IIRC... they had "int DIV int -> int" to compensate.
Posted Mar 20, 2014 15:35 UTC (Thu)
by Wol (subscriber, #4433)
[Link] (2 responses)
While typing is a good way of getting the compiler to spot your mistakes, it is wholly unnatural from a maths p-o-v. Looking at things as a mathematician and not as a computer scientist, a number is a number is a number, and this categorisation into things like ints, longs, reals, double-precisions etc is abnormal. If I wanted 3/4 to give me 0 I'd use mod, not divide.
Cheers,
Posted Mar 21, 2014 8:20 UTC (Fri)
by mbunkus (subscriber, #87248)
[Link] (1 responses)
So what's my point? Not sure, maybe something like: computer maths aren't like real world maths anyway, so arguing we should make an operation like division more like real-world maths doesn't make much sense to me.
However, int/int = int makes a lot of sense to me, coming from C and C++. Those are languages whose explicit goal is to make you pay for what you use, but not for anything more. Back when C was specified there were no FPUs in processors. Floating point number operations were exceedingly expensive. They're still more expensive than their integer counterparts. So yes, keeping division of integer operands an integer makes a lot of sense.
Note: I'm only arguing C/C++ here, not Python, as one of the OPs mentioned C/C++, and the reason for the integer division is simply speed/efficiency and giving the programmer control over the runtime cost.
Posted Mar 24, 2014 17:21 UTC (Mon)
by quanstro (guest, #77996)
[Link]
floating point is problematic. it's not a ring. not abelian. and has all sorts of other ... rather unexpected properties.
Posted Mar 20, 2014 8:58 UTC (Thu)
by dakas (guest, #88146)
[Link] (13 responses)
Posted Mar 20, 2014 12:34 UTC (Thu)
by zuki (subscriber, #41808)
[Link] (12 responses)
Posted Mar 20, 2014 12:51 UTC (Thu)
by apoelstra (subscriber, #75205)
[Link] (11 responses)
It seems very counterintuitive to me that mathematical operators would change numeric types, I'd expect that adding extra magic type-conversion rules to a language in which everything type-related is already done magically will increase your potential for bugs.
Posted Mar 20, 2014 13:31 UTC (Thu)
by zuki (subscriber, #41808)
[Link] (8 responses)
> It seems very counterintuitive to me that mathematical operators would change numeric types
I don't understand that... In "real" math, integers are also reals, and 5 is exactly the same as 5.0, and 4.999..., and nothing should change if one is substituted with the other. And operations *do* change type, e.g. with division, raising to the power or logarithm with might get a (non-integer) real from integers, and on the other hand, floor and ceiling and division or multiplication, we might get an integer from reals... I like it when Python keeps up this illusion, whenever feasible.
Posted Mar 20, 2014 15:43 UTC (Thu)
by renox (guest, #23785)
[Link]
Posted Mar 20, 2014 16:16 UTC (Thu)
by apoelstra (subscriber, #75205)
[Link] (6 responses)
Often there are implicit embedding maps from, as in your example, the integers into the reals. As long as they are actual embeddings (i.e. injective and a homomorphism of the appropriate type) they are left implicit, but otherwise you need explicit maps to move between types. And in fact this type information can cause confusion; exponentiation might map from a group of numbers under addition to a group under multiplication --- and if these groups have the same (or nearly the same) underlying set, it can cause confusion and bad logical implications, for exactly the same reason that type conversions are a source of bugs in programming.
So it is nothing new that 2 and 2.0 are different types in programming. (Though the truncating behaviour of '/' is pretty weird --- in mathematics the integers are not a division ring and division is not done on them in general.) And in programming there are dramatic differences: for one thing, there is no 'real' type since almost all reals cannot be described in finite information, while every integer can. With floating point types, all arithmetic operations are approximations (and their errors can compound in surprising ways) while integer operations are precise (everywhere that they are defined).
Posted Mar 20, 2014 20:28 UTC (Thu)
by nybble41 (subscriber, #55106)
[Link] (5 responses)
That's certainly an interesting way to look at it. Usually one doesn't consider the set an object was *selected from* to be an identifying characteristic of the object. The same object can belong to multiple sets, and, mathematically speaking, there is no difference between 2, 2/1, and 2.0. The number 2 is a member of the set of integers, and the set of rationals, and the set of reals, all at the same time. It has a square root in the set of reals, but not in the set of integers. It's a unit in the rings of rationals and reals but not in the ring of integers. The set qualifier goes with the property (has an integer square root, is a unit in the ring of rationals), not the number.
I'm not quite sure what you mean when you say that "rational 2" is not prime... "rational 2" is the same number as "integer 2", which is a prime number. If you're referring to a different kind of prime, like a prime field, then you'll have to be more specific before you can classify 2 as prime or not.
Posted Mar 21, 2014 15:10 UTC (Fri)
by apoelstra (subscriber, #75205)
[Link]
The reason I look at it this way is because I have encountered "type errors" when building mathematical proofs, for example when working with two groups whose group operations are both denoted as multiplication. The extra mental effort to tag every object with its originating set, in order to see which operations are available and meaningful, feels exactly like the extra mental effort to analyze code in an implicitly typed language.
Often hypotheses in mathematical proofs are actually type declarations, as in "Let M be a compact closed manifold". Early "proofs" before the late 19th century would lack this type information (in fact people were unaware that they needed it) and the result was an enormous body of confused and incorrect mathematical work. For example it was common practice for "proofs" to have corresponding lists of counterexamples. This is evidence that it's very easy and natural for people to lapse into defective reasoning when faced with implicit typing. Though of course programmers have a big advantage over 18th-century mathematicians in that we know not only that the types are there, but also a complete (and small) list of all available types.
Maybe this is a confused analogy. But I don't think it is, and it's helpful to me in both mathematics and programming.
> I'm not quite sure what you mean when you say that "rational 2" is not prime... "rational 2" is the same number as "integer 2", which is a prime number. If you're referring to a different kind of prime, like a prime field, then you'll have to be more specific before you can classify 2 as prime or not.
It's a bit of an aside, but I mean prime in the sense of commutative rings: an element p is prime if it is (a) nonzero and not a unit, and (b) if p divides ab, it divides one of a or b.
In the ring of integers 2 is prime; in the ring of rationals 2 is not (because it is a unit, which means it has a multiplicative inverse).
Posted Mar 22, 2014 0:03 UTC (Sat)
by mathstuf (subscriber, #69389)
[Link] (3 responses)
Well, in algebra "1 - 1 + 1 - 1 + …" is undefined since algebra cannot reach the end; in the realm of infinite series, ½ is a valid value for the series (and the only one). There's a similar thing with any of ζ(n) when n is a negative odd integers (ζ(-1) = 1 + 2 + 3 + 4 + … = -1/12, ζ(-3) = 1 + 8 + 27 + 64 + … = 1/120, etc.). Computations definitely rely on the context in which they reside; I see no reason why numeric values would be exempt.
Posted Mar 22, 2014 9:54 UTC (Sat)
by Jandar (subscriber, #85683)
[Link] (2 responses)
Posted Mar 22, 2014 10:46 UTC (Sat)
by mathstuf (subscriber, #69389)
[Link] (1 responses)
[1]https://en.wikipedia.org/wiki/Particular_values_of_Rieman...
Posted Mar 22, 2014 15:23 UTC (Sat)
by Jandar (subscriber, #85683)
[Link]
Posted Mar 21, 2014 10:42 UTC (Fri)
by moltonel (guest, #45207)
[Link] (1 responses)
Do you also find it intuitive that adding 1 to a big enough value yields a very low negative value ? Some languages, python included, will convert that machine-level integer into an object that can store arbitrarily long numbers (BTW, it's transparent in python3, but python2 appends an 'L' suffix to that number), and that is a good thing in most cases.
Type-changing operators are actually common and expected, as long as they follow some common sense (how about the classic "date - date = duration" for example ?). And common sense says that numbers are numbers are numbers.
You only find "2/3=0" intuitive because you've been formated by years of using languages where this is the case for technical reasons. But tell that to any non-programmer or user of other languages and they'll be asking "WTF?". Python3 fixed that gotcha. This is a Good Thing.
Posted Mar 21, 2014 15:37 UTC (Fri)
by apoelstra (subscriber, #75205)
[Link]
I really don't like that in C, signed integer overflow is undefined behaviour -- in this case, I can't be sure of anything after the addition. :(
I like your "date - date = duration" example. I think you're right that POLS says division should have a floating-point output (and the truncation behaviour should be relegated to some other construct, e.g. a truncate() function), since what '/' does to ints in C is not, in fact, division.
In this case I can still be sure of the types of objects before and after the operation --- of course there is no intrinsic reason that these should be the same.
Posted Mar 24, 2014 13:58 UTC (Mon)
by moltonel (guest, #45207)
[Link]
And these are only items that were well known and discussed while moving to python3. As the lwn article shows, it isn't an exaustive list. See also https://wiki.python.org/moin/Python2orPython3
Posted Mar 12, 2014 19:37 UTC (Wed)
by smurf (subscriber, #17840)
[Link]
Posted Mar 12, 2014 19:03 UTC (Wed)
by dskoll (subscriber, #1630)
[Link] (17 responses)
I've seen many instances of:
Posted Mar 12, 2014 20:57 UTC (Wed)
by Tara_Li (guest, #26706)
[Link]
Neat trick may be neat, but it doesn't really make things *better* or *clearer*.
Posted Mar 12, 2014 22:34 UTC (Wed)
by gerdesj (subscriber, #5446)
[Link] (1 responses)
To avoid corner cases like that or effects that are well known to "experts" but not me I always spell out tests as simply and unambiguously as possible with the emphasis on unambiguously. I can generate bugs at quite a rate myself without trying to be clever.
Now, times are especially weird as a data type due to the myriad rules and the modulo effect and to even contemplate trying to squeeze in a notion of one time being true whilst another false looks bloody stupid to me.
It looks to me like the clever buggers who test the existence of a var with if $var are just plain lazy. If it's a common construct then I think it's probably a really stupid one. I think that you are effectively wanting to test a variable's existence by comparing its value with true/false which are two different things.
Surely you should test for "is not null" (or whatever syntax is necessary in the language concerned) which implies existence or test the contents of the variable. Trying to do both at once clearly ends in tears eventually or ends up with strange discussions about date/times that might be true or false
Cheers
Posted Mar 12, 2014 23:06 UTC (Wed)
by dchichkov (guest, #90878)
[Link]
Posted Mar 12, 2014 22:58 UTC (Wed)
by bracher (subscriber, #4039)
[Link] (9 responses)
$ perl -e 'use DateTime; my $d = DateTime->now(time_zone => "UTC")->set(hour => 0, minute => 0, second => 0); print $d ? "true" : "false","\n";'
which is the whole point of the hoopla in python-land, that doing otherwise is non-sensical at best...
Posted Mar 12, 2014 23:26 UTC (Wed)
by dskoll (subscriber, #1630)
[Link]
DateTime object with time set to UTC midnight returns true as most sane observers expect:
That's 00:00:00.
The preceding sentence, of course, means "That's true."
I think you get my point. The whole notion of treating non-Boolean variables as "truthy" or "falsy" opens one up for a whole class of bugs.
Posted Mar 13, 2014 20:25 UTC (Thu)
by jimparis (guest, #38647)
[Link]
Posted Mar 14, 2014 17:37 UTC (Fri)
by jtc (guest, #6246)
[Link] (6 responses)
The object you're testing, $d, is a DateTime object, while in the given python example, the object is being tested is a time object, so your example is not equivalent. (You might think $d->time would produce a Time object and thus be equivalent, but it just produces a string that evaluates to true.) DateTime in perl does not (apparently) use or expose a Time object, so there perhaps is no equivalent comparison. (False dates - as opposed to times - are another issue - but we might have a hard time defining what is a 0 date.)
However, if you evaulate a zero hour, minute, or second in perl you do get false, as this example shows:
#!/bin/perl
my $d = DateTime->new(time_zone => 'UTC', year => 0, month => 1, day => 1,
Posted Mar 14, 2014 18:13 UTC (Fri)
by mbunkus (subscriber, #87248)
[Link] (5 responses)
Therefore your comparison is a bit besides the point.
Posted Mar 14, 2014 22:44 UTC (Fri)
by jtc (guest, #6246)
[Link] (4 responses)
The point of my post was simply to point out that bracher's example compared a DateTime in perl with a Time in python - two different classes with different semantics - and thus not a valid comparison. I wasn't addressing the article - at least directly.
To make this point only my first sentence was needed ("The object you're testing ... not equivalent"). The rest was a - perhaps unnecessary - elaboration of the types involved with respect to DateTimes and Times in python vs perl (and yes, hour, minute, and second in perl evaluate to integers) and how, when it comes to Boolean evaluation, this leads to complexity and possible confusion. I'll grant that this is off-topic from the article.
On the other hand, I think it might be relevant (to the article) to point out that such complexity and confusion that result from regarding some instances of zero-ness as 'false' and other instances as 'not false' can cause problems that lead to defects and that it's wise not to use this idiom, at least for large projects. (Obviously, this puts me on the side of those arguing for midnight not evaluating to false in python, although I go even further to say "don't allow such an idiom in the code") The best way to avoid the idiom is to not allow it in the language (as is the case in statically-typed languages like Java [though not the case in C or C++]), but that's probably not practical for perl or python (or ruby, where 0 is true), since much existing code, obviously, uses these idioms. In that case all you can do, I suppose, is follow a coding standard that outlaws such comparisons (and perhaps use a lint-like tool to find violations).
(I elaborated on this point a bit more in another post:
Posted Mar 15, 2014 8:30 UTC (Sat)
by mbunkus (subscriber, #87248)
[Link] (3 responses)
C: the value 0; C++: 0 / nullptr (C++11 and newer) or depending on the class if operator bool is overloaded; Perl: undef / () / 0 / "0" (but not "0E0" or other strings which evaluate to 0 if taken in a numeric context) or depending on the class if bool conversion is overloaded; Lisp: depending on the Lisp dialect in question, somethings nil and the empty list (), sometimes only nil, sometimes nil and the Boolean type false as in Clojure); Ruby: nil and the Boolean false…
Therefore I consider saying anything along the lines of »0 is always/never supposed to be false« to be the wrong approach.
I fully agree that Boolean conversion for instances of a time-based classes (no matter if it's just the time component or a date as well or even both of them) is harmful. Remembering special rules like this leads to mistakes, it always does. There are useful cases for this kind of overloading, but they're rare: e.g. a TriBool class representing True/False/Unknown.
Posted Mar 15, 2014 10:35 UTC (Sat)
by peter-b (subscriber, #66996)
[Link] (1 responses)
> Of all the standard Scheme values, only #f counts as false in conditional expressions. Except for #f, all standard Scheme values, including #t, pairs, the empty list, symbols, numbers, strings, vectors, and procedures, count as true.
The reason I like this is that it removes all uncertainty about what, exactly, a conditional expression is testing on.
Posted Mar 15, 2014 15:03 UTC (Sat)
by khim (subscriber, #9252)
[Link]
My position is different: I'm Ok with multiple “false” values in statically-typed languages (if any single type can have exactly one “false” value) but to me it looks like crazy thing to do with dynamically typed languages! I mean: if I see something like But with dynamically typed languages this justification flies right out of the window: you can pass around “real” “false” everywhere, why would you need surrogate ones? They will just complicate everything for no good reason! Perl, python, php - they all adopted C concept of “zero of any type is “false”” without thinking when they had no need for that kludge! This is a pity, really, but I'm afraid we are stuck with this decision: such change when applied to popular types will just break way too many programs.
Posted Mar 16, 2014 5:43 UTC (Sun)
by jtc (guest, #6246)
[Link]
I agree.
"I fully agree that Boolean conversion for instances of a time-based classes ... is harmful. ..."
We appear to be in 100% agreement. :-)
Posted Mar 13, 2014 10:05 UTC (Thu)
by intgr (subscriber, #39733)
[Link] (3 responses)
Note that in Python, the string "0" is considered True (but integer 0 is False). It's consistent because in most contexts there is no implicit casting from str to int.
Posted Mar 14, 2014 19:21 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link] (2 responses)
What contexts do?
Anyways, while we're in Python's deeper, darker corners, some things I really dislike about Python:
bool('False') -> True (ast.parse_literal is needed) whereas int('1') -> 1
Posted Mar 17, 2014 12:13 UTC (Mon)
by intgr (subscriber, #39733)
[Link] (1 responses)
Actually I can't think of any from the top of my head. I just said "most" because I suspect there are a few edge cases in the standard library; Python's typing isn't always as strict as people make it out to be. Implicit casts to str are quite common, for example.
> bool('False') -> True (ast.parse_literal is needed) whereas int('1') -> 1
Why do you think this should return False? It doesn't work this way for most other built-in types either, consider list('[1,2]'). The fact that it works for numeric types is a coincidence, because that's a useful way to represent numbers. It never was a goal for sometype(str(value_of_sometype)) return the original value again.
I think using bool(x) to get the truth value of x makes lots of sense, just like list(iterable) does.
> there is no way to pass non-None arguments to str.split and get the no-arg behavior
Agreed, that is inconsistent.
Posted Mar 17, 2014 13:13 UTC (Mon)
by mathstuf (subscriber, #69389)
[Link]
Casts for compound types makes sense since the internal type to parse out is very ambiguous. I would have thought bool would follow int since they're both POD-ish. It bit me in a config parser (it was our pre-existing format, so the stdlib module wasn't going to work) where I passed a function to use to cast the value out from the parse. You could use int, but anything else needed a wrapper function.
Posted Mar 13, 2014 1:17 UTC (Thu)
by kenmoffat (subscriber, #4807)
[Link]
OTOH, my commiserations to anybody affected by this. Choose your language with care, and then suffer the consequences - whichever language you choose, at some point there will be an excrement/fan interface.
Posted Mar 13, 2014 9:58 UTC (Thu)
by l0b0 (guest, #80670)
[Link]
hours * 60 + minutes (integer)
The second interpretation would make errors 60 times less likely to occur, and if the fractional seconds are taken into account the probability is very small indeed. The documentation does not mention how the conversion is done.
Posted Mar 13, 2014 13:57 UTC (Thu)
by dmarti (subscriber, #11625)
[Link] (1 responses)
Posted Mar 13, 2014 19:00 UTC (Thu)
by dlang (guest, #313)
[Link]
Posted Mar 13, 2014 16:55 UTC (Thu)
by fandingo (guest, #67019)
[Link] (8 responses)
>>> bool(datetime.date(datetime.MINYEAR, 1, 1))
That alone should be a reason to get rid of falsy behavior.
Posted Mar 13, 2014 18:08 UTC (Thu)
by pboddie (guest, #50784)
[Link] (2 responses)
Time-related stuff in the Python standard library (and extensions) has been suffering for quite some time. I'm not even sure whether elementary changes were ever made to make the simpler time structures aware of time zones, which was something I looked at adding many years ago now: the whole process just stalled, bugs were shuffled around, and requests made to re-target the patches for Python 3.x, because by the time people got interested again there was no core developer interest to "enhance" Python 2.x.
It's like cross-compilation support: patches sat around for a very long time, some even got applied, but despite all this such things are "enhancements" and thus ineligible for Python 2.x even though we'd have been in bug-fixing mode had things been considered and applied in a timely fashion.
Posted Mar 14, 2014 1:18 UTC (Fri)
by Karellen (subscriber, #67644)
[Link] (1 responses)
Or like treating every floating point value which happens to be an exact integer as false.
Posted Mar 14, 2014 17:37 UTC (Fri)
by duelafn (subscriber, #52974)
[Link]
However, the comparison to a float where every int is false is not exactly a fair comparison since a datetime is always True. Compare instead to a Decimal implemented as a pair: (DecimalIntPart, DecimalFractionalPart). Then making the DecimalFractionalPart be False when the Decimal is an integer is much more reasonable:
if my_decimal.fractional_part: print("Is not an integer")
It, to me, makes the original implementation seem less "completely insane" and more "not the best choice".
Posted Mar 14, 2014 17:55 UTC (Fri)
by jtc (guest, #6246)
[Link] (4 responses)
I don't think it makes sense that the start of a day (or of a Time, for that matter) should be false. So I'd recommend we not accept that argument. If the start of something can be considered false, then a Baby object, at the moment it is born (age 0) is false. The next moment it's true. How does that make sense? (Perhaps it could make sense in some contexts - that is, for some subset of possible applications; but the idea here, I believe, is to provide the most general definition possible. E.g., if a Time == 0 state in a quantum physics application makes sense as false, that doesn't mean that it would be false in a train-scheduling application.)
Posted Mar 14, 2014 18:17 UTC (Fri)
by fandingo (guest, #67019)
[Link] (3 responses)
Let me explain more why I came up with that specific argument. I read through the entire python-ideas thread, which I highly discourage anyone else from doing. The people arguing for falsey behavior fundamentally believed that midnight was a special time. There was a lot of disagreement back and forth on that subject.
Rather than attempting to walk into that line of reasoning, which seems intractable, I chose a different path that sidesteps it. So even though I don't believe it, I concede for the moment that midnight is special. That should mean that all date objects should have have this special condition at their minimum value and be False. Yet, they are always true. Thus, the rationale has been inconsistently applied and would need to be corrected, which refutes the argument that there is a compatibility, testing, and development cost to removing falsey behavior because it would need to be added to other classes.
Additionally, since months are not 0-indexed (i.e. 0-11), and datetime.MINYEAR is 1, it exposes another hole in the "midnight is special" argument. For consistency 1 CE January 1st, should be False, but that's not made up of all zeroes.
The point of my argument was that there's no need to get into the deep philosophical arguments of whether midnight is special and the wider discussion about what should be falsey. The most basic argument is that falsey behavior for datetime.time is inconsistent with the other datetime classes, and there should be consistency among them. Therefore, the whole depreciation and compatibility break has to happen anyways, and basically everyone (including Guido) agrees that if this were being redesigned falsey behavior would not be allowed.
There are a litany of other reasons that falsey behavior is bad.
Posted Mar 14, 2014 21:24 UTC (Fri)
by jtc (guest, #6246)
[Link] (2 responses)
As far as your main point, I think your example/argument of dates never evaluating to false while times sometimes do is valid. On the other hand, someone might argue that something that can have a 0 value (such as a time) should be false when it's 0. But a date cannot really have a 0 value (since, as you implied, there is no 0th day of year 0 [disregarding whether there can actually be a year 0] and no 0th month - so you can never have a date [year/month/day] that evaluates to 0) and thus it's appropriate for a date to never evaluate to false. (This is not my argument, but one I think some people would make.)
To address that last argument, let me bring in the Baby object from my earlier post. You might have Baby == 0 (AKA baby.age == 0) and you might think of that as false. But why? What does false mean? It exists [otherwise it would be null/void and not evaluate to anything :-)] - doesn't existence imply truth?
I suppose I'm actually generalizing the argument - partly because some people will object to your argument for the reason I gave. I suggest that for a system that will be around for years and will be modified and extended, Boolean checks should be explicit (i.e., there should be no auto-conversion of a type into Boolean) - Instead of if (not currenttime), the code should read, for example: if (currenttime.is_midnight). IMO, this is clearer and much less error-prone. It's less likely that someone changing the code will make a mistake because the intent of the if ... clause wasn't clear to him. (I suppose you could say this is a long-winded way of saying what someone else suggested earlier: Make the code readable.)
I think part of the problem is the dual nature of languages such as perl and python (and, to some extent, ruby) - One purpose of these languages is to allow writing informal scripts (usually short and quick) that take care of an immediate need. The other is to develop applications, many of which will be quite large and will be around for a long time, and will be used by many people. For the first purpose it perhaps makes sense to allow, e.g., 0 => false; but for major projects I think it might not be a good idea.
Posted Mar 15, 2014 0:22 UTC (Sat)
by fandingo (guest, #67019)
[Link] (1 responses)
> But a date cannot really have a 0 value (since, as you implied, there is no 0th day of year 0 [disregarding whether there can actually be a year 0] and no 0th month - so you can never have a date [year/month/day] that evaluates to 0) and thus it's appropriate for a date to never evaluate to false. (This is not my argument, but one I think some people would make.)
datetime.MINYEAR could be defined as 0, and I'm actually a little surprised that it is 1 because that seems incredibly arbitrary. As for having zero months and days, it's about about how you represent it. Months kind of naturally map this way if you have a list that represents the month names because 0-indexed makes that easier. Anyways, whether a month is zero-indexed or a time is tracked in a 12-hour AM/PM representation doesn't matter. That's the internal state of the object, which shouldn't leak out to users, but it does in the form of __bool__ for datetime.time.
> To address that last argument, let me bring in the Baby object from my earlier post. You might have Baby == 0 (AKA baby.age == 0) and you might think of that as false. But why?
It's obvious that some people (like Tim Peters) think this sort of behavior makes sense, but I would never write a class that did something like that. As you say, it's horribly unintuitive. If I read the line
if not my_baby:
there is a 0% that I would ever think that my_baby would be anything other than True or None (well, False if used as a sentinel but never due to __bool__). (And, I know that many people get upset of not using the pedantic "if my_baby is not None," but there's no reason for that if the object has no business being falsey.)
> It exists [otherwise it would be null/void and not evaluate to anything :-)] - doesn't existence imply truth?
I don't agree with this line of reasoning, which you go onto explain in more detail. The problem is that you're basically defining falsey behavior as NameError. That's actually an interesting thought where the sentinel None could be eliminated and a wider use of NameError used, but it would require substantial changes to Python and the development process. (You'd have to stop functions from implicitly returning None, get people to use exception handling far more, and so on.)
> Instead of if (not currenttime), the code should read, for example: if (currenttime.is_midnight). IMO, this is clearer and much less error-prone. It's less likely that someone changing the code will make a mistake because the intent of the if ... clause wasn't clear to him. (I suppose you could say this is a long-winded way of saying what someone else suggested earlier: Make the code readable.)
I take it a step further and believe that stdlib classes have no business implementing this sort of specialized behavior.
if currenttime == datetime.time(0, 0, 0):
That seems like unambiguously the correct answer. There are no surprises for the author or any subsequent readers.
> For the [informal script] purpose it perhaps makes sense to allow, e.g., 0 => false; but for major projects I think it might not be a good idea.
Yet, aren't the informal script writers the *least* likely to know about this behavior and also have the least tested code that is more likely to fall victim to unintentionally falsey behavior? Nick Coghlan said it best in the original article:
>> [...]having a caveat in your documentation isn't going to help, because people aren't even going to think to ask the question.
Posted Mar 20, 2014 15:54 UTC (Thu)
by Wol (subscriber, #4433)
[Link]
Although the Western calendar has positive and negative years, AD and BC, it's interesting that it doesn't have a year 0. Although when you look at the history this is no surprise. The current numbering system from the (assumed) date of Jesus' birth was implemented at a time when there was no concept of 0.
Actually, this would be a perfect rational for why year 0 should be false :-)
There is no symbol in Roman Numerals for 0, and iirc, it was introduced to Western mathematicians round about 1000AD, some 600 years after the calendar was implemented.
Cheers,
Posted Mar 13, 2014 20:41 UTC (Thu)
by mgedmin (subscriber, #34497)
[Link] (1 responses)
Posted Mar 14, 2014 2:03 UTC (Fri)
by Tara_Li (guest, #26706)
[Link]
Posted Mar 18, 2014 4:49 UTC (Tue)
by ldo (guest, #40946)
[Link] (1 responses)
This is why conditional expressions (as in if- and while- statements) must be required to be of Boolean type, and languages that don’t do this right are fundamentally flawed.
Languages that get this wrong, by allowing non-Boolean conditional expressions: C, C++, Perl, Python ...
Languages that got this right, with a sane treatment of Booleans as a subclass of enumerations as a subclass of discrete types: Pascal, Modula-2.
Language that gets this wrong, by its too-strict treatment of Booleans and enumerations generally: Java.
Posted Mar 18, 2014 13:50 UTC (Tue)
by mathstuf (subscriber, #69389)
[Link]
Posted Mar 21, 2014 8:21 UTC (Fri)
by abket (guest, #95676)
[Link]
In reasonable languages, you can only test boolean values, and nothing is implicitly converted to them.
But anyway, this is a language where variable assignment is effectively best-effort, since any typo in the variable name will make it have no effect without warning, so that's the least of the problems.
Don't use Python.
This whole this is not hilarious, it's sad. All that hoopla clearly does not affect Python 2 (which most people are using) so what difference does it make?
A false midnight
A false midnight
Bug / misfeatures in in Python 2 but nobody @ python-ideas even discuses that: note how they talk about Python 3.5 or may be Python 3.6, but “of course” not about Python 2.
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
Which has a lot less scope for pain (mostly, I suspect, relating to the behaviour of the optimizer) than upgrading from Python 2 to Python 3 does. gcc 3.4 supports ISO C90, ISO C99, and ISO C++98, and so does gcc 4.9. Version 3.4 of the Python interpreter does not support the version of the Python language supported by version 2.7 of the Python interpreter, and a quick look suggests that the available machine-translation approach is in the realms of "good, but definitely not fully reliable".
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
http://pythonhosted.org/six/#six.with_metaclass
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
Yes, it's similar gotcha. But there are a difference: A false midnight
from __future__ import division
handlily fixes problems with division, but you count not do anything like that to fix problems with DateTime.
A false midnight
A false midnight
A false midnight
A false midnight
I think this is the same gotcha as the article shows.
A false midnight
3/4 has nothing to do with strong typing, but it is related to the type of A false midnight
operator/(int, int)
... It's just like defining complex operator+(real r, imaginary i)
and having auto c = 3 + im(4);
yielding a complex "c".
A false midnight
A false midnight
A false midnight
Wol
A false midnight
A false midnight
A false midnight
and in all strongly-typed languages I know of the division operation doesn't change type.
Algol and Pascal come to mind right away.
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
Affects Perl too
# Ignore unless $var is defined and non-empty
if ($var) {
# ... do something
}
which can fail if $var is the string "0". I religiously use constructs like
if (defined($var)) or if ($var ne '') depending on what I mean.
Affects Perl too
Affects Perl too
Jon
Affects Perl too
Affects Perl too
true
Affects Perl too
Affects Perl too
Perl considers the time "00:00:00" true, and the string "0" false.
Python considers the time "00:00:00" false, and the string "0" true.
Affects Perl too
use Modern::Perl;
use DateTime;
use Data::Dumper;
sub execute {
my ($o, $msg) = @_;
say "$msg: ", Dumper($o);
say 'o: ', ($o? 'true': 'false');
say '=' x 66;
}
hour => 0, minute => 0, second => 0);
execute($d, 'year 0 ...'); # true
execute($d->time, 'time 0 ...'); # true
execute($d->second, 'second 0 ...'); # false
execute($d->minute, 'minute 0 ...'); # false
execute($d->hour, 'hour 0 ...'); # false
execute($d->hour + $d->minute + $d->second, 'h+m+s 0 ...'); # false
Affects Perl too
Affects Perl too
http://lwn.net/Articles/590778/ )
Affects Perl too
Affects Perl too
Affects Perl too
if (i/*int*/) …
or if (p/*pointer*/) …
in C I know that there can be exactly one value which will be interepreted as false in this particular place. That is: language has many possible false
values, but each particular point in a program can employ just one of them! I dislike tricks like std::ios::operator bool
for that reason (yes, it's clever and sometimes it looks cool, but it erodes concept of “false” value).Affects Perl too
Affects Perl too
Affects Perl too
str.split() is approximately filter(len, split(' ')) (there is no way to pass non-None arguments to str.split and get the no-arg behavior)
Affects Perl too
Affects Perl too
A false midnight
Conversion
hours * 60 + minutes + seconds / float(60)
hours * 60 + minutes + seconds / float(60) + fractional_seconds / float(60)
Of course you shouldn't schedule anything important for exactly midnight anyway...
No scheduled flights at 00:00
No scheduled flights at 00:00
A false midnight
> True
>>> bool(datetime.datetime(datetime.MINYEAR, 1, 1))
> True
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
A false midnight
Wol
A false midnight
A false midnight
This Is Why Booleans Must Be Booleans
This Is Why Booleans Must Be Booleans
A false midnight