Hacker News new | comments | show | ask | jobs | submit login
Show HN: Shareable reasons to upgrade to Python 3 (whypy3.com)
62 points by kichik on Aug 10, 2017 | hide | past | web | favorite | 50 comments



Aren't most of these also backportable to Python 2.7? Especially the libraries, where the reason seems to be, "we added a nifty little tool."

If so, then why the breaking language change? Why should I port my Python 2.7 code to Py3 for some libraries that I didn't feel like I was missing?

I'd suggest including more demos of how the backwards-incompatible changes made Python 3 better than 2.7.


Python 2 is dead, end of life coming soon, move on.


> Why should I port my Python 2.7 code to Py3

Because Python 2.7 will no longer be supported past 2020.


See my other answer.


You know, if Python 3 had been backwards compatible everyone would've been using it 10 years ago and you wouldn't need to waste time creating stuff like this. And more to the point, we wouldn't all have had to waste tends of thousands of hours of programmer time porting to Python 3.


Most of the things that makes Python 3 great would have broken compat. See my other comment.

You could have broken down those changes into small pieces, with deprecation. But then, you would have spent the last 10 years changing your code every 2 years. I don't think that's better.

Look at the JS community. They break things every 6 months. The result ? Oh they are moving fast.

But most projects I met in JS are disposable ones. They will not evolve. They will not be maintained. They will be trashed and rewritten.

So yes, the transition has issues, and it could have done it better. But it's far from as bad as most people say it is. People just like to be have everything without paying any cost, but it doesn't work. They want a modern language, but that never changes. They want something stable but that stays up to date. They want to write code and never touch it again, but then complain they can't use their shinny new toy.

And seriously, I always hear about those too many huge hours spent to port code 2 to 3. That is so much bullshit.

I do that regularly with my clients, and that's far, far away from the reality. Unless you have some very exotic code, it's a few minutes per files. Something you do anyway cause you redactor and clear your code.

The team of numpy, twisted and dropbox did suffer from the port. But come on, most people are not them. And THEY didn't complain. People that don't have the problem usually complain the most. They are just scared of the movie they played in their head. They all think they are a special snowflake.


I'd have been using Python 3 ten years ago if they put something semantically useful in like a switch/case statement.


FWIW, I worked on a switch/case for Python about ten years ago and abandoned the effort because it reached a dead-end.

Without the ability to mark variables as constants, a switch-case can't implement an efficient jump table. And without a jump table, a switch/case statement is merely syntactic sugar for an if/elif/elif chain.


I'd be happy with the syntactic sugar.


I don't see how it saves you that much of typing.


It's not about typing. It's about code legibility. With a series of if/elif's you have to mentally evaluate each condition to verify what's happening. If the use case really is switch/case, then a switch/case statement would allow the programmer to mentally evaluate only one condition and quickly glance at a list of possibilities.


If/elif


Damn you'd think it was rocket science. For goodness sake it was EASY to upgrade. People just wanted to whine and whine until eventually they had to try it and find it wasn't that hard.

Now everyone uses python 3 and has stopped griping, which wasn't warranted in the first place.


Not very compelling at the moment considering how few examples there are. But fully explaining why Python 3 is worth it is at this point, actually quite hard. Things like better Unicode support, type annotation syntax, and removal of old school classes are some of my favorite reasons.

Also, when making kwarg only functions, you don't need to name the rest args parameter. You can do this:

    def a(a, b, *, options=None):
I think this case also enforces that you don't add any additional positional arguments that go unused.


Unicode support and new style classes were in Python 2.7.

Type annotations that don't do anything are one of the worse ideas I've ever seen in a programming language. Optional type annotations that are checked and used for optimization would be fine. But what's gone in is useless. Also, the syntax, which puts some type information in comments, is lame.

Python 3 isn't bad at this point. The first few years of Python 3 really sucked. The syntax was less compatible, and particularly hard on those who used Unicode in Python 2.7, because u'abc' was prohibited. "2to3" didn't work very well. Many, many libraries were not converted. The newer libraries for Python 3 were buggy due to being underutilized. Python 3 before Python 3.3 was quite bad.

Most of that's been fixed. The "six" module was a big help in porting. Eventually, "u'abc" was allowed. Most of the libraries were ported and available and debugged on both 2.7 and 3.x. But it took many years, and gratuitous incompatibilities made it harder than necessary. It's fitting punishment for Von Rossum that he's forced to maintain the Python 2.7 system at his day job at Dropbox.


> Unicode support and new style classes were in Python 2.7.

