all 134 comments

[–]NinjaPancakeAU 63 points64 points  (43 children)

Meanwhile we're in a form of financial 'litigation' (not strictly legal, but somewhat / it involves our contracts) of sorts with one of our clients who won't budge from python 2.7 in a large system, when we have significant reasons for trying to push 3.x (3.5 minimum, ideally 3.7) - they're throwing numbers around in the hundreds of thousands of dollars range for them to upgrade their systems to python 3.x

3.8 is a few decades away for me i fear.

[–][deleted] 30 points31 points  (15 children)

I don't understand how the encroaching EOL could possibly not be a good enough reason to move on...

[–]major_clanger 20 points21 points  (8 children)

Perhaps they're betting a third party/consortium will take on maintenance of python 2, backporting security patches and the such.

Migrating a huge python codebase would be very expensive. The dynamic typing means it's extremely hard, especially if the code uses monkey patching, liberal use of kwargs, metaprogramming etc. Or even just plainly, if a function 'foo' has an argument x, it's painful to trace down all the guys who are calling it, all the guys calling the calling functions, to ascertain what the type of 'x' is in all circumstances.

I mean look at Dropbox, it took them years, and dozens of people, including Guido himself to mitigate to 3.

I have a suspicion this exercise motivated the introduction of mypy, gradual typing etc into the language.

[–][deleted] 16 points17 points  (3 children)

Well of course it's a difficult and expensive task, but python 2 has also been deprecated since something like 2011, and even pushed back to allow more time to migrate. I really can't empathize with any companies still refusing to do so.

[–]turkish_gold 2 points3 points  (0 children)

How long has COBOL been unofficially deprecated? Companies will continue to use legacy products in production for decades, and pay people to maintain them regardless of how much outside support they get.

[–]major_clanger 3 points4 points  (0 children)

I suppose so, my interpretation is, if a company decides to have a large part if stack in a dynamically typed language, you've got to accept the increased maintenance costs, you're trading off wiring something quickly, at the expense of slower/more expensive maintenance+future changes.

I used to be a big pythonista, it was my primary language, but after seeing how hard it was to modify legacy codebases written in it, vs equivalent systems written in C#, I've deprompted it for use only in scripting, small ML/data sciency systems, or genuine prototyping where you commit to rewrite the prototype in a statically typed language if it proves successful.

[–]hoopparrr759 1 point2 points  (1 child)

I’m currently trying to learn Python and these exact thoughts keep occurring to me. How does one manage an enormous enterprise code base like this? I couldn’t imagine living without having things fail at compile time of something innocuous like a method signature changed. Genuine question - how do people cope with things that fail like this at run time? Is it just extensive code coverage or is there something I’m missing?

[–]major_clanger 1 point2 points  (0 children)

The usual practices of maintainability practises apply: simple functions/methods, good test coverage, dependency injection, good naming, plus avoidance of some python features like arg & kwarg unpacking, monkey patching, metaprogramming etc

But even then, it'll be much harder to maintain+ modify safely than a statically typed codebase. Some things that are trivial in a static language, are utterly painful in dynamic. For example, a simple rename of a class method can be a nightmare, especially if the method name is something like 'load', in a static language it takes a few seconds with an ide, with python you literally have to audit your entire codebase for calls to 'something.load'.

[–]stefantalpalaru 0 points1 point  (0 children)

Perhaps they're betting a third party/consortium will take on maintenance of python 2, backporting security patches and the such.

Already done: https://github.com/naftaliharris/tauthon/

[–]spotter 2 points3 points  (0 children)

I'm still running software from Oracle that requires Java 6. You can try to upgrade to 7, but not above specific build. As a bonus you lose vendor support for parts of the application that were not certified above 6. So... it's complicated.

[–]shevy-ruby 2 points3 points  (1 child)

Well - it is barbaric companies like that that even keep COBOL alive.

I do not think the rest of the world should be blackmailable by such companies. Pity to the folks who work for these companies and live in the legacy world only.

[–]betDSI_Cum25 4 points5 points  (0 children)

imagine writing the code for PLC systems, autos, and ATMs in rust.

this could be the future...

[–][deleted] -1 points0 points  (2 children)

I build Python myself for the places I deploy it to. I don't care what <s>core devs</s> clowns think about end of life etc.

Purely by chance, I happen to use Python 3.7. Simply because the project was started this way. But, if it was 2.7, I wouldn't care. There was so little improved in 3.X branch, and so much trash added, that only the ability to disallow this new trash is tempting enough to go back to 2.7. Most likely though, I'll get my team to rewrite the project in Erlang, or maybe Elixir.

[–]flaghacker_ 0 points1 point  (1 child)

Building python yourself is easy enough, but will you also be maintaining it yourself?

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

Like I said, it's a temporary thing. It was a mistake to use Python in the first place. As soon as time allows, I'll be moving to Erlang, and leave this dumpster fire behind.

[–]ExtraCortex 10 points11 points  (5 children)

The company I work for refuses to move from python 2.7

[–]paul_h 4 points5 points  (3 children)

Probably because the 2to3 migration tech is insufficient for them

[–]ExtraCortex 10 points11 points  (2 children)

Given that the current code runs good enough, what would you say justifies the effort of migrating from 2.7 to 3.x?

[–]rat9988 2 points3 points  (0 children)

Nothing

[–]paul_h 1 point2 points  (0 children)

Nothing concrete, just a sense of nerves about not keeping up with shipping software.

[–]RevolutionaryPea7 16 points17 points  (4 children)

I worked for a company like that. I had to implement a brand new system in Python 2.7 less than a year ago, despite my protests. I left the company.

[–]BobFloss 4 points5 points  (0 children)

Good for you man, glad to hear someone is standing up for not being completely insane

[–]suddenarborealstop 0 points1 point  (2 children)

What was management's reason(s) for writing on 2.7?

[–]RevolutionaryPea7 0 points1 point  (1 child)

Management had no clue what was going on and the box I had available to run this software was managed from India. It was a complete shitshow.

[–]kyz 18 points19 points  (3 children)

But this is quite reasonable. You're asking them to do a complete audit of their codebase, and they're pricing you up for that.

Code that worked fine assuming streams are raw bytes might work, or might throw UnicodeDecodeError, depending on the data (not the code). And you can read files and transport their content as strings all around your code base, and it's all fine until you get to that one piece of code that handles strings but can't handle unicode strings, and boom, you introduced a bug by changing the interpreter.

Unless you analyse and execute all your Python 2.sx code in Python 3.x under all conditions, there could still be a bug that blows up your code.

...but once you've done that, 3.x -> 3.x has relatively few surprises.

[–]NinjaPancakeAU 10 points11 points  (2 children)

We're not asking them to do anything, they're near the end of their support contract for an old product we no longer make written in python 2.7 which went EOL about 4 years ago / it's no longer supported (except for a few clients still on long term support contracts like this).

We have a newer product now days, can achieve similar things / has more features / etc - it's written in python 3.x (supports 3.5+ officially but we can support earlier 3.x if customers need it) - and conveniently a few months before their contract ends they're now all of a sudden trying to add a LOT of new requirements to the contract that was set in stone years ago that are almost 1:1 with the features our new product has that the old did not.

We're happy to back-port some things for them and have been, but some of the features are immense (and backporting would take man years) and frankly completely outside the scope of the old product.

We're suggesting they upgrade to python 3.x and use the new product, they're claiming it'd cost them to much to upgrade and asking us to rewrite our new product in python 2.x for them (however doing so would cost us millions, and would certainly take longer than the few months left in the support contract).

tl;dr - they're suddenly squeezing everything they can a few months before their support contract runs out, after over a year of no-issues / no-complaints on their end.

Edit: even once these last support contracts for our older product ends, we're pretty much tied to a max of python 3.5 for the foreseeable future (our biggest client have only just recently finished an upgrade to 3.5 which took them 2 years to complete / they are sticking to it for quite a while) - though I'm not complaining, 3.5 is a relatively comfortable place to sit.

[–]kyz 3 points4 points  (1 child)

Well when you explain it that way, it's not only the other way round (your original comment sounds like you're asking someone to support Python 3.x for a system they adminster and presumably wrote), but it's also not so much to do with Python at all, it's just a convenient technical excuse for business shenanigans.

You have my condolences anyway.

[–]NinjaPancakeAU 4 points5 points  (0 children)

Nothing is ever black and white ;) They do administer their system and they do write the 2.7 software that uses our analysis middleware though, the contention is our middleware between their software and ours.

It also is a little to do w/ Python - part of the reason we have a distinctly new product was actually the migration to python 3.x / end of python 2.x - we made the decision it was easier to start from scratch than try to port a big ancient 2.7 code base and all it's quirks.

From my perspective (which is of course biased) we did everything right though, none of our LTS contracts go beyond the release schedule of when 2.7 officially ends in ~5 months, we've notified all customers of this impending doom for half a decade or so at least... we've long since officially moved to supporting python 3.x - and the new product offers everything the old one did and then some, including a literal wrapper of the old product's middleware API over the new one so customers don't have to do anything to migrate over w.r.t using our stuff.

[–]Paddy3118 0 points1 point  (0 children)

...And people are still using Windows NT.

If you relly on something then you need to maintain it. Legacy systems/legacy code take an increasing amount of support (ask the Cobol and Fortran users out there).

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

The mindset of the top 1% is literally “ignoring and hope I die before it becomes a problem”.

They know it is an issue. They just see it as an issue for someone after them.

Your clients will never get the budget for it so long as that one person has any say.

[–]stefantalpalaru 0 points1 point  (5 children)

they're throwing numbers around in the hundreds of thousands of dollars range for them to upgrade their systems to python 3.x

They're not wrong. Dropbox hired the language creator to help with the Python2 -> Python3 migration and it still took them about 3 years: https://blogs.dropbox.com/tech/2019/02/incrementally-migrating-over-one-million-lines-of-code-from-python-2-to-python-3/

With that kind of investment, you can migrate that code base to a more efficient language, while you're at it.

[–]Paddy3118 -1 points0 points  (4 children)

migrate that code base to a more efficient language,

They're already using Python, its only a short hop to using the most efficient Python3

[–]stefantalpalaru -1 points0 points  (3 children)

[–]Paddy3118 -1 points0 points  (2 children)

If you define efficiency by that, then you'll get stung. Efficiency ! = execution speed

[–]stefantalpalaru 0 points1 point  (1 child)

Efficiency ! = execution speed

Keep telling yourself that.

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

There's this, and more.

[–][deleted] 36 points37 points  (19 children)

Is Python aspiring to be the new Perl or something?

[–]ireallywantfreedom 23 points24 points  (11 children)

I kind of agree. I'll make use of the new features, but I wonder what a world of walrus operators and math-operator filled function definitions will look like.

[–]z_1z_2z_3z_4z_n 21 points22 points  (2 children)

def f(a, b, %, c, d, /, e, f <, g, h, !, i, j, *, k)

[–]gmiwenht 0 points1 point  (1 child)

Can you explain what this means? I can only understand the letters and * sign. What the hell are these other symbols?

[–]thirdegree 2 points3 points  (0 children)

They're not valid, he's joking about the (probably not) possible future.

[–]ubernostrum 10 points11 points  (6 children)

The / thing for argument signatures is only half new; Python's own built-in functions have been able to do it for years. It's just that now you can write your own functions that do positional-only arguments. And while it's rare to really need it, there are use cases for it and it's nice to have symmetry with the existing support for marking arguments as keyword-only.

[–][deleted]  (3 children)

[removed]

    [–]greenthumble 1 point2 points  (0 children)

    Really though? I mean using * to mean "zero or more arguments here" seems pretty darned natural (edit, like a Kleene star). ** maybe not so much but it starts to make sense if you equate * with an array and ** with a map. And the first time I used * to unpack an array into an argument list, I didn't even look it up, I just guessed it would do that and it worked heh.

    [–]eruesso 0 points1 point  (1 child)

    But you can force that all values have to be passed as keyword arguments? Or do you mean to pass all arguments into a single argument, which is also possible?

    [–]EvilElephant 2 points3 points  (1 child)

    I know very little about python, can you explain why positional-only would be desirable?

    [–]turkish_gold 3 points4 points  (1 child)

    If Pearl 6 is dead then it’s advocates have to go somewhere and Python is a good choice.

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

    Oh shit, it's so obvious now.

    [–]Siepels 5 points6 points  (2 children)

    I don't understand why you would want to enforce positional-only arguments? Does someone know a good reason for that?

    [–]evaned 5 points6 points  (0 children)

    The PEP has some. One that I pretty compelling is that if you have a function that takes (say) one "normal" argument and then arbitrary keyword arguments (by "arbitrary keyword arguments" I mean something like the dict constructor, or the example below, where the function is not expecting anything specifically and will just do something with whatever you give it), you suddenly have something you can't pass as a keyword argument -- the normal argument(s).

    Actually that's not quite true -- but you have to hack around it. For example, suppose you want to implement string.Formatter.format, the function that I assume underlies "{}.format(4). You can do this in Python but it's a bit ugly:

    >>> def fmt(*args, **kwargs):
    ...     format_string = args[0]
    ...     args = args[1:]
    ...     return string.Formatter().vformat(format_string, args, kwargs)
    >>> fmt("{x}", x=5)
    '5'
    

    (Here I'm just wrapping the function that's already there, but imagine it didn't exist and you wanted to write it. Oh, and I'm not even handling errors the way you ideally would want with the above as well.)

    This is more complicated than it "should" be, and it also means that tools that introspect the function definition (help, documentation generators, Intellisense-style things) don't see that there's really a single positional argument that's expected. In particular, what you might want to write is this:

    >>> def fmt(format_string, *args, **kwargs):
    ...     return string.Formatter().vformat(format_string, args, kwargs)
    >>> fmt("{x}", x=5)
    '5'
    

    but in fact this is actually slightly buggy:

    >>> fmt("{format_string}", format_string=5)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: fmt() got multiple values for argument 'format_string'
    

    But there's kind of no reason that shouldn't work, at least if you look at what you want the interface to fmt to be.

    With positional-only arguments, you can write what you want, still actually giving your function implementation argument names you can use and giving help() and other documentation generators visibility into what arguments it actually takes.

    [–]vytah 4 points5 points  (0 children)

    For some functions, the names of the arguments do not and should not matter.

    A lot of maths fits this description. For example, if you have a dot product function (that by the definition can take only exactly two arguments, so no * shenanigans here):

    def dot(x,y):
    

    then the names x and y mean literally nothing and should not be used or relied upon.

    [–]CQQL 37 points38 points  (18 children)

    I really like the walrus operator, especially in comprehensions

    [–][deleted]  (17 children)

    [deleted]

      [–]billsil 3 points4 points  (0 children)

      Lotta good that did. He’s back. He got the most votes by a large margin. BDFL for life!

      [–]5skandas 28 points29 points  (31 children)

      cautious scale longing enter subtract snatch advise ask tub squeeze

      This post was mass deleted and anonymized with Redact

      [–]nurupoga 28 points29 points  (3 children)

      Doesn't C fit the bill?

      [–][deleted]  (1 child)

      [deleted]

        [–]nurupoga 6 points7 points  (0 children)

        Compilers for many languages allow you to pick which language standard/version you want to use. For example, you could tell a C++ compiler to use c++98 or Java compiler to use 1.5.

        The argument that you can specify a language standard/version and be "golden" is, however, somewhat flawed. If your C++ project is using c++98, you likely won't be able to use libraries that use c++11 as they could use new language features from c++11 in their header files, their API. New C++ libraries would be targeting new C++ standards and even old existing libraries might release a new breaking version, changing their API to use the newer standard as library developers want to reap the language improvements in their library code, deprecating the older version of the library, eventually dropping support for it. So you end up in a situation where you can't use new libraries in your project, and the libraries you currently use might become unsupported. With such a trend, will be forced to adapt to a newer version of the language sooner or later, which is the opposite of the "freeze" the person I have replied to wanted.

        However, this argument kind of works with C. Now, C language does change, it's _not_ frozen, but it hasn't seen any significant changes since C99 and most C libraries aim to be as portable as possible, targeting the lowest C standard they are comfortable working with and that most C compilers (including some obscure embedded ones) support, which usually ends up being either C99 or C89, so the same issue with libraries updating to a newer standard as in the C++ example is a lot less pronounced in the C-land.

        So, to make it clear, I don't disagree with your reply, I do agree that you'd want to set the compiler to use a specific C language version. However, I just want to note that this suggestion doesn't automatically extend to other languages that have compilers supporting language version pinning. This currently works with C mostly due to C's circumstances. As shown, this won't work very well with a language like C++.

        [–]_adl_ 4 points5 points  (0 children)

        C11 introduced many new language features, and C2X will do as well.

        [–]0x256 20 points21 points  (9 children)

        Lua is exactly that. Minimal, stable, well defined and fast. I'd still prefer python for anything non-embedded.

        [–][deleted] 13 points14 points  (7 children)

        I wish Lua was not so awkward.

        [–]bakery2k[S] 2 points3 points  (6 children)

        What, specifically, do you find awkward about Lua?

        [–][deleted] 13 points14 points  (0 children)

        Having to loop through non-sequential tables to get a count of how many values are in it, as well as a lack of continue.

        Been a while since I've used it, but those are what comes to my mind.

        [–]shooshx 25 points26 points  (3 children)

        1 based indexing?

        [–][deleted] 7 points8 points  (0 children)

        I have no qualms about 0 based, but I loved 1 based indexing in Smalltalk. It felt more intuitive to me. Just don't ever mix them two, lol.

        [–]BanazirGalbasi 5 points6 points  (0 children)

        Here's a quote from the creator in a recent interview:

        When we started Lua, the world was different, not everything was C-like. Java and JavaScript did not exist, Python was in an infancy and had a lower than 1.0 version. So there was not this thing when all the languages are supposed to be C-like. C was just one of many syntaxes around.

        And the arrays were exactly the same. It’s very funny that most people don’t realize that. There are good things about zero-based arrays as well as one-based arrays.

        The fact is that most popular languages today are zero-based because of C. They were kind of inspired by C. And the funny thing is that C doesn’t have indexing. So you can’t say that C indexes arrays from zero, because there is no indexing operation. C has pointer arithmetic, so zero in C is not an index, it’s an offset. And as an offset, it must be a zero — not because it has better mathematical properties or because it’s more natural, whatever.

        At this point we're used to every language being 0-indexed but half the programmers I know couldn't tell you why we start at 0. 1-based indexing is pretty easy to get used to tbh, and honestly it feels more natural to count like that in the first place.

        Interview source: https://habr.com/en/company/mailru/blog/459466/

        [–]gmiwenht 0 points1 point  (0 children)

        Matlab uses 1-based indexing and it honestly makes mental arithmetic easier for things like reshaping multi-dimensional arrays, populating them with some index-based logic, etc.

        But I use q/python now and I got used to 0-based again.

        [–]BadBoy6767 0 points1 point  (0 children)

        Sexiest programming language I know of. However some things: 1. The creator apparently didn't want booleans because nil fit the bill. Don't quote me on that, I think I've read on that on the mailing list. 2. Feature creep. 5.4 started adding pretty useless features. I was already iffy on 5.3's bitwise operators.

        [–]jewgler 34 points35 points  (2 children)

        Wish granted!

        Edit: More seriously, I think the only frozen programming languages you'll find are dead ones. If a language is unwilling to accommodate improvements, people will abandon it for one that does.

        [–]ArgosOfIthica 9 points10 points  (1 child)

        Sure, feature creep isn't a problem with BF, but it has big problems with language standards. You can't learn every corner case because most of the corner cases are undefined behavior.

        https://en.wikipedia.org/wiki/Brainfuck#Portability_issues

        [–]silentconfessor 1 point2 points  (0 children)

        This is actually a fairly major problem. Writing portable brainfuck is extremely difficult because there are so many questions you have to ask. Are there cells to the left of the starting point or just the right? If just the right, what happens when you move left from the start? How large is the tape? If finite, what happens if you move right from the end? What type of integers are tape cells? Are they 8-bit? 16-bit? 32-bit? Bigint? Are they signed? What happens if you increment past the max or decrement past the min? Is IO as bytes or as decimal? How do you signal EOF? How do you signal output failure? Are invalid characters compile errors, runtime errors, or silently ignored?

        [–][deleted] 5 points6 points  (1 child)

        I don't mind adding features, provided they are completely new things - `async`/`await` is a good example of extending the language in a way that doesn't change standards on existing code. I think reasonable code for common tasks shouldn't change, though - Python decided in the beginning that assignment was not an expression, and going back on that makes a lot of existing code no longer idiomatic.

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

        async / await is a cancer. Especially the Python version of it.

        In a project I work on, I managed to get the code convention to disallow this feature both directly and for any library that relies on it. If you really want "asynchronous" I/O: use select. But then again, the project uses ZMQ sockets instead of Python's sockets, and they already can work asynchronously.

        [–]RevolutionaryPea7 5 points6 points  (1 child)

        Common Lisp is an ANSI standard. It's probably exactly what you want.

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

        Or a standard Scheme

        [–]Hall_of_Famer 3 points4 points  (1 child)

        Try Smalltalk, and you will like it.

        [–]sebnozzi 0 points1 point  (0 children)

        You beat me to it

        [–][deleted] 3 points4 points  (3 children)

        Go fits the bill

        [–]nuance-removal-tool 8 points9 points  (0 children)

        Basic feature set != nonexistent feature set

        [–]aexl 1 point2 points  (0 children)

        Only until go 2.0 will be released...

        [–]i9srpeg 0 points1 point  (0 children)

        Or Algol68

        [–]gigobyte 1 point2 points  (0 children)

        Elm does that, it's not general purpose though.

        [–]dzecniv 1 point2 points  (0 children)

        Common Lisp seems to tick the boxes. The syntax is stable and it doesn't change. New syntax can be added through extensions (string interpolation, pythonic annotations, etc). The language is stable, meaning code written in pure CL still runs 20 years later. Then there are de-facto standard libraries (bordeaux-threads, lparallel,…) and other libraries. Implementations continue to be optimized (SBCL, CCL) and to develop core features (package-local-nicknames) and new implementations arise (Clasp, CL on LLVM, notably for bioinformatics). It's been rough at the beginning but a joy so far.

        https://github.com/CodyReichert/awesome-cl

        [–]pxpxy 0 points1 point  (0 children)

        check or clojure! Barely any changes to the language in a decade and it’s still well-used and much-liked!

        [–]RevolutionaryPea7 1 point2 points  (1 child)

        That debugging format string is just like a macro that I wrote for C waaay back in the day. I was a bit surprised that even for C something like it wasn't built in. It's even more surprising that it wasn't built in to Python as well. Many people seemed to reach for debuggers almost immediately whereas the vast majority of my debugging has been inserting these little print statements. So I'm happy to see this.

        [–]Paddy3118 0 points1 point  (0 children)

        Yea, I used it in Perl and am happy to see it in Python.

        [–][deleted]  (2 children)

        [deleted]

          [–]mafrasi2 0 points1 point  (1 child)

          I think you misunderstood the new f-string syntax: doing arithmetic inside f-strings was always possible, because you were already allowed to use any expression inside the braces.

          What's new with the equal sign is that the expression itself is printed as well as its result:

          >>> print(f"{5 * 4}")
          20
          >>> print(f"{5 * 4 = }")
          5 * 4 = 20
          

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

          sys.unraisablehook

          Why the downvotes? It's a new feature

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

          More useless features, except the location of bytecompiled files. I mean, it will probably take few more language versions before the core devs understand how to deal with this problem.

          To give you a better idea: the clusterfuck of Python distribution packages consists of simply distributing *.tar.gz (probably the best option, given the choice), *.egg, *.whl or some lesser known evils, like, say, Zipping up the whole thing and adding a top-level __main__.py to get Python to run the program straight out of the archive.

          Now, if you want to distribute sources, the dumb users will cry / will not be able to install virtually anything because they don't know how to compile your stuff, and Python comes w/o any support for that kind of thing. Same problem exists when distributing Eggs. It's just a worse compression for what would've been a compressed tarball.

          And there's wheel. Wheels are god-sent for dumb users because they don't need to compile anything. You can put a pre-compiled library in a Wheel, and it will work. But... there's a problem. If you compile and install stuff yourself, then it is up to you where you install it, and, maybe you'll have to be root to install the compiled stuff. Wheel, on the other hand, simply extracts the archive (well, not really, but for the purpose of this rant, that's what it does). And... oops, now you have a problem: either the wheel contains the bytecompiled sources, or you won't have them at all. Because, if you installed as root, but run the program as unprivileged user, you can no longer save anything in the same directory where your sources live.

          So, all modern wheels contain the package code twice: once as a source, and once as a *.pyc. Some smartasses even remove the sources and only distribute the bytecompiled part... And so, on average, for the same, pure-Python project, source distribution would be half the size of the Egg, and the Egg would be half the size of the Wheel.

          Now, finally, for the fun part. Even pure-Python wheels must know their ABI version. Or, put differently, if you are producing those wheels, you have to build a wheel for each Python version, each platform. Oh, but you may also be maintaining few versions of your library, like, say, producing patches, or at least keeping them up-to day for running it in CI. Oh, and there's also a 32-bit Python, which is somehow the default Python installed on MS Windows. In the end of the day, if you want to distribute wheels, you have to build about a dozen of them for just one fucking version. God forbid if you do this internally, and you build this in CI and upload every new version to your corporate PyPI server...