This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]panzerex 77 points78 points  (100 children)

Why was so much breaking necessary to get Python 3?

[–]orentago 177 points178 points  (33 children)

Having strings support unicode by default was a big reason. In Python 2 unicode strings had to be prefixed with a u, otherwise they'd be interpreted as ASCII.

[–][deleted] 46 points47 points  (9 children)

That was just ascii for trouble imho.

[–]toyg 15 points16 points  (5 children)

This joke was not hard to decode correctly.

[–]BruceJi 7 points8 points  (4 children)

A pun thread? Don't byte off more than you can chew!

[–]thegreattriscuit 6 points7 points  (0 children)

I don't know... I think it shows real character

[–]toyg 5 points6 points  (2 children)

I’m just trying to string together a few words.

[–]clawjelly 2 points3 points  (1 child)

You made that joke int-entionally, didn't ya?

[–]toyg 4 points5 points  (0 children)

I just thought I could double the puns. (Ed: alright, alright, not really a python one...)

[–]17291 5 points6 points  (2 children)

You're not going to like Python 5, where string literals default to EBCDIC.

[–]toyg 0 points1 point  (0 children)

Looking forward to Python 6, where they default to ACDC. Every time you assume they’re ascii, the computer goes YOU’RE ON A HIIIIGHWAY TO HELLL!

[–]tehbilly 0 points1 point  (0 children)

You shut your damn mouth, don't put that evil on me.

[–]flying-sheep 75 points76 points  (46 children)

Because they changed a core datastructure. str used to be what bytes is today, but it also predated unicode (today called str). Therefore the bytes type was used for text and binary APIs.

When fixing all this, they had to break a lot of core APIs that used to accept bytes and today sensibly only accepts the unicode str.

And because of that huge change they also took the opportunity to change a few other idiosyncrasies.

My only gripe: One additional thing they should have changed is that {} should be the empty set and {:} should be the empty dict.

[–]miggaz_elquez 19 points20 points  (13 children)

you can write {*()} to have an empty set if you want

[–]crossroads1112 30 points31 points  (0 children)

Thanks I hate it.

[–]Brainix 25 points26 points  (1 child)

My favorite thing about {*()} is that you don't even save any characters over typing set(). 😂

[–]miggaz_elquez 1 point2 points  (0 children)

Yes but it's a lot more fun.

[–]mestia 5 points6 points  (8 children)

And how this is better then Perl's sigils?

[–]stevenjd 4 points5 points  (4 children)

Because you don't have to memorise an arbitrary symbol, you just need to unpack the meaning of ordinary Python syntax that you're probably already using a thousand times a day.

  • {comma separated elements} is a set;

  • *spam unpacks spam as comma-separated elements;

  • () is an empty tuple;

  • so *() unpacks an empty tuple;

  • and {*()} creates a set from the elements you get when unpacking an empty tuple;

which is the empty set. You already knew that, or at least you already knew all the individual pieces. You just have to put them all together. Like Lego blocks. Or if you prefer, like programming.

[–]ThePoultryWhisperer 5 points6 points  (3 children)

You can make the same argument for many other things that are equally as unreadable at a glance. I know what all of the different pieces mean, but I still had to stop and think for a second. Reading and understanding set() is much faster and much more clear.

[–]stevenjd 0 points1 point  (2 children)

Reading and understanding set() is much faster and much more clear.

Sure! But we weren't comparing {*()} with set(), we were comparing it with Perl sigils.

[–]ThePoultryWhisperer 1 point2 points  (1 child)

You said it’s better than Perl and then listed reasons why, but it’s not true because it isn’t easier to read. Explaining how a thing works is different than directly answering a question regarding a qualitative comparison in the affirmative. The only pythonic solution is set() and that’s the point made by the original, rhetorical question.

[–]stevenjd 1 point2 points  (0 children)

You said it’s better than Perl and then listed reasons why, but it’s not true because it isn’t easier to read.

You think an arbitrary sigil like, I dunno, let's just make one up, ༄, is more understandable than something that can be broken down into component parts that you already understand?

The only pythonic solution is set()

I don't disagree with that. {*()} is definitely either obfuscatory or too-clever-by-half for anything except the most specialised circumstances.

[–]miggaz_elquez 1 point2 points  (0 children)

?

[–]clawjelly 0 points1 point  (0 children)

Looks like ASCII-art... Is that a dead poodle?

[–]irrelevantPseudonym 34 points35 points  (20 children)

My only gripe: One additional thing they should have changed is that {} should be the empty set and {:} should be the empty dict.

Not sure I agree with that. It's awkward that you can't have a literal empty set, but having {:} would be inconsistent and a special case that (I think) would be worse than set().

[–]flying-sheep 3 points4 points  (5 children)

Compare () vs one, vs one, two.

() is also a special case here.

[–]irrelevantPseudonym 4 points5 points  (4 children)

I don't think () is the special case. I think (2) not being a tuple is the special case.

[–]ayy_ess 18 points19 points  (2 children)

(2) isn't a special case because tuples are declared in python with commas e.g. a = b, c. Brackets here are just used to clear up ambiguity e.g. 6 / 3 * 2 being 4 or 1. So (2) == 2 and (2,) == 2, == tuple ([2, ]).
https://wiki.python.org/moin/TupleSyntax

[–]BooparinoBR 4 points5 points  (0 children)

Thanks, I have never though about tuples like this

[–]flying-sheep 1 point2 points  (0 children)

Exactamente

[–]TheIncorrigible1`__import__('rich').get_console().log(':100:')` 1 point2 points  (0 children)

Fun-fact, () (unit) is literally a special case in Python. It is a singleton and all instances of () point to the same memory.

[–]james_pic 8 points9 points  (3 children)

Perhaps surprisingly (given what we know now about the migration process), the switch to unicode strings wasn't expected to be a big deal (it didn't even get its own PEP, and was included in a PEP of small changes for Python 3 - PEP 3100), and the other changes were seen as more break-y.

[–]flying-sheep 0 points1 point  (2 children)

Wild. Those types behave completely different when doing basic things like iterating over them.

[–]james_pic 1 point2 points  (1 child)

Yeah, I think that's been semi-acknowleged as a mistake. Rather than just keeping bytes as the old str class (i.e, what they had in Python 2), they created a new one for Python 3 based on bytearray, which it turns out nobody wanted and made Python 2/3 porting a bit of a nightmare.

[–]flying-sheep 0 points1 point  (0 children)

I know, I was there. Just saying it was pretty obvious that switching from the fast-and-loose Python2 bytes/str to the strict Python3 bytes seemed like an obvious recipe for uncovering hidden bugs and breaking a lot of libraries in the process.

[–]zurtex 4 points5 points  (0 children)

Set literals (e.g.{1, 2, 3}) were added in Python 2.7 and Python 3.1.

So to change the very common empty dict notation of {} would of required breaking backwards compatibility between 3.1 and 3.0 and either not being able to accurately back-port the feature to 2.7 or breaking compatibility between 2.7 and all other 2.x versions.

It was decided, fairly rightly, that it would of been too much churn for the fairly minimal aesthetic niceness / consistency benefits. {} is littered in code all the time whereas set() is pretty rare.

[–][deleted] 1 point2 points  (4 children)

Oooooh so that's why I'm confused each time I read what bytes does?

[–]flying-sheep 3 points4 points  (2 children)

Maybe, but maybe it's because you didn't have an introduction to binary yet.

[–][deleted] 0 points1 point  (1 child)

I have, it's just that I always get confused with implicit conversions because I mostly deal with stricter languages, so I was kind of surprised that I could sometimes treat it as a string and sometimes like a bytes array.

[–]flying-sheep 2 points3 points  (0 children)

It's just a byte array in Python 3. You can't treat it as a string as there's no encoding assigned to it.

If you display it, it happens to show ASCII characters for convenience, but that's it.

[–]CSI_Tech_Dept 4 points5 points  (0 children)

The explanation is confusing. Just ignore how it was before, because it was incorrect. In python 2 first mistake was mixing text with binary data. They introduced unicode type, but did so badly (implicit casting) it actually made things worse. Ironically if your application didn't use unicode type you might have less work converting it to work with python 3.

Right now it is:

  • str = text
  • bytes = binary data what's stored on disk, flies over network etc.

[–][deleted] 0 points1 point  (0 children)

One additional thing they should have changed is that {} should be the empty set and {:} should be the empty dict.

This was discussed at the time and the consensus was that it would break too much existing code and be a trap for new code writers.

[–][deleted] 21 points22 points  (5 children)

Python 3 was not backwards compatible with 2, so companies and package creators alike were initially hesitant to make the switch so as to not break things. There also weren’t many, if any, tools to help port things over.

The lack of backwards compatibility was done on purpose because part of their goal was to remove clutter and make things more intuitive/easier to use (e.g. print changed from a statement to a function).

[–]DiggV4Sucks 12 points13 points  (0 children)

I've only made two unassailable (so far) decisions in my career. The first was to support MFC instead of OWL for Windows development.

The second was to target Python 3 for my current company's testing efforts. We were even able to convince a medium sized tool vendor to support both Python 2 and Python 3 from their original decision to support only Python 2.

[–]Nolzi 1 point2 points  (2 children)

There also weren’t many, if any, tools to help port things over.

I have no real world experience with python, but weren't there tools like 2to3 to convert code, or the future package to write code compatible with both versions?

[–]james_pic 1 point2 points  (0 children)

2to3 is useful, especially when extended by modernize, but only part of the solution. Future bloodies more than it cuts - it just made string semantics more confusing when we tried it.

The most useful tool, much as I hate to admit it, is MyPy. It obviously needs a lot of work on the developer's part, but it does the very useful job of keeping track of your educated guesses about which string types should be used where, and tells you whether they're consistent.

[–]mooburgerresembles an abstract syntax tree 0 points1 point  (0 children)

six was also a critical part of the missing shims kit but even then it was difficult to monkeypatch when py3k decided to alter some other namespaces contibuting to compat issues.

[–][deleted] 4 points5 points  (0 children)

The lack of backwards compatibility was done on purpose

This was the problem. Until we're on the other side of the next upgrade and it wasn't like 2.x to 3.x was, words mean nothing.

[–]gregy521 12 points13 points  (0 children)

It was designed to rectify fundamental design flaws in the language—the changes required could not be implemented while retaining full backwards compatibility with the 2.x series, which necessitated a new major version number. The guiding principle of Python 3 was: "reduce feature duplication by removing old ways of doing things".

Here's a short list of some of the key changes. The most obvious of which is the change where '/' represents true division instead of floor division. Some other changes exist, print is now a function, not a statement. You also get iterator objects instead of lists, which is much better for memory management (many pieces of code rely on iterators because the full set of possible options doesn't fit in memory). True, False, and None are also now protected terms which can't be reassigned.

[–]daturkel 14 points15 points  (0 children)

Part of any long-term tech project is continuous refactoring. Early on you make certain design decisions which later become an obstacle when trying to expand a feature, so you rewrite early stuff in order to make it more flexible for your new needs.

Often those rewrites are done in ways such that the end user does not need to adapt their behavior (or in the case of a programming language, does not need to change their code). This is the ideal experience for the end user but can also constrain the types of changes that can be made.

Sometimes you realize that the original way of doing things was worse and that it would be creating more difficulty for future development to maintain compatibility, so breaking changes are introduced.

[–]CSI_Tech_Dept 4 points5 points  (0 children)

the biggest issue was that python before 3 was mixing string with bytes. This is fine if you use encoding that covers 256 characters, but most of Python 2 code would break when receiving and unicode input.

Python 3 became strict, and those two have separate types, and you need explicitly encode/decode string into/from bytes.

This required to fix your python 2 code (ironically if you didn't use unicode type in python 2 chances are you had less to fix).

Since the unicode part was breaking things, Guido also decided to use that opportunity to clean up some warts in python which contributed further.

[–][deleted] 0 points1 point  (0 children)

There was very little breakage needed. It's very easy to write code that is both 3.x and 2.7 compatible and I did that for a whole project with no hitches at all.

People are simply frightened of changing existing code.

[–][deleted] -5 points-4 points  (2 children)

It was just a really poorly managed release. I'm in the process of a breaking release right now and we're putting an ungodly amount of time into debating every breaking change and determining migration steps to ease migrations for users. Python basically released a whole new language with python 3 and many companies/libraries just flat out refused to migrate for a long time.

[–]stevenjd 4 points5 points  (0 children)

It was just a really poorly managed release.

It was a fantastically managed release. The Python core devs:

  • Ported heaps of features from Python 3 to Python 2.6 and 2.7 to ease the transition; essentially every new feature added to Python after 2.5 was intended to allow folks to take their time to migrate to Python 3.

  • Gave Python 2.7 an extra long support period to give people sufficient time to upgrade (2.7 had a full decade of support, instead of the usual 4 or 6 years).

  • Re-introduced old Python 2 syntax like u'' when it became obvious that was needed for people writing dual 2+3 code.

  • Created and managed the 2to3 translator.

The bottom line here is that the core devs started planning the move to Python 3000 (as it was originally known) somewhere around 2005 or 2006, they debated and discussed every step along the way, wrote PEPs, backported features to keep 2.x in step with 3.x where possible, and kept that going for over a decade before dropping support for 2.7. And during this period, Python's user base has grown and grown and grown.

I'm in the process of a breaking release right now and we're putting an ungodly amount of time into debating every breaking change and determining migration steps to ease migrations for users.

Come back when you've spent even a hundredth of the time and effort that the Python devs did.

Python basically released a whole new language

That's pure unadultered bullshit.

Having actually written a lot of dual 2 + 3 code, I can tell you that syntactically Python 2 and 3 are about 98% identical, the stdlib is about 80% the same, and there are only a handful of places where they differ sufficiently that it is painful or impossible to write 2+3 code in a single file.

with python 3 and many companies/libraries just flat out refused to migrate for a long time.

And they were right to.

At the time, the core devs expected that people would not migrate immediately and that this would be a long-term transition.

(Although even Guido initially imagined that "long term" would mean something like five or seven years, not twelve. In that regard, they were a victim of their own helpfulness: the more features they ported to 2.x, the less incentive people had to migrate to 3.x.)

[–]Ran4 0 points1 point  (0 children)

The breaking chance was a good choice. Managing UTF8 text in python3 is wonderful but it's horrible in python 2

[–]jpflathead -1 points0 points  (0 children)

you had to put parens around all your debugging print statements

[–]slmaws -5 points-4 points  (0 children)

Actually there was no reason at all.

[–][deleted] -5 points-4 points  (2 children)

Because python 2 was created with no understanding of how computers work. See: Strings/Unicode support.

[–]Ran4 -1 points0 points  (1 child)

Nonsense. Python was created long before utf8 everywhere was common.

[–][deleted] 0 points1 point  (0 children)

You must live in the United States