Unicode support is tremendously easier in Python 3. You don't have to deal with the confusing encode and decode stuff, and Python 3 strings have the right behavior in a lot of weird cases where Python 2.7 unicode objects do not.

Unicode support was my biggest reason for switching to Python 3, because I was dealing with natural language data with a lot of emojis and Python 2.7 was just an endless headache.


I actually argue that it's worse. In Python 3 you are FORCED to deal with unicode encoding; Python 3 does not react well with legacy data that should just be handled as is (without making things worse or incompatible).

The alternative is to use a byte array without an encoding, which would be OK if Python 3 made coercion between the two easy (and had the concept of a 'validated' unicode string; operations known to keep a unicode string valid could leave that set, otherwise it would be 'not validated' but you wouldn't /need/ to care).


That prevents lots of mistakes. You only specify encodings while reading external data, and then everywhere else in your code you know you are dealing with strings and that's it (instead of your code working until a random Fóréígner trips it out)


Well, I spend most of my time dealing with internet and serial protocols, and the strict demarcation between unicode strings and byte strings is what keeps me sane. Explicit encode/decode calls are a feature, IMHO.


I wouldn't mind the strict demarcation if the internal format was UTF-8. Then a simple validate call on input would be all that was needed.


> But what's gone in is useless. Also, the syntax, which puts some type information in comments, is lame.

This is only the case in python2. In python3(.6), I don't believe there is any type information that cannot be expressed only in comments.


> Type annotations that don't do anything

As in any language, they “do something” in Python, to wit, enable static type checking.

Now, static type checking is provided by systems separate from the language implementation, but the still exists.

> Also, the syntax, which puts some type information in comments, is lame.

The comment-based component of the syntax is a compatibility feature for Py 2.x.


Now, static type checking is provided by systems separate from the language implementation, but the (sic) still exists.

That's almost a direct quote from the static typing proposal. No such checker existed at the time. There's a half-completed one, "mypy".[1] It's a bit different from Python 3.6 type annotation, though it tries to stay close.


I think mypy can be considered the canonical type checker for python, Guido van Rossum works on that project. It uses python 3.6 type annotations fully as far as I know.


It would be nice to have a little plain English description for each example as well. E.g. the suppress example[0] kind of does this with a comment, but the Enum example[1] isn't really clear about what advantage it creates over Python 2.

[0] http://whypy3.com/#2

[1] http://whypy3.com/#8


At the bottom there is a link to the inspiration for this project. It has a much better format. "You can already do this:" "Now you can do this:" which actually shows the differences. Really a simple explanation of what's happening and why it's useful and why it's better than py2 would be nice. Especially for newer programmers who are trying to figure out which version to start with.


I've added some more comments as many people asked for them. I initially tried to keep it very simple to the point where you read a little piece of code and think to yourself that's really cool and I want to try it out in Python 3. But you're right and a little text can help put things in the right context which might not be immediately obvious.


Those seem like excellent reasons, but I really don't like that they keep repeating after a while.


There's only 9 of them.

If you disable JS, they'll be shown all at once.


The very first one I was presented with was the following perversity:

    from pathlib import Path
    directory = Path("/etc")
    filepath = directory / "test_file.txt"
Overloading the addition operation for list concatenation: fine. Something something monads composition symbol. But the division operation?

[Apparently I don't know Windows very well, since I'm told the following sentences are wrong:] Anyway, I thought Python was meant to try and be cross-platform. On Windows, the forward slash is not a path separator.


Having done a lot of work using both the old os.path library and new pathlib, the ergonomics of pathlib are much, much better. The behavior of the overloaded operator is well-defined, unsurprising, and otherwise intuitive.


While I get what you mean, we're very used to using "/" to concatenate components into full pathnames: we do it every time we're using the shell. "/" is the canonical pathname concatenation operator, and tricking a specific Python class into also using it feels a lot more natural than the joinpath stuff.


We've got a C++ codebase with its own path class, which works this way. They also used unary operator* (dereference operator) in the string class to return a pointer to the underlying c-string. These are the "clever" kinds of things that the original devs used to get kind of sheepish about when I first brought them up.


At one time, operator overloading was encouraged. See for example, << in the cout command. Everyone was trying to think of new ways to use them.


Well, and it worked well enough, in the limited context of our software system. There were a few quirks like that, that everyone knew about. It was kind of funny how opinions changed in the years between when it was written and when I started working there, though.


The `joinpath` method on Path objects works just as well. Also AFAIK pathlib.Path handles both forward and backslashes and does the right thing with them (assuming you don't have some very specific needs around, say, not accepting invalid slashes at all).


Sure, `joinpath` may be the best thing since sliced bread. But I maintain that it's semantically perverse to use the division operator symbol to represent it.


Boost::filesystem also does this with its path class. Needless to say I was amazed to see that the + operator wasn't chosen for path concatenation.


I'm pretty sure forward slash works fine for file paths in Windows. It's just not the standard if you cut and paste a filepath from Windows Explorer. As a primarily Linux user that was a pleasant surprise because $BigCorpIWorkAt only has a Windows offering.

Unless it was Cygwin being sneaky, that is...


Using forward slashes on windows will work fine.


ISTR slash works in most, but not all contexts.


I do every new Python project in Python 3 when I can. Unfortunately, there are still cases where I need to write a script to run on CentOS6 environments, and I can't install any dependencies :/.


I've always just bit the bullet and compiled Python 3 and installed it to ~/.local when I had to deal with such a system. It generally compiles without needing me to build any other dependencies. Unless I need sqlite, berkeley db, tk et al., that is...


While not perfect, the following removes most of the pain and some programmer in the future will thank you for it.

from __future__ import (absolute_import, division, print_function) __metaclass__ = type # pylint: disable=invalid-name


For those you are still wondering why you should use Python 3, you should know that most of the reasons given to you have been far from the best one.

The best reason to move to Python 3 is:

-- It is just an easier language --

It's easier to write. It's easier to read. It's easier to maintain. It's easier to debug.

The problem is, there is no killer feature that shows that.

You only realize it when you wrote 10 projects in Python 2, and 10 in python 3, which I did.

What makes Python 3 easier is not "this one big thing", but a hundred of little things that cumulatively make your whole program just easier to <anything>.

Among the stuff:

- A lot of mistakes you could make with Python 2 are impossible in Python 3. Stupid comparisons, bad import layouts, concatenation of string and bytes, overriding keywords, loading too many objects in memory...

- Things are just less verbose. Automatic super(), no object inheritance, better unpacking, no from __future__ or encoding declaration, no longer generator aliases, f-strings, imports from codec...

- The right thing to do is more obvious. Way less aliases for modules, methods and built-ins, the easiest way is usually the best way (f-strings, range(), etc), division is doing what people thinks...

- The stdlib is way more consistent. Better naming, unicode/bytes in => unicode/bytes out, the __path__ protocol allow Path objects everywhere...

- Python is globally easier to debug. The error messages are better, we have new tools to debug memory consumption, but all in all, because everything is easier, you just produce less bugs. And you can type check on big code bases if you wish to.

Each of those things are not revolutionary. But cumulatively they are adding up to a huge difference. Much like being in the sea doesn't feel that much different until you go out and gravity calls again. That's why it has been hard to sell.

That and the fact Python 3 has started to be usable only on 3.3, cool in 3.5 and to really, really rock in 3.6.

What's more, most of those things could NOT have been done without breaking compatibility. True, improving error messages could, but fixing unicode, comparison, verbosity, naming, etc. would have broke so many programs anyway. But yes, they could have been done more incrementally though.

However, I can't help but notice that a lot of people complaining are doing so for the sake of it. Without real knowledge about it.

Most porting take a few minutes to a few hours. Not kidding. I've been doing that a lot. It's way scarier in people's head.

In reality, it is not that much work. Very few people have such a big and complicated code base that it's hard to port. They do exist. And it's a real pain for them. But most people on this thread are not them.

Porting to Python 3 is most of the time really easy, and the gain , while not obvious at first, are actually quite enormous.

What's hard is to make this shitty mix of 2 + 3 compatible code base.

In all case, I'm quite glad a lot of people will wait until the last minute to migrate. I'm starting to position myself as a consultant export in 2 => 3 and I plan to make a lot of money with it in 2020.


    a, *rest, b = range(10)
    print(rest)  # prints [1, 2, 3, 4, 5, 6, 7, 8]
In which context could this be useful?


If you want to split a list into a head and a tail:

    head, *tail = list_to_split
In Python 2, the best way I know of for this is

    head, tail = list_to_split[0], list_to_split[1:]
which is unwieldy.


When you want to quickly slice off the first and last elements of a list?


If it's a list (and not a generator like `range`), then `l[1:-1]` will do.


Of course.

Consider though:

a, b, c = (l[0], l[1:-1], l[-1]) is also feasible, but I think it adds a bit of overhead




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: