|
|
Subscribe / Log in / New account

A false midnight

By Jake Edge
March 12, 2014

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:

in Boolean contexts, a time object is considered to be true if and only if, after converting it to minutes and subtracting utcoffset() (or 0 if that's None), the result is non-zero.

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 time
The 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 time
But "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:

There's a great saying in the usability world: "You can't document your way out of a usability problem". What it means is that if all the affordances of your application (or programming language!) push users towards a particular logical conclusion (in this case, "datetime.time values are not numbers"), having a caveat in your documentation isn't going to help, because people aren't even going to think to ask the question. It doesn't matter if you originally had a good reason for the behaviour, you've ended up in a place where your behaviour is confusing and inconsistent, because there is one piece of behaviour that is out of line with an otherwise consistent mental model.

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:

If I had to do it over again I would *definitely* never make a time value "falsy". The datetime module was conceived long ago, when the dangers of falsy objects that weren't clearly trying to be "like numbers" or "like collections" weren't so clear. Today I would argue that times aren't enough "like numbers" to qualify.

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.



to post comments

A false midnight

Posted Mar 12, 2014 18:26 UTC (Wed) by khim (subscriber, #9252) [Link] (45 responses)

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

Posted Mar 12, 2014 19:14 UTC (Wed) by rswarbrick (guest, #47560) [Link] (2 responses)

I'm no Python fan, but your statement is factually incorrect. This bug / misfeature is in Python 2 as well:

http://docs.python.org/2/library/datetime.html#time-objects

(probably version 2.3, if I'm understanding the documentation correctly)

A false midnight

Posted Mar 12, 2014 19:32 UTC (Wed) by khim (subscriber, #9252) [Link] (1 responses)

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

Posted Mar 12, 2014 23:17 UTC (Wed) by nikarul (subscriber, #4462) [Link]

Why would they? Python 2 is officially in maintenance mode, so no new features are going in, and a backwards compatible break is exactly what Python 2 users would probably like to avoid.

A false midnight

Posted Mar 12, 2014 19:19 UTC (Wed) by zuki (subscriber, #41808) [Link] (41 responses)

In what way it doesn't? This "bug" (or feature) is present in Python 2.

A false midnight

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?

A false midnight

Posted Mar 12, 2014 19:47 UTC (Wed) by zuki (subscriber, #41808) [Link] (6 responses)

If a bug was found in gcc 3.4, the solution would be the same: move to some current version, the bug will be fixed there.

A false midnight

Posted Mar 12, 2014 20:12 UTC (Wed) by dlang (guest, #313) [Link]

the difference is that gcc at least pretends to have backwards compatibility (to be able to keep working with older source code), python 3 explicitly doesn't, so it's not nearly as simple.

A false midnight

Posted Mar 12, 2014 20:29 UTC (Wed) by mpr22 (subscriber, #60784) [Link] (4 responses)

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

Posted Mar 13, 2014 0:56 UTC (Thu) by zuki (subscriber, #41808) [Link] (3 responses)

The "distance" between Python 2.7 and 3.3 is not that great, and certainly many times smaller than between 2.5 and 3.0. Writing code that runs on latest 2.x and 3.x is not too complicated. It won't be able to use some stuff that is in Python 3 only, but the code is nice, without too many kludgy workarounds.

A false midnight

Posted Mar 13, 2014 4:33 UTC (Thu) by dlang (guest, #313) [Link]

the issue isn't writing something new, it's continuing to run older stuff without re-writing it.

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.

A false midnight

Posted Mar 14, 2014 19:16 UTC (Fri) by mathstuf (subscriber, #69389) [Link] (1 responses)

How do you do metaclasses supporting both? Python3's way is a syntax error in Python2 and Python2's way is ignored in Python3.

A false midnight

Posted Mar 15, 2014 12:58 UTC (Sat) by edomaur (subscriber, #14520) [Link]

Well, you can use the six library, there is some tools for that very case :
http://pythonhosted.org/six/#six.with_metaclass

A false midnight

Posted Mar 12, 2014 23:20 UTC (Wed) by pboddie (guest, #50784) [Link]

Don't worry: it'll surely get fixed in the not-to-be-spoken-of Python 2.8, even if it ends up being called something other than "Python".

A false midnight

Posted Mar 13, 2014 11:46 UTC (Thu) by moltonel (guest, #45207) [Link] (30 responses)

> 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?

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.

A false midnight

Posted Mar 13, 2014 17:55 UTC (Thu) by pboddie (guest, #50784) [Link] (29 responses)

Which problems are these? Maintenance-related problems or something else?

A false midnight

Posted Mar 13, 2014 22:43 UTC (Thu) by moltonel (guest, #45207) [Link]

I don't have a specific example in mind, but using software that upstream only reluctantly maintains is never a good idea. That midnight issue is one example, but I'm sure there are others. The string/binary distinction in python3 is much safer, and there are plenty of other changes that make your life better.

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.

A false midnight

Posted Mar 13, 2014 22:51 UTC (Thu) by kokada (guest, #92849) [Link] (26 responses)

Well, various language decisions that maybe made sense when the only other major language was C, but nowadays don't make sense anymore.

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.

A false midnight

Posted Mar 14, 2014 10:28 UTC (Fri) by khim (subscriber, #9252) [Link] (1 responses)

Yes, it's similar gotcha. But there are a difference: 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

Posted Mar 14, 2014 16:09 UTC (Fri) by hummassa (subscriber, #307) [Link]

Well, actually... nothing keeps you from doing a pragmatic "fixdatetime" module and "from fixdatetime import fixdatetime" so that DateTime.__notzero__() is redefined to a saner default for the scope of your programa, isn't it?

A false midnight

Posted Mar 14, 2014 18:38 UTC (Fri) by pboddie (guest, #50784) [Link] (2 responses)

Integer division has nothing to do with the datetime problem described, or at least it isn't the cause of it. The truth value of an object is determined by the result of the __nonzero__ method in Python 2, perhaps with some exceptions for the built-in types (I haven't looked at the CPython code for a while) that produce the same effect anyway.

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.)

A false midnight

Posted Mar 16, 2014 15:31 UTC (Sun) by kokada (guest, #92849) [Link] (1 responses)

Well, OP asked for similar gotchas on Python 2.x language, not what is the reason of the problem (that is explained on the text anyway).

A false midnight

Posted Mar 18, 2014 20:19 UTC (Tue) by pboddie (guest, #50784) [Link]

I was explicitly responding to the assertion...

I think this is the same gotcha as the article shows.

...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.

A false midnight

Posted Mar 17, 2014 13:07 UTC (Mon) by ikm (guest, #493) [Link] (20 responses)

> Of course, the first example is C-like, while the second is what you'd expect from a non-typed language

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.

A false midnight

Posted Mar 18, 2014 1:18 UTC (Tue) by hummassa (subscriber, #307) [Link] (5 responses)

3/4 has nothing to do with strong typing, but it is related to the type of
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

Posted Mar 18, 2014 20:23 UTC (Tue) by pboddie (guest, #50784) [Link] (4 responses)

Yes, it's mostly something seen in C (and other languages) that became policy. Indeed, I imagine there's some group theory related elegance that appeals when defining such policy. Quite some time ago, of course, the decision to change this was made, but I can easily see people agitating for "int/int -> decimal" and "int/int -> fraction", and there are languages which have gone down those paths, too.

A false midnight

Posted Mar 19, 2014 0:29 UTC (Wed) by hummassa (subscriber, #307) [Link]

in C, "int/int -> int";

the whole "int/int -> float" thing came from Pascal/Modula via Algol IIRC... they had "int DIV int -> int" to compensate.

A false midnight

Posted Mar 20, 2014 15:35 UTC (Thu) by Wol (subscriber, #4433) [Link] (2 responses)

Given that, outside of programming, 3/4 DOES equal 0.75, then of course.

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,
Wol

A false midnight

Posted Mar 21, 2014 8:20 UTC (Fri) by mbunkus (subscriber, #87248) [Link] (1 responses)

Well, from the maths POV a lot of stuff in programming doesn't make sense. Underflows/overflows? All operations should operate on arbitrarily-sized numbers, of course. And 0.3 - 0.2 - 0.1 should always == 0.0, of course. And infinity handling could be better, too – you know, there are no exceptions in real life (or processor flags or…) ;)

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.

A false midnight

Posted Mar 24, 2014 17:21 UTC (Mon) by quanstro (guest, #77996) [Link]

from a math pov, programming DOES (generally) make sense. by generally, i mean with the exception of floating point. operations in math are defined over a thing. that thing might be a group, a field or a ring. so there is such a thing as division on Z (integers), and there is such a thing as division on R (real numbers), but Z is not a division ring. programming typically operates on Zn, where n = 2^m, m is typically one of 8,16,32,64 for unsigned numbers. signed numbers are similar. note that the fact that Zn is not a division ring (i.e. there's a n satisfying n = a/b for all a, b in Zn) does *not* make this strange to mathematicians.

floating point is problematic. it's not a ring. not abelian. and has all sorts of other ... rather unexpected properties.

A false midnight

Posted Mar 20, 2014 8:58 UTC (Thu) by dakas (guest, #88146) [Link] (13 responses)

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

Posted Mar 20, 2014 12:34 UTC (Thu) by zuki (subscriber, #41808) [Link] (12 responses)

I've been using mixed Python 2/3 environments recently, and after getting accustomed to the new division behaviour, when I use Python 2, the old truncating behaviour is one of the bug "sources". I've had to add 'from __future__ import division' more than once in the last few weeks to fix actual bugs in code. Of course one can say that I should simply remember to do a cast to float here and there, but at least for me, it feels much easier and results in fewer bugs to do an explicit truncating division with // when necessary.

A false midnight

Posted Mar 20, 2014 12:51 UTC (Thu) by apoelstra (subscriber, #75205) [Link] (11 responses)

I think in general if you are doing a project of sufficient complexity that you need to be aware of the type of your objects (but are not), Python is the wrong language for you, and something where types are explicitly described and tracked would be better.

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.

A false midnight

Posted Mar 20, 2014 13:31 UTC (Thu) by zuki (subscriber, #41808) [Link] (8 responses)

If I do a division, I *am* aware of the type... They are numbers, and specifically numbers.Real. In Python, both an int and and a float are subclasses of numbers.Real. For almost all intents and purposes, the difference between them is unimportant, but division is one them. Things might work when called with 5.0, but break if that argument changed to 6. I'd rather avoid that.

> 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.

A false midnight

Posted Mar 20, 2014 15:43 UTC (Thu) by renox (guest, #23785) [Link]

I think that the main reason why other language doesn't do this is that reals have several possible representations: float32, float64, float intervals, rationals,etc which make choosing the 'real' type for the division's result quite arbitrary..

A false midnight

Posted Mar 20, 2014 16:16 UTC (Thu) by apoelstra (subscriber, #75205) [Link] (6 responses)

Well, even in mathematics there are different 'types' corresponding to the set in which an object is supposed to lie. For example, the integer 2 is prime while the rational 2 is not; the rational 2 is a unit while the integer 2 is not. The real 2 is a square while the rational or integer 2 is not, etc. etc.

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).

A false midnight

Posted Mar 20, 2014 20:28 UTC (Thu) by nybble41 (subscriber, #55106) [Link] (5 responses)

> Well, even in mathematics there are different 'types' corresponding to the set in which an object is supposed to lie. For example, the integer 2 is prime while the rational 2 is not; the rational 2 is a unit while the integer 2 is not. The real 2 is a square while the rational or integer 2 is not, etc. etc.

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.

A false midnight

Posted Mar 21, 2014 15:10 UTC (Fri) by apoelstra (subscriber, #75205) [Link]

> 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 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).

A false midnight

Posted Mar 22, 2014 0:03 UTC (Sat) by mathstuf (subscriber, #69389) [Link] (3 responses)

> Usually one doesn't consider the set an object was *selected from* to be an identifying characteristic of the object.

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.

A false midnight

Posted Mar 22, 2014 9:54 UTC (Sat) by Jandar (subscriber, #85683) [Link] (2 responses)

ζ(n) with negative odd n is infinite.

A false midnight

Posted Mar 22, 2014 10:46 UTC (Sat) by mathstuf (subscriber, #69389) [Link] (1 responses)

Depends on the context[1] :) . Each series resulting from the ζ(n) with n as a negative, odd integer has a single, useful value which can be used in its place when it occurs in equations (physics, chemistry, etc.).

[1]https://en.wikipedia.org/wiki/Particular_values_of_Rieman...

A false midnight

Posted Mar 22, 2014 15:23 UTC (Sat) by Jandar (subscriber, #85683) [Link]

It's embarrassing to see how much one can forget. My mathematical studies lie some 20 years in the past and almost anything about sheaf and germs is lost.

A false midnight

Posted Mar 21, 2014 10:42 UTC (Fri) by moltonel (guest, #45207) [Link] (1 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.

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.

A false midnight

Posted Mar 21, 2014 15:37 UTC (Fri) by apoelstra (subscriber, #75205) [Link]

Integer overflow is a property of the integer type in many languages (though as you say, not Python). It's intuitive that a fixed-width type is going to overflow at some point, by the pigeonhole principle. In languages like Python or Lisp which hold arbitrarily-sized integers, this overflow is not a property of the type. In either case there is no type transition caused by adding 1 repeatedly to an integer, so I can be sure of the types of my objects before and after the addition.

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.

A false midnight

Posted Mar 24, 2014 13:58 UTC (Mon) by moltonel (guest, #45207) [Link]

Just stumbled upon a nice writeup of many python2 gotchas that got fixed in python3 : http://python-notes.curiousefficiency.org/en/latest/pytho...

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

A false midnight

Posted Mar 12, 2014 19:37 UTC (Wed) by smurf (subscriber, #17840) [Link]

… which is mostly-frozen, so discussing which version to fix the problem in simply don't mention Py2.

Affects Perl too

Posted Mar 12, 2014 19:03 UTC (Wed) by dskoll (subscriber, #1630) [Link] (17 responses)

I've seen many instances of:

# 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

Posted Mar 12, 2014 20:57 UTC (Wed) by Tara_Li (guest, #26706) [Link]

My sense is - unless it's a Boolean variable of some kind, or the Boolean result of comparisons & logical operations, it should be a full on syntax error to say "if variable" or even "if !novariable". 0 is not FALSE, -1 is not FALSE, the empty string or empty list is not FALSE.

Neat trick may be neat, but it doesn't really make things *better* or *clearer*.

Affects Perl too

Posted Mar 12, 2014 22:34 UTC (Wed) by gerdesj (subscriber, #5446) [Link] (1 responses)

You and me both. I am not a particularly skilled programmer but I have to write scripts from time to time in quite a few languages.

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
Jon

Affects Perl too

Posted Mar 12, 2014 23:06 UTC (Wed) by dchichkov (guest, #90878) [Link]

I would kindly suggest writing readable code and emphasizing readability.

Affects Perl too

Posted Mar 12, 2014 22:58 UTC (Wed) by bracher (subscriber, #4039) [Link] (9 responses)

well, object as true/false affects perl, but perl's datetime library doesn't treat UTC midnight as false. DateTime object with time set to UTC midnight returns true as most sane observers expect:

$ perl -e 'use DateTime; my $d = DateTime->now(time_zone => "UTC")->set(hour => 0, minute => 0, second => 0); print $d ? "true" : "false","\n";'
true

which is the whole point of the hoopla in python-land, that doing otherwise is non-sensical at best...

Affects Perl too

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.

Affects Perl too

Posted Mar 13, 2014 20:25 UTC (Thu) by jimparis (guest, #38647) [Link]

They both have quirks.
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

Posted Mar 14, 2014 17:37 UTC (Fri) by jtc (guest, #6246) [Link] (6 responses)

"perl -e 'use DateTime; my $d = DateTime->now(time_zone => "UTC")->set(hour => 0, minute => 0, second => 0); print $d ? "true" : "false","\n";'"

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
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;
}

my $d = DateTime->new(time_zone => 'UTC', year => 0, month => 1, day => 1,
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

Posted Mar 14, 2014 18:13 UTC (Fri) by mbunkus (subscriber, #87248) [Link] (5 responses)

Simply because the three getter methods return integers and not objects. Of course an integer of 0 is false in Boolean contexts in Perl. The whole point of the article is that certain objects evaluate to false in Python while "if obj: …" is a common way of checking whether or not obj currently holds an object instance.

Therefore your comparison is a bit besides the point.

Affects Perl too

Posted Mar 14, 2014 22:44 UTC (Fri) by jtc (guest, #6246) [Link] (4 responses)

"Therefore your comparison is a bit besides the point."

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:
http://lwn.net/Articles/590778/ )

Affects Perl too

Posted Mar 15, 2014 8:30 UTC (Sat) by mbunkus (subscriber, #87248) [Link] (3 responses)

Almost each and every language has its own rules which values it considers false in Boolean contexts. They're varied too much to approach remembering them with logic. You just have to learn them by heart.

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.

Affects Perl too

Posted Mar 15, 2014 10:35 UTC (Sat) by peter-b (subscriber, #66996) [Link] (1 responses)

I found myself thinking about this last night -- I quite like languages where there is one and only one "false" value. Like Scheme:

> 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.

Affects Perl too

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 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).

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.

Affects Perl too

Posted Mar 16, 2014 5:43 UTC (Sun) by jtc (guest, #6246) [Link]

"Therefore I consider saying anything along the lines of »0 is always/never supposed to be false« to be the wrong approach."

I agree.

"I fully agree that Boolean conversion for instances of a time-based classes ... is harmful. ..."

We appear to be in 100% agreement. :-)

Affects Perl too

Posted Mar 13, 2014 10:05 UTC (Thu) by intgr (subscriber, #39733) [Link] (3 responses)

> which can fail if $var is the string "0".

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.

Affects Perl too

Posted Mar 14, 2014 19:21 UTC (Fri) by mathstuf (subscriber, #69389) [Link] (2 responses)

> most contexts there is no implicit casting from str to int.

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
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

Posted Mar 17, 2014 12:13 UTC (Mon) by intgr (subscriber, #39733) [Link] (1 responses)

> What contexts do?

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.

Affects Perl too

Posted Mar 17, 2014 13:13 UTC (Mon) by mathstuf (subscriber, #69389) [Link]

The only place I can think of for implicit str casts is Python2's print statement. The %s way warns at least IIRC.

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.

A false midnight

Posted Mar 13, 2014 1:17 UTC (Thu) by kenmoffat (subscriber, #4807) [Link]

I thought this was going to be related to Douglas Adams "Time is an illusion, lunchtime doubly so." - That would have been far more interesting than the weird and wonderful corners of language specifications ;-)

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.

Conversion

Posted Mar 13, 2014 9:58 UTC (Thu) by l0b0 (guest, #80670) [Link]

What does "converting it to minutes" even mean? Three obvious possibilities are

hours * 60 + minutes (integer)
hours * 60 + minutes + seconds / float(60)
hours * 60 + minutes + seconds / float(60) + fractional_seconds / float(60)

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.

No scheduled flights at 00:00

Posted Mar 13, 2014 13:57 UTC (Thu) by dmarti (subscriber, #11625) [Link] (1 responses)

Of course you shouldn't schedule anything important for exactly midnight anyway...

No scheduled flights at 00:00

Posted Mar 13, 2014 19:00 UTC (Thu) by dlang (guest, #313) [Link]

the problem now is you shouldn't schedule anything for midnight UTC as well as midnight local time

A false midnight

Posted Mar 13, 2014 16:55 UTC (Thu) by fandingo (guest, #67019) [Link] (8 responses)

The standard argument for midnight being false for datetime.Time is that it's the start of a day -- the first representable moment for that object. If we accept that argument, then the conclusion is that the same should apply to datetime.datetime and datetime.date objects. However, they do not have falsy behavior at the minimum.

>>> bool(datetime.date(datetime.MINYEAR, 1, 1))
> True
>>> bool(datetime.datetime(datetime.MINYEAR, 1, 1))
> True

That alone should be a reason to get rid of falsy behavior.

A false midnight

Posted Mar 13, 2014 18:08 UTC (Thu) by pboddie (guest, #50784) [Link] (2 responses)

The whole notion of falseness for datetimes is absurd: it's like treating every integer whose decimal representation ends with a zero as being false. I can only imagine that this "feature" came about in an ill-considered moment of "wouldn't this be cool/useful".

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.

A false midnight

Posted Mar 14, 2014 1:18 UTC (Fri) by Karellen (subscriber, #67644) [Link] (1 responses)

"it's like treating every integer whose decimal representation ends with a zero as being false."

Or like treating every floating point value which happens to be an exact integer as false.

A false midnight

Posted Mar 14, 2014 17:37 UTC (Fri) by duelafn (subscriber, #52974) [Link]

First: I agree that it was a poor choice to make midnight False.

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".

A false midnight

Posted Mar 14, 2014 17:55 UTC (Fri) by jtc (guest, #6246) [Link] (4 responses)

"The standard argument for midnight being false for datetime.Time is that it's the start of a day -- the first representable moment for that object. If we accept that argument,..."

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.)

A false midnight

Posted Mar 14, 2014 18:17 UTC (Fri) by fandingo (guest, #67019) [Link] (3 responses)

You misunderstand my point. I don't think that anything beyond obviously False objects (numbers and empty containers) should ever implement __boot__(), and thus, should always be true.

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.

A false midnight

Posted Mar 14, 2014 21:24 UTC (Fri) by jtc (guest, #6246) [Link] (2 responses)

Actually, I agree with your main point and wasn't meaning to argue against it. I didn't make it clear I was only disagreeing with the side issue that the start of something should evaluate to false. (And it was clear to me you weren't stating this proposition was your view.)

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.

A false midnight

Posted Mar 15, 2014 0:22 UTC (Sat) by fandingo (guest, #67019) [Link] (1 responses)

I pretty much entirely agree with you, so don't take these comments as a vigorous rebuttal.

> 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.

A false midnight

Posted Mar 20, 2014 15:54 UTC (Thu) by Wol (subscriber, #4433) [Link]

MINYEAR could be 0? Why?

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,
Wol

A false midnight

Posted Mar 13, 2014 20:41 UTC (Thu) by mgedmin (subscriber, #34497) [Link] (1 responses)

The best part is where times corresponding to midnight UTC in the eastern hemisphere are considered False while times corresponding to midnight UTC in the western hemisphere are considered True.

A false midnight

Posted Mar 14, 2014 2:03 UTC (Fri) by Tara_Li (guest, #26706) [Link]

Heck, you have four *major* occurences while walking across the North American continent, and half-a-dozen other minor ones - note, for example, that there's a chunk of Canada that is *30 minutes* off of the major time zones, and that at times of the year, Australia has both vertical *and* horizontal divisions of time zone, some of the offset by *15* minutes. It can be a *REAL* nightmare, especially for a website being connected to from all over the world.

This Is Why Booleans Must Be Booleans

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.

This Is Why Booleans Must Be Booleans

Posted Mar 18, 2014 13:50 UTC (Tue) by mathstuf (subscriber, #69389) [Link]

I'm Haskell, you can also overload 'if' (with the -XExtensibleSyntax flag) for things like Maybe or Either. Luckily, flags can be set in a file so you don't have to chase the build file down to see what's going on.

A false midnight

Posted Mar 21, 2014 8:21 UTC (Fri) by abket (guest, #95676) [Link]

What a shitty language.

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.


Copyright © 2014, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds