Hacker News new | past | comments | ask | show | jobs | submit login
Reasons Python Sucks (hackerfactor.com)
405 points by BerislavLopac 11 months ago | hide | past | web | favorite | 535 comments



Most of the points in this article fall on a continuum between irrelevant to dead wrong.

1 (versions) and 2 (installation) have to do with the ecosystem, not the language. Unsigned packages are a real problem, but hating on community-maintained software is just weird. Most software in your local Linux distro's repository is maintained by a "community" that might just be one person working in their spare time.

3 (syntax) seems to be about not supporting the author's own highly idiosyncratic habits, which include deep nesting and putting debug code in the first column (ugh). The result actually seems better for maintainability than the author's own unconstrained code would be.

4 (includes) is in the "exactly wrong" category. Includes as in C/C++ are a horrible way to handle module interfaces. Really, just about the worst option available. Modules are far better, and if the author hates the fact that importing a module might run initialization code wait until s/he finds out about constructors (including __init__ and __attribute__((constructor)) even in C).

5 (nomenclature) is also a bit insane. Lists aren't called arrays in Python because they're not arrays. Next!

6 (quirks) criticizes quote handling in Python, but mentions bash's quote handling without a word of comment about how that's even worse. Different languages have different quoting conventions. Python's might not be perfect, but many are far worse. Get over it.

7 (pass by reference) is another totally-wrong one. Passing by value is not the universal norm in other languages, and it's not even clear what it means sometimes because of deep vs. shallow copies. Passing by reference is almost certainly better for efficiency, and often for correctness as well when copies becoming inconsistent with each other is a concern. I'd rather have pass-by-reference as the default, and have to work to make copies, than the other way around.

8 (local names) barely even make sense. Shadowing names, whether of variables or of modules/libraries, is a bad idea anyway. If you want the library and the program not to have the same name, use a prefix for each role. Expecting the language to handle this for you is just asking for trouble.


> 1 (versions) and 2 (installation) have to do with the ecosystem, not the language.

This argument really summarizes a beautiful and dangerous thing we see in the tech community far too often; you have a strong technical and scientific understanding of the system, but lack product and design thinking.

You're right. Its a problem with the ecosystem, not the language. I'm still not going to use python because of the ecosystem. If the language cares about its users, it needs to care about the Product, which includes the ecosystem. Users interact with the Product, not just the Language.

That's something recent languages like Go, Typescript, Rust, and even old languages like Java, understood quite well. Its not just about the syntax and the compiler; getting users into your language means caring about the IDE, caring about the development experience, the package management, the libraries, basically everything that a developer will touch. That doesn't mean you have to touch all that stuff as the core development team, but it does mean you need a Strategy and a Message.


Note how almost all languages you take as shiny examples of virtue post-date Python: they all learnt from it so much, particularly on things like the stdlib. Python’s stdlib was the gold standard for a long, long time (the “batteries included” slogan was effective for a reason - they really were!). This was not by accident - Guido and others have always had the utmost care for good developer experience out of the box. How many languages pack an editor ready to go? Or a way to install modules with a single command? Not even Java, with all its commercial might, ever achieved that - it barely got a REPL last year, which Python has had for what, 20 years now?

What people decry about “the Python ecosystem” is simply a function of its success as a generalist computing language over 25 years. Nobody uses Typescript to build Linux distributions; nobody uses Go to build lib-gluing GUI apps; nobody uses Rust to write spreadsheet formulas. Python does all that (and more), and this of course resulted in a sprawling community, with tools pulled every other way. If any of your new shiny languages will ever achieve a similar level of popularity, you can bet their ecosystem will also become a complete mess.


> Note how almost all languages you take as shiny examples of virtue post-date Python

That's because Python is old. It's as old as Haskell. It's older than Java and JavaScript. It's older than the Borland Delphi and C++Builder IDEs. When Python was released, C++ was only 5 years old. Python is older than Linux. Rust and Go are both older than C++ was when Python was released. Hell, when Python was released, Perl was only 3 years old.

Further, Python's age doesn't mean that we can't or shouldn't criticize it. You shouldn't get a free pass on what you could do better just because you've done other things right. We should absolutely point to what doesn't work well and lobby for improvements even if they require significant overhauls. While, yes, Guido and company have worked very hard on developer experience out of the box, it would be really nice if someone would care about system administration and the long tail, too. It's great that your engineers love your tools, but someone still has to live in the homes that they build.

It's not like anybody here is shocked or confused when they hear people complain about Python versioning being a rats nest. I'd wager we've all experienced it at least once just like we used to have problems with multiple concurrent versions of the Java Runtime Environment being installed or other fun forms of dependency hell. If virtualenv or conda are such good solutions, why do so few projects seem to use them or deploy with them? If popularity or ubiquity are the measure of what's good in the ecosystem, doesn't that suggest that there's still a problem to be solved? A language should direct programmers to styles of system design that makes system management easy and clear, and Python does not do that at all. Is there a problem with complexity? Is there a problem with lack of education?


> Python's age doesn't mean that we can't or shouldn't criticize it.

I don't disagree, but for people paying attention, it's become really boring. "I've just switched from $language to Python [because work told me to], and this is what I hate" is almost a parody, at this point. It's a sign the ecosystem has reached ungodly proportions.

> If virtualenv or conda are such good solutions, why do so few projects seem to use them or deploy with them?

I don't know about conda, but venv/virtualenv was the deployment standard before Docker happened (I'd argue it still is, for people who won't/can't use containers). Personally, I still like to use venvs even in docker.

> If popularity or ubiquity are the measure

How do you measure popularity? So often the pip hate seems to come from a very loud minority. Meanwhile, the ecosystem continues to expand and people keep using pip/venv without any problem, because it works for most cases (now that wheels have fixed the "can't build on Windows" issue) and it's simple enough. Somebody upthread compares pip with Maven, and it makes me laugh: the Java ecosystem is shrinking and the Python one is exploding, and stuff like the user experience of Maven vs pip is among the reasons.

> there's still a problem to be solved

Of course there is, there is always one; I'm sure that you'll find Cargo critics too, once that community grows enough, and there are plenty of loud NPM haters. Trying to be all things to everyone takes careful reasoning and herding - because it's a political problem as well as a technical one, in a polis that keeps growing. Shouting PIP SUCKS!!111!! is only going to result in more crap like pipenv. The shortcomings of pip have well-known for a while, but the solution is not trivial, as pipenv demonstrated.

> A language should direct programmers to styles of system design that makes system management easy and clear

That's like, your opinion, man. One of the strengths of Python is that it's just as structured as you want it to be, and no more - even when that might not satisfy someone's arbitrary definition of a Platonic application.


I'm not sure where you get the idea that the Java ecosystem is shrinking when every quantifiable measure (stack overflow polls and data releases, GitHub data) point in the other direction.

Further, if you factor in the growth of languages like Kotlin and Scala the JVM is blowing Python out of the water.


> This was not by accident - Guido and others have always had the utmost care for good developer experience out of the box. How many languages pack an editor ready to go? Or a way to install modules with a single command? Not even Java, with all its commercial might, ever achieved that - it barely got a REPL last year, which Python has had for what, 20 years now?

The Java module experience is miles ahead of the Python one. There's no global module path where you can install different things on different machines, nothing that screws up if you accidentally run it as root, no stateful virtualenvs where different shell windows run the same project with different versions of python. You just list your dependencies in your POM (which, yes, is XML; it was the early 2000s, everyone was doing it, and it does make it easy for your IDE to put a nice editor interface on it) and that's it, you're done: released versions are immutable and transient dependency resolution is deterministic, so you don't have to worry about pinning/unpinning or anything like that. There's a well-known repository that all the important libraries are in; as and when you want to have a company private repository there are a couple of standard ones you can pick from and run, either just for your own artifacts or proxying third-party libraries from the central one as well. It all Just Works.

Maven came out 14 years ago and Python still doesn't have anything that works nearly as well.


> and that's it, you're done

You are seriously comparing Maven, a huge and over-complicated xml-based system that is not even installed by default and that people hate so much that there are umpteen alternatives (gradle etc), with `pip install -r requirements.txt` that works out of the box? I just can't even...

> Python still doesn't have anything [like Maven]

And I thank the Gods for that.


> a huge and over-complicated xml-based system

It's not complicated. It's verbose, that's the nature of XML, but the behaviour is very direct.

> is not even installed by default

Which is the better approach because it means it's not coupled to specific versions of the language itself. You can use a single maven install to manage multiple versions of Java (or vice versa); if you need to rely on a new maven feature in a project that's stuck on an old version of Java, it's no problem.

> people hate so much that there are umpteen alternatives (gradle etc)

There will always be refuseniks (particularly for pioneers; most of the things people hated maven for are the same things people love cargo et al for) but the Java ecosystem has done a very good job of keeping everything interoperable; it doesn't matter if one of the libraries I call builds with gradle, because there's a common packaging/dependency/repository standard, so everything will just work exactly as it should.

> with `pip install -r requirements.txt` that works out of the box?

Until you come to upgrade, or until you run it the wrong way (e.g. forgetting to start your virtualenv first) and mess up your system install.


> with `pip install -r requirements.txt` that works out of the box?

Works out of the box until you're missing a distro package that is required to build a dependency that needs to be compiled from source.

I've never used maven so I don't know if it is better or worse, but I am not a fan of languages having their own package management system that has not integration with the distro one (which probably also offers some of the same packages, and mixing them breaks things in subtle, annoying ways).


> Works out of the box until you're missing a distro package that is required to build a dependency

With decently-maintained packages on PyPI, i.e. shipping prebuilt wheels for the most common architectures, that's no longer a problem - unless you insist in using the distro package-manager, in which case you should pick it up with the distro people. If you stick to pip+venv, on most common architectures, these days chances are you only need a simple `install`.

> I am not a fan of languages having their own package management system that has not integration with the distro one

You must feel miserable then, considering it is pretty much all modern ones. Go, Rust, Node, Perl, Python, PHP, Java, even C# and friends...

There will always be tension between what developers want (the library released yesterday and can be forever tweaked) and what OS/sysadmins want (the library that has been tested for months and can be locked down). This is why platform-agnostic delivery systems for developers are so popular, and it is not going to change any time soon.


> I've never used maven so I don't know if it is better or worse, but I am not a fan of languages having their own package management system that has not integration with the distro one (which probably also offers some of the same packages, and mixing them breaks things in subtle, annoying ways).

Mixing is where Python (and Perl, even though it is integrated with the distro package management) go wrong, IME. Maven is isolated from the host package management but completely (or at least completely enough, in practice); most OSes don't bother trying to ship system-wide versions of Java libraries. The OS manages the JVM (and Maven), Maven manages whatever libraries an app wants on a per-app basis, and neither interferes with the other.

This does mean you get very little help managing dependencies of JVM libraries on native libraries; fortunately those are rare enough in the JVM ecosystem that you can handle the few that do occur by hand, IME.


> I've never used maven so I don't know if it is better or worse

It's about the same for packages with a native component. Local build tools are still needed.

Java stuff is slightly less likely to have a compiled component in the first place, in my entirely subjective impression. Maybe that's because of convention, or performance; I don't know. But when a compiled dependency does exist, and nobody included a prebuilt chunk of binary for your architecture in the jar, a build is needed in roughly an equivalent fashion to what pip (or npm, cpan(1), etc.) do.


I’ve always found thinks like LWJGL tricker than pyglet etc.


That's because LWJGL is complicated just like DirectX is. It uses low level API that calls native C code. It aims to give you a thin Java API layer above OpenGL, Vulcan, controllers, audio, etc.

https://en.wikipedia.org/wiki/Lightweight_Java_Game_Library

Whereas pyglet, seems to me like a high-level regular game engine that includes a widget library.


At least with most packages this can't happen anymore.

If a package owner distributes a wheel, you're good. Most packages do now.


I do not see your comment as fair to the parent:

> there are umpteen alternatives (gradle etc)

there was ant (build tool) then came maven which is much more flexible and contains dependency management capabilities (and _is loved_ by many) nothing to add that the parent did not already say. Then came gradle which has faster build speed and features like incremental builds and a Groovy based configuration.

pip did not ship with python for a long time, and has problems of its own.


> Then came gradle which has faster build speed and features like incremental builds and a Groovy based configuration

You make it sound like adding Apache Groovy for configuration to a build system is an improvement. When build systems with a declarative config language, such as make, ant, and maven, replace procedural build scripts, that's the improvement. Using a procedural language like Groovy instead is a large leap backwards because it encourages programmers to add unneeded procedural features to build scripts, creating an unmaintainable mess.


I agree, my above comment was aimed at this part of the parent comment:

> a huge and over-complicated xml-based system

It was an attempt to show that there are not 'umpteen different alternatives' and the ones that exist, do so for a reason. Though looking back, I should have worded it in a better way.


There's a lot I don't like about Maven, but its a vastly better solution than pip. Support for dev dependencies and no need to manage virtual environments more than makes up for its complexity.


There's pipenv now, which wraps pip and venv to do all that in a super simple way.

It has some performance issues but it's still quite a recent project.


Pip was not part of Python by default for a long time.


Right.

A post on my blog:

pip now installed with Python 2.7.9:

https://jugad2.blogspot.com/2015/03/pip-now-installed-with-p...

which mentions:

https://docs.python.org/2.7//installing/index.html

and::

https://pip.pypa.io/en/latest/installing/#pip-included-with-...

I've used Python versions before that, where you had to install pip separately, and it was not trivial (to find it / install it), although not very difficult either.


> transient dependency resolution is deterministic, so you don't have to worry about pinning/unpinning or anything like that

Is that true? I see a lot of pom.xml files specifying version ranges for dependencies. If I have a project specify a dependency that has its own dependencies with version ranges specified, then there doesn't seem to be an easy way for me to pin all the subdependency versions. (Yarn and npm both generate a lock file automatically, pinning all dependency and subdependency versions, which seems great.) It seems like I could specify all of the subdependencies in my own pom.xml file too with specific versions, but maven doesn't appear to make that easy to do.


> I see a lot of pom.xml files specifying version ranges for dependencies. If I have a project specify a dependency that has its own dependencies with version ranges specified, then there doesn't seem to be an easy way for me to pin all the subdependency versions.

Version ranges are indeed nondeterministic, that's why they're discouraged by the community (and IME very rare).

Pinning specific ones is easy enough (I tend to just look at the dependency tree in eclipse and right click -> lock transitive dependency version); I can't find a way to do that for all transitive dependencies though (I've never had more than a handful of transitive dependencies that used ranges, so it's not been a problem I've had tbh).


This begs the question though why can't the python ecosystem simply evolve? Is that too much to ask? You say these languages have learned so much from Python. If there is so much to learn in terms of do's and dont's, why doesn't Python simply follow this advice?


To a significant extent, it has. The article assumes you will use distro packages for the language, and pip for installing libraries. And that used to be standard practice.

Today, if you are interested in a reproducible build environment, you will use pyenv to locally install an interpreter and stdlib, and pipenv to locally (to your project) install libraries and dependencies.


In my mind, creating a Python 4 that would be a consolidation of all of these things under one release umbrella (or at least a major release), so that things could be synchronized would be a good way to handle this. Right now, things are all over the place, and things that used to work 5 years ago still kindof work, rather than either working or failing. And it's that "partial working" that really gets developers ticked off.

Rust is doing some interesting things with Rust 2018 Edition, where there is a complete system harmonization point. If it takes off there, I'd expect other languages to pick it up.

My question though is a bit different: Can Python catch up?

I know - strange question, but momentum is like that. It's often hard to see something going faster than you until it passes you. Perl is probably the best example, where it was way out front, and the PHP, which was way behind, but going mach 10, passed it quickly in the web space. Perl never caught up, and has lost significant relevance compared to the stature it used to command.


I agree. imo python has been steadily falling behind ever since the 2x/3x split.


I would argue that for most projects (for most of my projects, anyway), this is still a sensible default. Except you should probably still be using distro packages for major libraries too.

Consider; the reason for installing dependencies locally to your project is that it allows you to be precise about what library version you want. For example in Rust, we have Cargo.toml and I can have dependencies like primal = "0.2". I hate this model. It takes us right back to the old days of projects shipping whatever version of a library they want and never updating it. For small projects this is a constant source of security issues and code stagnation.

I groan internally every time I see a project I'm interested in is written in node. Not because I dislike the language, but because I know when I download and build it there are going to be a dozen outdated dependencies hardcoded in the config file, some with "severe" security warnings flagged by npm.

I advocate letting library developers and distribution maintainers do their jobs. Most of the time new versions of libraries are supposed to be backwards compatible. When they're not the distribution can ship multiple versions of a library and make sure they all have security updates backported.

There are cases where managing all your own dependencies is important (like closed source web applications), but I think those are exceptions. Dependencies were supposed to be a solved problem.


Note that that will select any version in the 0.2.x range, not exactly 0.2.0. This is the range of versions that are interface compatible. If there’s a security release for the 0.2.x line it will pick it up. (Though you have to run “cargo update” in your project, though there are also tools to tell you if and when you need to do this for security issues.)


> This begs the question though why can't the python ecosystem simply evolve? Is that too much to ask?

It evolves constantly. Python packaging then and now has massively changed (largely without breaking compatibility, mind you). It's not perfect, but it is much better than it was in 2005.


Python was my first programming language, and though I don’t personally reach for it anymore (at least very rarely), it’s package management is the one large reason I don’t want to use it. All the other complaints I see about the language I don’t give much merit to, people like to complain, every language has it’s quirks and some people just can’t look past them. But, while not a solved problem, there are tons of great package managers Python could learn from to make pip not suck. How is there not a standard way/built in lock system, or even a single standard package manifest (Gemfile, package.json, cargo.toml, mix.exs, or even composer.json) — which all (except maybe composer, it’s the one I’m least familiar with) are supported by default by the language, and have version locking.

I know pythons big thing is backwards compatibility, and breaking things is almost the biggest sin in that community (python2...), and maybe I’m ignorant, but I just don’t see how implementing a single standard package manager with a standard manifest and lockfile would break anybodies anything. All they have to do is make it so pip still works without it, and if they want to see how to do that, just look at NPM, who before this year was notorious for errant package versions due to the lack of lockfiles, but now they have it and I think pretty much everyone was happy about it. They literally could just port bundler to Python and call it a day, the code is open source, they can look at how it works, and it’s one of the most well regarded package managers out there, I’ve maybe run into 2 issues ever with it. The only other package manager I see get more praise is cargo, which they could (should) also look at for inspiration.

I know things have been improving. When I was using Python daily, the standard was still punching in some incantations to start of Virtual Env (which still confuses the hell out of me to this day) and piping the contents of a requirements.txt file into pip (with I think at least some level of version locking). But, from what I’ve gathered is there are some solutions being built to bring pip out of the early 2000s, but they’re quite fragmented efforts, and they all fall short in different places.

Python is a great language, pip and all the bullshit that comes with it is terrible.


Maybe you haven't used it in a while but requirements.txt lets you specify packages and lock (or unlock) the version.


Yeah, I’m aware you can pin versions in a requirements.txt, but does that also pin those packages dependencies? For example, if I had two pinned packages (A, B) and they both depend on package C, what happens if A updates to depend on a higher version of C that contains changes that break package B? Can you pessimistically (Gemfile ~>, or NPM ^) pin versions, or are you only limited to just equals X and the greater/less than operators?

Now that I’ve wrote that out, I’m wondering if packages can even pin their deps required versions. I’ve only published one python package years ago, and I can’t remember if that’s possible. Which I shouldn’t have to even worry about in a modern package management system.

I know the above example wouldn’t be possible in any of the systems I mentioned above, because the aside from installing packages, they ensure that the versions of every package are compatible. That’s why lockfiles are so important, assuming you’re downloading decent packages, the package manager can assure you that they’re all going to work together, also allowing you to update without having to worry about some random deeply nested dep isn’t going to break some other package when it gets updated, making updating (usually) a breeze.

In pips current state, it seems more or less like a slightly more strict NPM before yarn made them get their shit together (still vastly prefer yarn). The only difference is your packages are wherever Venv puts them instead in a “pip_modules” folder in the project, which is at least better than a global free-for-all. Though, that also might be less of a headache than dealing with Venv, I hope it’s gotten better since I’ve used it, because that was always such an annoyance.


requirements.txt is a lock file, generated by pip freeze, which defines the exact version of all packages in the current environment.

> Now that I’ve wrote that out, I’m wondering if packages can even pin their deps required versions. I’ve only published one python package years ago, and I can’t remember if that’s possible.

It is.


Ah, you see, you actually only know second-generation packaging. "pip and all the bullshit [i.e. virtual envs, setuptools]" is actually what has been developed to address issues in the older distutils (and easy_install) tooling, and are fairly recent additions to Python. Buildout comes from the same era (~2005ish and was built on very different principles; it's still used in some niches.


Pipenv and poetry both provide lock files. Both handle virtualenvs and version resolution.


"Pipenv: promises a lot, delivers very little":

https://news.ycombinator.com/item?id=18612590


On that note, I wonder why no one has tried to do packaging right, like really right, and then make it avaialble as a system for multiple languages. Someone should get on that. And I don't mean yum/apt/etc I mean like a universal package format for programming language packages with all the best bells and whistles, so fledgling languages can have a mature package system at their inception by simply linking with a C API.



Conda which was built for Python (Linux, Windows, OSX, arm) handles package dependencies. Anaconda is built on it and R packages are also distributed with it. If you really want to, you can build perl packages or really any kind of package you want.


There's an XKCD on the subject: https://imgs.xkcd.com/comics/standards.png


It always surprises me when new languages don't make a REPL a top priority. It's such a crucial thing for a good developer experience. Rust still doesn't have one, as far as I know, and it's a major pain point for me as a light user of the language.


Rust expressions are very contextual due to things like the borrow checker, though, and a REPL basically means that every bound top level name has an infinite lifetime. That would make some things very awkward.


Rust has a REPL under development on github: https://github.com/murarth/rusti

Seems to be active up until around July-August


I feel very strongly about never accepting the age old excuse "they were killed by their success, have pity on them." All that's really saying is that they didn't know how to manage the success, which is simply a leadership failure.

And no one could possibly argue that Python hasn't had irregularly poor leadership for a long while. I'd go so far as to say that its success is in spite of its leadership, culminating with Guido basically giving the middle finger and walking away this year. Can't say I blame him.

It is the nature of newer languages to learn from the mistakes of past ones. But the big irregularity which wasn't learned is that these languages I listed have very strong leadership. In the case of Go, I'd argue the strongest leadership of any language ever made, often to the disappointment of the community.


Your comment is a great argument for languages that make compromises to be good at some domains and not others. I’ve hated every general-purpose language I’ve ever used, and the ecosystem mess is probably why.


Oh yeah, totally. The more domain-specific you get, the more coherent (and narrowly powerful) your experience can be.

However, there are obvious costs in terms of context-switching, learning curve, career flexibility, and so on, which is why general-purpose languages are so popular.


>>Not even Java, with all its commercial might, ever achieved that - it barely got a REPL last year, which Python has had for what, 20 years now?

Java has had Eclipse right from when users needed it for prime time.

And yes, Python's repl is barely a REPL. You are better of writing code and running it. Its not exactly Lisp experience.


I get your point, but do those other languages not often have differing versions available as well? Its equally possible to install many different major and minor versions of Java, for example.

And I regularly run into issues with people using non-distribution Java packages that don't really integrate as well with the OS as the packaged one would.

I feel like some of his complaints about the ecosystem there were really just complaints about his specific setup.


Yes, other languages have the ability to have multiple differing versions as well BUT the big difference is that most have a relatively easy and well-known way to install and set up the environment. In Ruby, if you wanted to run multiple versions of ruby on the same machine, you'd use RVM. Python is particularly hard to set up the environment for. I'd rather set up the environment for Ruby, PHP, Node or Perl than Python. Every other language has an easier path to get up and running. Even upgrading is usually fairly easy as well.


> PHP

really?

which way?

embedded? fastcgi native? slowcgi? fpm? cli?

natively managed or via process control?

which process control?

which ini file is configured for which application again?

are my suexec/whatever wrappers working?

if not, which user/group/permission N levels up is causing the issue?

which bytecode cache to use? is that working properly?

why aren't my URL rewrites working properly?


I don't disagree with any of what you said, but I'd say its importance is up to the developer. Characterizing the difference as a "lack" is pretty rude IMO. The language and the ecosystem should be separable to some degree. It's called modularity, and I won't say you lack familiarity or appreciation of that even though you obviously prioritize it differently. If the author hates those particular parts of the ecosystem so much, s/he is quite free not to use them, probably more so in Python than in most other languages due to the explicit "batteries included" ethos of its creators. If you want to think about the "product" and a "strategy" (all for an OSS project) then that's great. The world needs more like you. Knock yourself out. Other people quite legitimately prefer to focus their attention and energy on the core technology.

The real problem here isn't that Python was used to mean the language alone when it should have been used to mean the entire ecosystem, or vice versa. The problem is that it's ambiguous. I didn't mention that points 1 or 2 are about the ecosystem to invalidate them. There are stronger refutations available. I was just trying to identify a scope that the author had left ambiguous.


It's funny how far off the author was, because there are genuine things to complain about with python, though they may not be exclusive to python.

I regularly wish python required some sort type indication for function parameters, because dealing with libraries that take complex objects as function parameters can be an absolute nightmare.

I wish python had some equivalent to the switch statement that didn't involve workarounds with dictionaries, because sometimes you just want a clean way to deal with a value that can take 7 different forms.

I regularly find myself wanting to write something of the form do_x() if a.y() This is stupid, but I still kind of want it since do_x() if a.y() else do_y() is valid

Edit: Also fuck this whole "it's so cool to shit on Java" thing. Grow up. You aren't in college anymore. You should recognize that Java fills its niche effectively, and does a decent job of being semi-fast, portable, easy to read and maintain, and safe.


Edit: Also fuck this whole "it's so cool to shit on Java" thing. Grow up. You aren't in college anymore. You should recognize that Java fills its niche effectively, and does a decent job of being semi-fast, portable, easy to read and maintain, and safe.

The "it's so cool to shit on Java" thing served an important purpose and is probably close to being retired, but it's easy not to understand if you weren't a Java programmer some time in Java's heyday. There was a culture and a set of assumptions around Java that today you could probably only find in the most stodgy and insular corporate programming environments. I helped introduce Java into a development group and ended up hating the people and practices it infected us with. The hatred is not completely unrelated to the language (which runs on a great platform and is not a horrible language) but it would never have attached itself to the language without some of the pathological culture surrounding it.


> The "it's so cool to shit on Java" thing served an important purpose and is probably close to being retired

I don't know in which world you live in, but in the real world Java is by far the dominant platform for web services.


> I don't know in which world you live in, but in the real world Java is by far the dominant platform for web services.

I read it as meaning 'the "it's so cool to shit on Java" thing' is close to retirement, not the language itself.


You're right. I was quick to assume that the comment was yet another jab at java. Unfortunately I have to deal with developer who insist in that line of argument, and it gets really tiring.


Does that apply to any web service that meets the following criteria?

  * Created in the last 5 years
  * Built by an organization without a large Java heritage
I bet that number goes waaaay down, way quick.


For sure it may, but I wonder how many of those decisions will be regretted? Java is a fast portable language with a decent type system and a bevy of time tested libraries. It certainly isn't perfect, but modern Java is a decent development environment (and this comes from a Rubyist). I suspect only Go can match its speed, productivity, and safety.


Why choose Java when you can pick from the more modern languages that also run on the JVM: Kotlin, Scala, Clojure, etc. You get all the advantages of speed and libraries without the imo bad language design.


I would bet that C# can match or exceed Java’s speed, productivity, and safety as well.


And JavaScript is probably the most written in language in the world... doesn't mean it's great. Though, it is good enough for most things.

I always liked C# better than Java though.


That provides some interesting context to that attitude, thanks.


> I regularly wish python required some sort type indication for function parameters

You can use type annotations, either via the builtin lib or 3rd party libs!

https://docs.python.org/3/library/typing.html

> do_x() if a.y()

I feel this. You can use `a.y() and do_x()`, but I'm not sure how people would react :o


Type annotations don't seem to actually have any effect, though? Consider:

    >>> def add(a: int, b: int):
    ...     return a+b
    ... 
    >>> add("a", "b")
    'ab'
This should be an error, even if it's a runtime error.


it's optional typing. Running it through mypy will result in an error. If you are using type annotations I don't see why you wouldn't also be using a proper IDE or testing your code.


There doesn't seem to be any interest in checking type annotations at run-time. Unfortunately, without doing so, type annotations look authoritative but are essentially just comments.

I believe Julia is an example of a dynamically-typed language which does perform such checks at run-time - including as part of its support for multiple dispatch.


I've been using enforce for this purpose (https://github.com/RussBaz/enforce). It provides decorators that enable type checking where wanted. This gives the advantage of migrating existing code to type checking at your own pace at least.

On a general note I've come across a few cases, when pushing python's type annotations to their limits, that force you to put the type names in string quotes, and that makes the whole thing feel like a hack. It's better than nothing for my use cases, but if I remember correctly both mypy and enforce have problems in common that are probably coming from the way python itself is built, such as self reference in a class definition.


> I've come across a few cases, when pushing python's type annotations to their limits, that force you to put the type names in string quotes

With pep-0563, python3.7, and `from __future__ import annotations` this should no longer be necessary. Those are some pretty detailed caveats, but I have been using it in places where I can and it is so nice.


Python has a "we're all adults here" mindset, which means it will allow you to reach into internals of libraries, ignore type hints, etc because maybe you actually know what you're doing and know the drawbacks but just need a quick solution (lots of python code is small throwaway scripts).

For actual projects you can use a static type checker like mypy.


Make mypy type checking part of the CI pipeline. Done.


Type annotations have no effect by default. You're free to use a tool to do compile or runtime checking though.


> `a.y() and do_x()`

If you're happy to write it that way round, why not just use `if a.y(): do_x()`?


It's worth noting the second can't be used inline.

I don't know the history, but it seems like that might be one of the reasons `x() if y()` isn't allowed in python. [x() if y()] if it existed, might carry an explicit else None.

On the other hand [x() and y()] does something different when y() is falsey.


>I don't know the history, but it seems like that might be one of the reasons `x() if y()` isn't allowed in python.

Maybe I misunderstood your context, but if not, conditional expressions do exist in Python:

  $ python
  Python 2.7.12 |Anaconda 4.2.0 (32-bit)| (default, Jun 29 2016, 11:42:13) [MSC v.1500 32 bit (Intel)] on win32
  Type "help", "copyright", "credits" or "license" for more information.
  Anaconda is brought to you by Continuum Analytics.
  Please check out: http://continuum.io/thanks and   https://anaconda.org
  >>> 1 if 1 == 1 else 2
  1
  >>> 2 if 1 == 1 else 2
  2
  >>> ^Z

  $ py -3
  Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
  Type "help", "copyright", "credits" or "license" for more   information.
  >>> 1 if 1 == 1 else 2
  1
  >>> 2 if 1 == 1 else 2
  2
  >>>
Using nested conditional expressions to classify characters:

https://jugad2.blogspot.com/2017/04/using-nested-conditional...


Sorry, the code shown in the two Python interpreter sessions above, is not a good example. To make the concept of conditional expressions more clear, this example is better:

  >>> from __future__ import print_function
  >>> for i in range(3, 5):
  ...     print(i, "is", "odd" if i % 2 == 1 else "even")
  ...
  3 is odd
  4 is even
which can be shortened to this:

  >>> for i in range(3, 5):
  ...     print(i, "is", "odd" if i % 2 else "even")
  ...
  3 is odd
  4 is even
Now print() is printing 3 values: i, "is", and either "odd" or "even" based on the boolean condition.

That works in both Python 2 and 3.


Against pep-8 standards. I do end up using that sytax quite a bit anyway though.


> PEP8: This document gives coding conventions for the Python code comprising the standard library in the main Python distribution.

It's fine not to follow PEP8 for your own software. It's a useful guide, not a law.


> You can use type annotations, either via the builtin lib or 3rd party libs!

> https://docs.python.org/3/library/typing.html

I know, but tragically, because I am not forced to, I never do. I do appreciate that feature though. Maybe this will be the impetus I need to start taking advantage of it.


I started doing this about a year ago and it has made me a better Python developer. Getting into the habit of always writing a type annotation was hard at first, but you quickly get used to it. It helps if you configure mypy to complain at you if the annotations are missing.


You'll have a much better experience in an ecosystem where typing is the widespread expectation, IME.

Try an ML-family language if you haven't already - I find they combine the best parts of something like Python and something like Java. There was a post pushing F# earlier today.


If you're starting a new project you can force yourself by running mypy's strict mode in CI.


Indeed. My personal bugbear is lack of multiline lambdas. I use these all the freaking time in JS and it's infuriating that I can't in python.

At the end of the day it's a minor complaint of course.


I think it makes more sense in a language where you have to write callbacks all the time. I consider it a feature in Python, because by using `def` you're forced to imbue the function with meaning by providing it a name. Otherwise, it's just reading through a potentially complicated function without any context of what it's supposed to be doing.


All languages, including python, where you do a lot of asynchronous io require callbacks all the time. It's only recently python got await-support to deal with this. Single line lambdas is a real PITA when your not using asyncio and python maintainers have said they don't plan to change it :(


It also makes sense if you're trying to do any sort of functional programming.


I use Python for functional programming and I don't miss multiline lambdas. I have a large monitor, so vertical space isn't at a premium for me.


Though it doesn't take that much of an effort to just name your callback function and pass it as an argument. Python functions are first-class "objects" that can be passed around, use them!

I've always found Python lambdas to be uncanny, weird and error prone. It's kinda syntactic sugar for expression-only defs with counterintuitive scope rules. I would recommend just not using them and fall back to named functions. It's just one extra line and that way you get to name it, which is always a nice thing.


This is extremely heavyweight syntax for something I like to use as a lightweight construct. If I wanted a function, I would make something a function.


x = lambda y: <code goes here>

def x(y): <code goes here>

What's so heavyweight? A new line and a tab?


IMO lambdas are useful because they reduce mental overhead by allowing the developer not to give names to trivial functions [1]. Compare these two equivalent pieces of code:

    youngest_person = min(people, key=lambda x: x.age)

    def get_age(x):
        return x.age
    youngest_person = min(people, key=get_age)
This may not seem like a huge difference, but using lambdas scales better.

[1] Incidentally, this is the same reason why I don't miss multi-line lambdas (in Python): multi-line functions are seldom trivial.


Yeah in the context of multi line functions the overhead is

A new line A tab A return

This does not sound heavyweight to me (in that context). In the context of a single line function I see the utility of lambdas.


Guido Van Rossum and the development team didn't want the language to be functional. There is very little functional support in Python in general, besides not supporting first class functions over multiple lines.


> There is very little functional support in Python in general, besides not supporting first class functions over multiple lines.

You are confusing “first-class functions” and “anonymous functions”, which are completely different things. Python functions are first-class, independent of length.


It depends who you ask.

Some definitions of "first-class function" require that it have the same value semantics as any other first-class type, which would include a literal syntax/anonymous functions.


> I regularly find myself wanting to write something of the form do_x() if a.y() This is stupid, but I still kind of want it since do_x() if a.y() else do_y() is valid

cough maybe you should be programming in Ruby ;)


Yes, I was thinking that too. Hell, you can write:

    do_x if a.y? 
Unfortunately (or fortunately, depending on perspectiven- great power and all that), Ruby allows things that Python doesn't and it's not hard to find yourself in a write-once mud bath.

I do like Python's modules over how Ruby handles them too. Ruby's import (require) is akin to C/C++, with modules as a separate idea. I prefer the style of importing exactly what you need.

But I know Ruby better so it's my go-to.


Or Perl 5. `do_stuff() if $a->y();` is a common idiom.

Or Perl 6. `do-stuff() if $a.y;` is the Perl6 version. You can even declare your variables to be sigil-less so you end up with `do-stuff() if a.y;` if you want. See my Perl6 Advent Calendar article for a quick tour of the language, available docs, with practical examples focusing on using Perl6's novel features to write a self documenting command line script.

https://perl6advent.wordpress.com/2018/12/16/

I've written code in everything from assembly to ML, Java, and Prolog. Perl6 is the only language I've used that I would "mind-expanding". The more I use it, the I realize how radical its design is.


Agreed. After indentation-as-syntax -- which I've always hated, and I've been using Python since version 1.5 -- lack of switch statement is my #1 problem with Python.


> indentation-as-syntax -- which I've always hated

What would you prefer? C-style syntax? Ruby-style `end end end`?


Literally anything that allowed easier linting, less "wait am I doing it right" regarding multi-line statements, etc.

If that is a keyword, or brackets, or whatever, I would prefer that. You can't minify or easily lint python. And you can't easily tell if there are mixed indent methods (tabs/spaces) and IDEs struggle with it vs. a simple bracket structure.


C-style syntax. I'm not a fan of using something that is inherently invisible to scope my code, and be yelled at if it's not quite right (I'm writing this in Python because I want it to be done quickly; just leave my code style alone and I'll fix it later!)


> fuck this whole "it's so cool to shit on Java" thing. Grow up.

Hey! Let people not like things, guy!

I put my pants on, go to work, pick up my heavy JVM, and crack away in the Java mines for 8 hours.

When I finally get home, tired, broken, achy fingered, and filled with disappointment for only making it to the AbstractAbstractFactory, rather than the AbstractFactoryFactory, I think I've earned a few "Yeah, with other type systems you'd be able to..." style complaints.

Greener pastures and whatnot.


> I wish python had some equivalent to the switch statement that didn't involve workarounds with dictionaries, because sometimes you just want a clean way to deal with a value that can take 7 different forms.

I am with you there. I have often wished for a C-like switch(). That said, I think the best Python leans towards the functional, so instead of a switch(), it probably would be better to come up with some kind of multi-arm match more in line with modern functional language semantics. That could also be more flexible with respect to the type of the tested expression. Maybe something akin to the Rust match syntax.

> I regularly wish python required some sort type indication for function parameters, because dealing with libraries that take complex objects as function parameters can be an absolute nightmare.

Well, of course Python has type annotations. TBH, I don't use them. I tend to be rather pedantic about Sphinx doc for functions, most especially __init__() constructors. Kind of old-school and manual, but it works -- make doc and read it in your browser. The other thing I do in constructors is 1) make them very tolerant of what gets passed as a parameter, and 2) be very pedantic about raising ValueError in the right place, at the right time, with a clear message.

I have a good friend that I argue with regularly -- now this guy is a compiler front-end guru -- ex-Sun senior staff level C/C++ compiler tech lead, very smart -- but we argue regularly about Python idiom. He litters his Python with extremely un-idiomatic assert(isinstance()) to check incoming parameters. I think C poisoned this man's brain with respect to type checking.

There are two things wrong with his code: 1) It raises the wrong exception, the correct exception is TypeError, not AssertError. 2) It totally breaks __int__, __float__, etc, so I can't implement those on custom classes.

In my constructors, to the extent possible, I do "type checking by re-construction" -- parameters i and p gets run through something like:

  self.i = int(i)
  self.p = PClass(p)
So if you send me a string for i or something weird that implements __int__(), everything is cool. And PClass.__init__(self, x) is responsible for turning p into an instance of PClass, leaving it alone if it already is a PClass, or raises ValueError. This idiom makes type checking as tight as you want it to be, but doesn't break Python Zen.


The pythonic way is to do duck typing, meaning don't check the params types at all. Assertions are ok to check things that should never happen. But if you're gonna put them everywhere then better use type hints. I'd use TypeError on public APIs, for private APIs is as bad/good as assert.


What I describe is duck typing, with eager authentication of duck specie. Exceptions raised at point where the greatest clarity of error message is possible.

Aspirationally, anyway.


I regularly wish python required some sort type indication for function parameters

As others pointed out, Python has optional type annotations and has had them since 3.0. They're static-only (you don't get runtime type checking from them), though, and getting what you want out of them will effectively require every single function, method and variable in not just your codebase but in every third-party dependency and all of their dependencies to be annotated.

Also, annotating "complex objects" tends to be an unreadable mess. If you have lots of functions which take complex objects as arguments, rather than a more clear list of parameters, that's probably not going to be solved by type annotations; it will be solved by rewriting the functions. In other words, you probably will not be helped by:

    def foo(o: HugeLongComplexTypeDefinition) -> OtherHugeLongComplexTypeDefinition
But you would be helped by:

    def foo(widget: Widget, operation: WidgetOperation, tolerance: float) -> Widget


> I regularly wish python required some sort type indication for function parameters, because dealing with libraries that take complex objects as function parameters can be an absolute nightmare.

Meh, never encountered this in 20 years. Typing is helpful in large projects however, and not just with complex objects.


> and safe.

Oh no! You were so close!


The article read more like a rant of "I PERSONALLY HATE THIS" than a well-reasoned argument of why the language might actually suck. Here's an example :

> PyPy, PyPi, NumPy, SciPy, SymPy, PyGtk, Pyglet, PyGame... (Yes, those first two are pronounced the same way, but they do very different things.) I understand that the 'py' is for Python. But couldn't they be consistent about whether it comes first or second?

First, stupid examples. PyPy and PyPi (actually PyPI, with the uppercase) are not pronounced the same, its "pie-pie" and pie-P-I.

Second, on the package names, they're third-party. They could be named pretty much anything. Why does their naming matter in any way? I haven't seen much consistency in any language I've encountered anyway. I'd even say most of the examples he gave are at least pretty self-descriptive, compared to a lot of libs in multiple languages...


> The article read more like a rant of "I PERSONALLY HATE THIS"

Given the entire premise of the post is that he's written a list of things he hates about Python as an answer to his friend's question as to why he hates Python, I'm not sure why you think there's some ambiguity here.


I think the issue is that the article is called "Reasons Python Sucks" rather than "Things I Hate About Python". It comes off as the author saying that these things are objectively bad about python even though many of them are clearly subjective.


Exactly this. The overwhelming majority of his arguments were purely subjective, like his points on code clarity or mandatory indentation.


clearly subjective

Are they objectively subjective?


Which makes it perplexing that it's on the HN front page. A list of very personal and petty gripes aren't all that informative. Especially when half of them seem to show a profound lack of familiarity with Python.


Perhaps it was upvoted because there is a prevailing sense that something in Python isn't right, and even though most people seems to disagree about what exactly isn't "right".

I "hate" python for my own (much more petty) reasons, although the horrible naming conventions struck a cord with me, so I upvoted this article, with the hope that I'd be able to learn something from the discourse it creates.


> 1 (versions) and 2 (installation) have to do with the ecosystem, not the language. Unsigned packages are a real problem, but hating on community-maintained software is just weird. Most software in your local Linux distro's repository is maintained by a "community" that might just be one person working in their spare time.

Regarding 1: I'm sick of people saying that the ecosystem != the language. No one is going to be able to prop up an alternative ecosystem and get more traction than the language's own ecosystem, so you are basically always stuck with whatever crazy ecosystem comes with the language. Most of what people care about is the ecosystem and the syntactic sugar relationship the ecosystem has with the language, so it is a very real criticism imo if a major programming language has a bad ecosystem.

You are stuck with the ecosystem you ship with the language, so get it right the first time.


There actually are separate Python ecosystems (applications embedding Python or relatively detached distributions like Anaconda). Similarly you don't really care about e.g. the larger Lua ecosystem when you are targeting a specific application using Lua. It has its own ecosystem.


That's my point though. Unless the primary language developers themselves say "Hey, this ecosystem is broken" and then make an alternate one, we are generally stuck with the old broken one being the de facto standard. And even in that scenario you have to make the choice between breaking compatibility with everything that relies on the old ecosystem, or supporting both new and old at the same time. The lesson to be learned here is you only really get one shot at doing the ecosystem the right way, so do it right the first time. Hence Crystal/Go/Rust/Nim/etc


Generally, yes, but in Python, no; there are at least 2 good ecosystems.


I think the frustration with that argument is, who is it directed at? How would you fix it?

venv has been included with Python since 3.3 (2012) and was a separate package before that. If you're having troubles with the ecosystem playing well with Python 2.6 (2008) what do you expect anyone to do? I really think OS package managers have been really negligent about multiple versions and project-level dependencies--maybe it's outside of their scope (that also wouldn't help you on Windows)? People have been griping about software specific package managers for over a decade and OS package managers have only added the most minimal support, but never addressed the reason for them.

I've argued for years, if something is critical to your business then decouple it from the OS. Of course a new version might screw up your OS and you may need it to address a specific business concern...that's just one of the problems of any interpreted language. I wish things were better out of the box, but it's not unreasonable to address yourself.


> No one is going to be able to prop up an alternative ecosystem and get more traction than the language's own ecosystem

node vs web browser? linux vs unix? ubuntu vs debian? autotools vs base make?

i'm sure these aren't the best examples.

irrespective, you can (like|dislake) the language and (like|dislike) the ecosystem separately.

"oh I hate programming in XYZ it's far too verbose but it really does have a nice package management system"

yes, they are connected, but I don't criticize a hamburger by complaining about the taste of my soda..


#7 is so very wrong in so many ways.

Even languages that are pure "pass by value" cheat pass the value of a reference for objects.

Very few languages support doing a deep copy when passing an object. I'm trying to think of a single one, and I know that none of the major ones do. C will pass an entire struct, and so long as the struct doesn't have any pointers, sure...

But even in C passing a struct by value is highly discouraged, since it can spew all over registers and the stack! I've worked with coding conventions where pointers to structs are passed, and then the called function memcpy's the struct if it needs to hang on to it, or just leaves well enough alone if it only needs to pull values.


That one dumbfounded me as well.

Has the author never given a pointer as an argument to a function before? Do they make copies of their strings and structs every time they want to pass it around? I just... I just don't understand how they could think that.


I think I can answer this one because it is one of my gripes of Python. The problem isn't that it doesn't copy, rather it is that the = assignment operator specifically doesn't copy.

If you create a list "list1", set "list2 = list1", and change list1, both of the lists change. When I learned Python, this was a major source of confusion for me. Eventually I learned that in Python, instead of nothing being a pointer as it first appears, it is actually that everything is a pointer. On the other hand, while the same things occur in C++, the assignment operator does a shallow copy and anytime you are doing a pointer copy it is explicit.


I don't remember when in my Python career I learned about names vs. values, but it finally clicked for me that I'm predominately just binding a value to a name with the = assignment. Now everything I read has become very easy to reason about and understand.

Ned Batchelder is a good resource for learning many things about python, and this talk he gave at Pycon some years ago is no exception:

https://nedbatchelder.com/text/names1.html


Wow, there's a name I haven't thought about in a long time. I met him several times in the context of Lotus/Iris. Super nice and obviously wicked smart guy. Always approachable and eager to help, too. I'm not surprised at his current vocation.

What a small world...


> Has the author never given a pointer as an argument to a function before?

Possibly not, many devs now-days have never used a real native language.


Weird though when the author mentions the desire to rewrite Python functions in C.

The whole section about references was very confused. Especially for someone who supposedly knows C.


I suspect the issue is precisely that he's coming from C. In C, pass by value vs pass by reference is extremely explicit. In python, it's "magical", because it depends entirely on the type of the variable that is passed in, which is itself hidden from view of the code due to the dynamically typed nature of the language.

This has been one of my own annoyances with Python as well.


It doesn't depend on the type at all. Objects are never copied when passed (or assigned to names). The only thing that might give you the opposite impression is that some objects are immutable, so you wouldn't be able to notice if a copy were made (without calling `id`).


7 Isn't Python actually pass-by-value / pass-pointer-to-object-by-value like Java is? Many confuse pass-pointer-to-object-by-value with pass-by-reference, but they are different things. Pass-by-reference allows to write a procedure that swaps two external values.


People confusing this terminology is one of my pet peeves. And it's not a petty one. It often impedes the conversation they're trying to have.


You're right that you can't perform swaps with a Python function, but it's never pass-by-value like Java primitive types. It feels like pass-by-value because int, long, float, complex, and string are basically immutable.


I may be missing something, but I can't find any example in my mind which indicates difference between passing in Java and Python.

And it's pass-by-value for all types, because for objects an address is the value.


Java primitives (short, float, double, etc...) are copied to the calling function, but arrays and objects are passed by reference. This is easier to explain in a language like C++, which can do either:

    // Java always does these:
    void f(Object& o) { ... } // passes objects by reference
    void g(double x) { ... }  // primitives are copied

    // Java can not do these:
    void p(Object o) { ... }  // copy of the object
    void q(double& x) { ... } // reference to primitive
CPython always passes by reference (pointer), and a C extension can break the rules and modify the otherwise immutable types (int, float, complex, string, etc...).

You can squint and say everything is pass-by-value because you can always pass pointers, but then we're really losing any meaningful definitions for what semantics we're describing. I mean, it's all pass-be-electron if you dive deeply enough.


> Java primitives (short, float, double, etc...) are copied to the calling function, but arrays and objects are passed by reference.

Not really. "Pass by reference" is a specific term for a much different technique which is very rarely encountered these days. It means that if you pass a variable "foo" to a function, that function can assign "foo" to some other value and the value of "foo" will also be changed outside of that function.

The confusion, I think, is that true pass by reference is almost never encountered in modern programming languages and courses, and some people mistakenly use the term "pass by reference" when explaining the difference between e.g. primitive types and objects in Java.

Java passes everything by value. The value of a variable assigned to an object is indeed a "reference" to that object in memory, but that's a coincidental use of the term "reference."


> "Pass by reference" is a specific term for a much different technique which is very rarely encountered these days.

C++ isn't all that rare.

I suspect we'll have to agree to disagree whether your definition of pass by reference is universal. Given your definition, can you name any language with calling semantics which are not pass-by-value? I mean, you're always passing the value of something (be it the actual value, the address of the memory where the data is stored, or the pointer to the string containing the name of the data in an associative array).


Popular as it is, C++ is still a pretty special case. Not many languages have references like it. (I personally don’t use any.)

  int a = 5;
  int& b = a;
  b = 6;
  // a is now 6
Allowing an lvalue argument to be changed by a function call is something more languages support, though, like C# and VB.NET. That’s what it means to “pass by reference”. Passing a reference/pointer is an accurate description of how most languages work, which might sound similar, but is really different enough to warrant not being called that. Especially because the behaviour has nothing to do with passing: variables just work that way in general in Python, Java, JavaScript, Ruby, C#, VB.NET, Lua, ….

Anyway, back to:

> I may be missing something, but I can't find any example in my mind which indicates difference between passing in Java and Python.

You’re right that C extensions can break the rules, but in the real world they don’t (because that breaks everything); immutable int objects are effectively primitives. (Modern JavaScript – in strict mode – is an example of a language that hides the implementation details of primitives better than Java.)


I don’t know any C++, so forgive me. I can’t name any language that doesn’t use pass by value. Okay, I’ve heard that FORTRAN does, but I’m not sure. But that’s the point. The distinction was made when both techniques were popular, since it was obviously an important distinction to understand.


Early FORTRAN programs could break if you passed a constant into a function and reassigned the value within that function, because the constant would change.


My C++ is a bit rusty, but AFAIR `void f(Object& o) { ... }` would allow you to replace the whole object in passed variable (`o = o2`). You can't do this in Java, you can only modify its fields.

It's more like `void f(Object* o) { ... }`


You're right, it is more like a pointer. In C++, that would call the assignment operator which can do anything it wants, but the address of the Object would not be replaced. Java would change the value of the pointer in the callee.

These cross-language comparisons have too many nuances for me to try and make simple examples. Really, my original point is that CPython does not have "primitive types" as in Java. Everything is passed by pointer to the PyObject.


> , but it's never pass-by-value like Java primitive types

That's because this class of types doesn't exist in Python.


I'm not sure what your point is...


Primitive types in Java are special. Like you have boolean and Boolean. You can't invoke methods on primitive types.

I think there were plans to change it in future versions of Java.


ditto


Thank you for going point by point. I was blown away by how little experience the author had with Python, compared to how strong his opinion is about it.

I hate hate hate language debates, but this isn't even that, it's just a misunderstanding. If this guy took a class on Python he'd figure out like 5 or 6 of these issues no problem!


True of most "X Sucks" discussions. I used to do a lot of ColdFusion. A mention of that would elicit the oh-so-clever, "I'm sorry...." A little digging found they either had never written a line of CFML in their life, or they worked on a project back in 2005 before they added numerable features, and before many of the frameworks and tooling that a typical developer would use.


same for PHP. Still not great but there is in 2018 essentially a "good parts" set of best practices, which combined w/ v7 is not a horrible language/platform. I feel you if you just don't like it, but I see so many people just quoting that old "fractal of bad design" post that's really not relevant any more.


And for JS. If you disable in linter some awkward features left for backward compatibility, modern JavaScript is pretty cool language.


Totally agree. The author needs to read the Python docs.


With regard to "pass by reference", it's not even clear to me that that's a correct way to describe how Python passes variables to functions. Python variables aren't names for storage locations to begin with; they're namespace bindings. Passing a variable to a function means passing a particular namespace binding from the caller's namespace to the function's local namespace. I don't think the author of this article understands any of that.


> Passing a variable to a function means passing a particular namespace binding from the caller's namespace to the function's local namespace.

I don't think that's true, as the function will never be able to change the binding, and only gets a reference to the value of the binding. If the function was passed a particular namespace binding, it would be able to change this binding's value, and that's not possible.

I agree that "pass by reference" is a misleading way to describe Python functions.


> the function will never be able to change the binding, and only gets a reference to the value of the binding

More precisely, the function's arguments when it is called are a set of values for variables taken from the caller's namespace, which then get bound to the corresponding names in the local namespace. You're right that "passing the binding" doesn't really describe that process very well.


> the function's arguments when it is called are a set of values for variables taken from the caller's namespace

Again, this is very obviously false, since a call need not involve any variables at all:

    foo(1, 2, "three")
The function's arguments when it is called are a set of values taken from the caller, yes. And those values are obtained from evaluating expressions that may involve variables. But they might not. Saying that the values are "a set of values for variables taken from the caller's namespace" is wrong.

You are mistaken, and your talk of namespace bindings is just obfuscation. Python arguments are passed by pointer. Python namespaces bind names to pointers. There is no "binding" object passed in a function call.


> this is very obviously false, since a call need not involve any variables at all

True, in which case the values would just be constants. But they will still get bound to names in the function's local namespace.

> You are mistaken

No, I left out a case which, it seemed to me, did not affect the main point I was making. If you want to make clear that that's a possible case, fine, you've done so. But you haven't refuted (or even engaged with) my main point at all.

> your talk of namespace bindings is just obfuscation

No, it's a very important difference between what Python variables mean and what variables in language like C mean. You might think the difference is unimportant, but not everyone agrees with you.

> Python arguments are passed by pointer. Python namespaces bind names to pointers.

At the C level inside the interpreter, yes, this is true: every "object", such as the 1, 2, and "three" in your example, is a pointer to a C struct containing the object's data (sometimes including further pointers). Again, that doesn't affect my main point at all.

> There is no "binding" object passed in a function call.

I already agreed to this in my response to pstch upthread (the post of mine you originally replied to). Once more, it doesn't affect my main point at all.


> But you haven't refuted (or even engaged with) my main point at all.

OK. Your main point (now that you have shifted the goalposts) seems to be that argument passing induces a binding of the parameter name to the argument value, yes?

In the abstract, this point is meaningless since it applies equally to every other programming language. In this C function:

    void foo(int x, float y, char *z) { ... }
the C compiler also has to manage bindings from variable names to the locations where their values are stored. It needs that information to find the correct values in registers or (just like in Python) on the stack.

In the concrete, the point is also meaningless since those bindings do not involve dictionaries as you seem to think. For positional functional arguments, loooong before the call takes place, the bytecode compiler resolves names to stack indices, and the variables are accessed through those. Just like C can access arguments passed on the stack using constant offsets from the stack pointer.

Here is the code for the normal call fast path: https://github.com/python/cpython/blob/62be74290aca26d16f3f5...

    f = _PyFrame_New_NoTrack(tstate, co, globals, NULL);
    if (f == NULL) {
        return NULL;
    }

    fastlocals = f->f_localsplus;

    for (i = 0; i < nargs; i++) {
        Py_INCREF(*args);
        fastlocals[i] = *args++;
    }
    result = PyEval_EvalFrameEx(f,0);
This sets up a stack frame for the callee (on the heap, yes), then copies the arguments (which are pointers to PyObject) into slots in that stack frame numbered consecutively from 0.

Access to these arguments inside the callee is via the LOAD_FAST bytecode instruction: https://github.com/python/cpython/blob/master/Python/ceval.c...

        case TARGET(LOAD_FAST): {
            PyObject *value = GETLOCAL(oparg);
            ...
which uses the GETLOCAL macro: https://github.com/python/cpython/blob/master/Python/ceval.c...

    #define GETLOCAL(i) (fastlocals[i])
Nowhere are name-value bindings allocated dynamically in this normal case. Python does perform dictionary manipulation for keyword args, but not for positional ones. For normal positional arguments, the mechanism is exactly equivalent to a C (or whatever) compiler pushing arguments onto the stack. Local variables are exactly what you claimed that they were not, namely names for storage locations (stack slots).

I'm done with this thread now.


> Your main point (now that you have shifted the goalposts) seems to be that argument passing induces a binding of the parameter name to the argument value, yes?

It's that there is an extra step involved that does not occur in languages like C.

> those bindings do not involve dictionaries as you seem to think

For positional arguments, you are correct. But, as you note, there is still an extra memory allocation on the heap, because the stack at the Python level uses heap memory, not stack memory, at the C level.

For keyword arguments, as you agree, there is a dictionary entry created.

> Local variables are exactly what you claimed they were not

More precisely, local variables inside a function that correspond to positional arguments are, for performance reasons, stored in an array of function local pointers at the C level, instead of being stored as dictionary entries (namespace bindings).

But the fact that this is a particular exception to Python's normal treatment of variables simply highlights the point I was making.


> Python variables aren't names for storage locations to begin with; they're namespace bindings.

Can you explain the difference? What is a "namespace binding"? When accessing a variable's value, the interpreter's code sure looks like it treats the variable name as the name of a storage location.

> Passing a variable to a function means passing a particular namespace binding

"Passing a variable to a function" is not a thing. Passing a value to a function means passing a pointer to that value. This is the same for "foo(some_var)" and "foo(1)". Since "1" is presumably not a "namespace binding", you seem to suggest that there are different parameter passing mechanisms for these cases. There aren't.


Every value in Python is a reference to an object. All function parameters are such values that are passed as they are. You get the same references to objects that the caller passed.

We could call this "passing references to objects by value."


That's a long form of what I most often hear: "pass by value of reference"

But in any case, that's the point that made me go wtf while reading through as well. Whatever you want to call it, it's the same as what C (when not passing pointers), Java, PHP, Javascript, and many many other languages use.

Which is why it's so frustrating we don't have a good, easily-understood/well-known name for it.


Over the years, I have found this article to be useful: http://effbot.org/zone/call-by-object.htm


> What is a "namespace binding"?

An entry in a Python dictionary that is the namespace for the current scope.

For example, to repeat a response I gave to someone else upthread, compare a variable assignment in C and Python.

In C:

  int i = 17;
Means "set aside one int's worth of storage and fill it with the bit pattern for the number 17".

In Python:

  i = 17
means "create an int object with the value 17 and bind it in the current namespace dictionary to the name i".


In other words, the latter means "set aside one pointer's worth of storage and fill it with the bit pattern for the pointer pointing to the value 17 (which you may have to create first)". Variables have values. In C those values may be ints, floats, pointers, whatnot. In Python (at the implementation level) those values are pointers to objects.


> In other words, the latter means "set aside one pointer's worth of storage

Set aside one pointer's worth of storage on the heap, yes; not on the stack or in the program's data segment, which is what the C statement I gave does.

> and fill it with the bit pattern for the pointer pointing to the value 17 (which you may have to create first)"

Which has no analogue whatever in the C version.

Also, you completely left out the part about creating an entry in the namespace dictionary, which also has no analogue whatever in the C version, and which involves additional memory allocations.


> not on the stack

Except for locals. (Well, OK, the Python stack is reified in the heap, but local variables are translated into small integer indices into the stack frame.)

> an entry in the namespace dictionary

Except for locals, which do not involve memory allocations or dictionary lookups.

Anyway, we're getting farther and farther away from the topic, which was that Python argument passing is no different from any other language in which everything is an object.


> Except for locals.

I meant the C stack, not the Python stack. At the C level all Python objects are allocated on the heap (except for some of the singletons like None which are statically allocated by the interpreter). (The fact that I used the term "heap" indicates that I was talking about the C level, since there is no "heap" at the Python level.)

> Python argument passing is no different from any other language in which everything is an object.

I disagree, since again you have left out the namespace binding step completely.


Here are pictures that explain the difference between variables as named boxes and names in Python https://david.goodger.org/projects/pycon/2007/idiomatic/hand...


That's a nice explanation for BASIC programmers, but for anyone who has ever programmed in some other programming language than BASIC, it should not come as a surprise that named boxes can store pointer values. As they do in Python. They literally store C pointers of type (PyObject *).


Python is not C. There are no pointers in Python.

I have no idea why would you mention BASIC here.


> Most software in your local Linux distro's repository is maintained by a "community" that might just be one person working in their spare time.

There is one big difference between Pypi and a distribution repository:

With Pypi, every contributors are truly individuals, they upload their packages alone, they maintain them alone, they basically do whatever they want. The maintainer here can likely be a single point of failure.

With distribution even if most packages are maintained by a single person, the repository content is the responsibility of the distribution as a whole. These distributions have formalized their processes and policies a long time ago. For example, if a critical security issue is found, unless something goes wrong, the package will not be left unpatched, even if the package maintainer is not reacting.

Also, Distributions put huge efforts into providing stable versions, if you are using packages from a distribution, you have some guaranties about stable APIs, and that these packages with stable APIs will actually be maintained for a few years. Even if it's not always perfect, with non-critical bugs not always fixed, it's a far cry from Pypi where the only true possibility is to hard pin every single version of your dependency tree inside requirements.txt.


> And I pity anyone who miscounts spaces and accidentally puts in three spaces instead of four somewhere -- this can take hours to debug and track down.

this is so hilariously wrong that it is clear that the author has never actually tried any of the things he complains about.

  Python 3.7.1 (default, Oct 22 2018, 10:41:28)
  [GCC 8.2.1 20180831] on linux
  Type "help", "copyright", "credits" or "license" for more information.
  >>> if True:
  ...     if True:
  ...         pass
  ...    pass
    File "<stdin>", line 4
      pass
         ^
  IndentationError: unindent does not match any outer indentation level
same error in Python 2.7.15. the caret is at the wrong place, but the error message is perfectly fine on its own.

edit: I checked the CPython source, and this message actually dates back to 2000. so unless the author was using python 1.x back in the 90s, or is dumb enough to use single spaces for indentation, the story is a complete fabrication.


I think the problem he is describing--somewhat poorly--is when the "third space" just happens to match some other scope above, and therefore is syntactically valid, but not nested the way one initially thought.

Those kinds of problems can be hard to track down.


Never searched for 'hours' but it's been a annoyance before, usually related to copy/pasting. I certainly have run into the indent issue where my editor doesn't know what block it should be in but that's more emacs fault.

That said, I sure with python offered a totally optional end block thing. In many situations it would make things far more clear.


Exactly this. I've spent many hours on multiple hard-to-replicate bugs that turned out to be from a couple of lines at the end of a loop body that were indented to the wrong level after a refactor.


A third space can never align with another line since it will be an odd number of characters while indents are always even (or vice versa). If you are two chars off, well at some point you need to be responsible for your broken blocks.

I agree with the other person that says this doesn't really happen in the real world with a baseline developer and editor.


We now have three real-world counter examples just in this thread. And the hacker news crowd is relatively sophisticated technically--even for programmers in general.

"It isn't a problem for me, therefore it isn't a problem for anyone." just isn't a good way to reason about problems like this.


The important issue is which solution is the best given the trade offs. Acceptance of nothing less than a flawless one is not a path forward.


Sold! As long as you admit that there is a problem and tradeoffs, then I'm fine with where this conversation is ending.


Sure, I'd never say a solution to a non-trivial issue was flawless. However, "a problem" is not very granular. I'd rather count this one in terms of centiproblems or milliproblems.


These kind of problems can be solved or avoided altogether by a decent editor.


I don't see how. An editor can help you get indentations that are syntactically valid, but can't possibly know if the programmer intends the program below to print plain "foo" or both "foo" and "bar".

  print("foo")
  # x is false for the problem
  if x:
    do_something()

    print("bar")

And to the parent who has written a lot of code but never had this problem, well, some people do.


That's a two space error and easy to see.

> never had this problem, well, some people do.

Neophytes also have a lot of problems with matching braces, neither is a panacea to those folks. But one is easier to read and type for everyone else, for years to come.


It's a two space error in a trivial example that no editor can help with.

If the function is longer or their are a couple of additional levels of indentation, and it isn't a print statement, but some additional logic, it is a hard problem to spot.


Not true, look up indentation guides and whitespace visibility. Even my barebones editor has them and squashes ambiguity.

Still at some point one needs to be responsible for the code they write, braces or not. Braces are not a guaranteed solution either, if the author gives up complete responsibility.

Basically, this is a theoretical non-problem. The lack of braces pays back in readability every single day. Like +100 + -2, then complaining about the -2.


I write python all the time, with editors that support this. People do encounter this problem.

You'll also note that I've never claimed braces were better, I'm just trying to explain OP's pont.

You may not encounter it, but "Hey it works for me" is not a legitimate answer.


> Those kinds of problems can be hard to track down.

In many years of writing Python I have not once encountered this issue. And I didn't always use four spaces...


Not to mention, who hits the spacebar four times for their indentation? Actually. Do people do that?


I frequently have to when copy/pasting code in VS Code and it assumes the wrong indentation level.


You could select the whole pasted part and press tab or shift+tab to change its indentation level.


To back this up, I was at a place where vim on my Mac and the Windows editor on the machine of one of the people consuming my code would somehow munge the spacing (tabs-to-spaces, or summat).

Point is, I'd see this problem constantly until I figured it out. And not once did it take more than a few minutes to fix it because, as you point out, it shows you where the problem is. And if you use PyCharm, or VS Code with Python extension, or a raft of any other editors, it'll bark at you long before you try and run the code, with circles and arrows, and a paragraph on the back of each one explaining the problem.

So, yeah, the author is making shit up. And needs to learn about what the Tab key does.


Doesn't even need to be a big heavy editor. I use the lightweight Geany and even it has indentation guides and whitespace visibility. Even so, I could probably fix such a problem without it.


Do you mean using spaces or using the space bar? 4 spaces is standard but the editor should take care of that with tab that results in 4 spaces. An actual tab is just plain wrong. It is allowed of course, but those developers are wrong for doing so.


>3 (syntax) seems to be about not supporting the author's own highly idiosyncratic habits

Yeah, I don't get the author at all. Using indentation is so, so, so, so, much cleaner and easier to understand, even with lots of nesting than trying to figure out if you closed all the stupid curly braces, curly braces be damned.


I asked a "C all the things!" developer a while back why he hated Python's enforced indentation and in a whole lot of words he basically said that it makes it difficult to visually track scope when you have long chains of conditionals.

The standard Python developer response to that is, "Aha! You like braces because they enable your bad programming practices!"

However, I found the best way to illustrate that point is this: I asked him, "If you're never allowed to use a text editor/IDE that highlights braces or the space between them ever again would you still prefer braces to indentation?"

I had to re-explain this concept several times but eventually I think he understood my point at least a little bit...

"Aha! You're using spaces because Python lacks decent development tools! In fact, because there's no static typing you can't even make a decent IDE for Python! Spaces are a crutch!"

Sigh.


> * I asked him, "If you're never allowed to use a text editor/IDE that highlights braces or the space between them ever again would you still prefer braces to indentation?"*

Being visually impaired and also having coded since before syntax-highlighting editors became standard, yes. A brace character is something that's easy to visually perceive; whitespace isn't.


Haven't indent guides been around since forever? And generally if you can't tell what indent it's wrong, it probably needs refactored anyways.

It's definitely an opinion your allowed to have, though not using the language over it would be rather draconian.


Literally anything other than spaces that allowed easier linting, less "wait am I doing it right" regarding multi-line statements, etc.

If that is a keyword, or brackets, or whatever, I would prefer that. You can't minify or easily lint python. And you can't easily tell if there are mixed indent methods (tabs/spaces) and IDEs struggle with it vs. a simple bracket structure.

(This is a rare repost within a thread. I'm punching that card for 2018. Whitespace significance is my #1 issue with Python. I love the language, hate this feature.)


What linting is difficult in Python?

I find that the linting available in Python is better than that in c++, though c++ has better auto complete.

Minification of most of Python is possible, although shouldn't be considered of any value since it's not passed over the wire to a user.


I hate meaningfull identation because the tools I end up using suck at maintaining it. I end up bugfixing on customer systems, sometimes on systems used by coworkers. There is no editor with consitent tab vs. spaces or tab width settings. I had editors clear two indents at once, fail to correctly line up new indents more often than not. I will accept whitespace as sane block scoping method the moment every text editor follows the same settings out of the box with the ability to customize the settings removed.


>There is no editor with consitent tab vs. spaces or tab width settings.

This is just plain false. PyCharm does, and I am sure there are others.


While I have PyCharm on my dev. system the chance that I can use it while I am debugging or extending a script on a customers system is zero, sometimes there is kwrite, sometimes it is gedit and if it is headless I may get vim. So consistency is guaranteed to be non existent.


>"If you're never allowed to use a text editor/IDE that highlights braces or the space between them ever again would you still prefer braces to indentation?"

Yes. (Though that's hardly the worst thing about Python.)


Isn't deep nesting (hence deep indentation) a sign of code smell? Yeah, you might end up shuffling the logic into a new file or a function that makes the file much longer, but I've noticed when doing that kind of refactoring it forces you to clean up the scoping quite a bit so there's less state to keep track of.


It depends on your definition of "deep".

Imagine an if, inside of a foreach, inside of a function definition, inside of a class. This is not terrible code, it's perfectly reasonable.

Now you're indented four levels in. Now combine this with some rather unreasonable and outdated assumptions that PEP8 (automatically enforced in many shops and OSS projects) has, like pretending that people are still on glass terminals and that anything longer than 79 characters is a problem. It also insists on four-space tabs, so this very simple construct has eaten 20% of your line budget.

..not to mention how it makes your code harder to read. I also share the author's concern about how you're less able to separate your debug code from your actual code.


This is all subjective, so it's hard to make a water-tight argument either way (even if we were looking at a concrete code example)...that's one reason it's referred to as "smell" instead of a bad-practice. PEP-8 effectively starts with "A Foolish Consistency is the Hobgoblin of Little Minds." Every place I've worked used a modified version of PEP-8 for standards because it never worked out of the box. Like I was saying higher up, it's hard to tell who to blame. It is the reality of using that language, but the developers have made a strong effort to address it. I guess it's a lesson to future language designers or community managers?

More concretely, I often find deeply indented code more difficult to read. There are more local variables to keep track of and breaking it out into a function helps encapsulate and name what's going on. Even from your description I might look to see if I could use a generator to filter the loop instead of "for" and "if" which should be more clear, fewer indentations, and easier to optimize at runtime. I do find trying to break up long line onto multiple rather annoying because diffs are more difficult to read.

I've never really used whitespace to separate my debug and actual code...which probably speaks more to my background than anything else. I have tended to put a # at the beginning of the line when debugging and next to the comment when commenting. Linters don't seem to care for that, but the code is tidied up before it's committed.


Four-space indent is very common in other languages too. I don't see why this choice should be argued over for Python specifically. The 80 char limit is also very common.

I too used to leave debug-code purposefully unindented. There are other ways to handle that and the loss is negligible to me considering there can't be misplaced braces in Python in return.


Yes, we indent code for readability sake. If you don't, that would be incompetent. Which makes the braces redundant at best, noise at worst.

Their lack in Python bothered me too, one afternoon in the spring of 2001, then I moved on.

When I hear someone complaining about it I immediately think, this person hasn't much experience with Python, or is one of those highly inflexible pedant types.


>Which makes the braces redundant at best, noise at worst.

YES!! Every time I try something other than Python that requires braces, I am like "WTF, why do I have to type this extra shit! Such an annoyance."


Arguably the lisp family contains the “huggiest” use of characters, but something like Paredit turns those parentheses into something of a turbo-charged way of handling code forms.

But it leads to another problem: hatred of typing commas in non-lisps.


When I switched to Python, the one thing I predicted I'd hate is the forced indentation.

The reality is that after a week or so I forgot completely about it. There are other things for sure that became an issue (took awhile to fully grok class variables vs instance variables), but the indentation was never one of them.


It’s even false that python passes parameters by reference. It passes the references by value, that is a completely different thing. It’s really easy to prove it by implementing a function swap(a, b) and looking at the values of a and b after calling the swap function. Obviously a and b are not swapped because they are not passed by reference, but instead the references are passed by value. It’s an article written by someone that doesn’t have the slightest idea of python if even I, that I used it only a bunch of times, don’t make this beginner errors... Btw I don’t like python, I prefer to it a lot of other programming languages.


> 1 (versions) and 2 (installation) have to do with the ecosystem, not the language.

How exactly do I use Python while entirely avoiding the Python ecosystem?

I get what you're saying but you're also massively nitpicking. His point is correct.


>>3 (syntax) seems to be about not supporting the author's own highly idiosyncratic habits, which include deep nesting and putting debug code in the first column (ugh). The result actually seems better for maintainability than the author's own unconstrained code would be.

Telling users they are not good enough for your technology is not the way you go about these things.

Also if this works in other languages, people do expect it should work in Python.


Glad you took the time to write that. I stopped reading the article after his beef with imports...


I'm pretty sure Python's list type is implemented as a dynamic array, otherwise random access wouldn't work very well.

Maybe try to not identify so hard with language choice? That way you gain the capability of processing critique without loosing your marbles.


PyList is a dynamic array (usually called vector) of PyObjects, i.e. it contains objects and never values.


Why should it matter how lists are implmented?


Because someone claimed that they're not arrays, which they are. So the posted article is correct in that sense; and not wrong about everything, which was also claimed.


3: Yes. Mandatory indenting is a godsend to readability. Author is wrong.

4: Yes. Imports >> Includes in every conceivable way. However, what is actually available to import _is_ confusing. This is really a symptom of a different problem though...

5: Yes

6: Yes - picking on the quotes thing with Bash is just one of a million syntax "quirks" that make Bash a horrible no good language. ( as an operator is my vote for most egregiously mind-bending "feature".

7: Yes. Once you get used to it, Python usually does what you want in terms of passing parameters.

1 and 2: NO. The ecosystem and the language are the same thing. They're useless without each other. Every other point the author makes is dumb, but this one is utterly correct. Now, the version schism is unfortunate, but it's spilled milk. I agree with the author that it's obnoxious, but there's not much to do about it now. However - creating, sharing and installing packages in python is THE problem. And I'm not saying it's an unsolvable problem - that's what makes it so frustrating! You have a thriving, healthy community of package maintainers creating amazing libraries that allow regular engineers like me to get work done out of the box. And in many ways, as other posters have pointed out, python paved the way for making this experience better than the hell that is c++ package management. Sure.

But the current user experience of trying to give somebody an application that happens to be written in python? The story of how you take some python code you wrote, bottle it up, and make it work on somebody else's system? And the story for how they take that code and make it available to themselves on their own system? It. is. atrocious.

Which one of these tools, concepts, commands and filetypes do I need to care about: package, module, egg, wheel, bdist, sdist, distutils, setuptools, easy_install, pip, pipenv, poetry, PEX, PyInstaller, py2exe, PyPI, conda, miniconda, anaconda, virtualenv, venv, requirements.txt, setup.py, pyproject.toml, pipfile, site-packages, dist-packages, $PYTHONPATH or motherfucking .pth files??!?!

Ever been curious how sys.path gets populated at startup? Gaze into the darkness: https://github.com/python/cpython/blob/master/PC/getpathp.c

And to anyone saying "Just use a virtualenv": that is so, so not an answer to this eldritch horror. Let me ask you this - when you installed chrome, did you have to create a new virtualenv? No. If you want to effectively distribute code to lots of people, you need more than a god damn virtualenv. The whole world isn't just scientists tooling around in sandboxes.


Application distribution is actually the easy use case; just use PyInstaller: https://www.pyinstaller.org

It's often tricky to specify abstract dependencies such that people can use pip to install libraries without having trouble with other packages in their environments, and also quite tricky to provide builds with native code or links to native libraries for different architectures.


Yea I was pretty surprised; Python really does suck, but I don't think I found a single one of the reasons listed in the article compelling.


On pass by reference.. I really miss Fortran's intent(in). You get a reference to the data but it is immutable... I would like if Python had something similar...


> 7 (pass by reference) is another totally-wrong one.

Yes, specially cos it is not "pass by reference", but "call by sharing"

http://www.effbot.org/zone/call-by-object.htm

https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sh...


Thank you for taking the time to explain this in such depth.

This was one of the most frustrating articles I have ever read.


Isn't pep440 actually dealing with versioning? In my mind, if there is a pep, then is the language.


1 and 2 are big issues for portability for me. It’s easier to ask for a C++17 compiler than insist on Python 3.6 with MKL. It’s not central to the language, but how people use it affects distribution.


I saw "Reasons Python Sucks" on HN and I thought I was going to finally see an updated list of well-informed and articulated concerns about Python.

Instead, this list is bizarre, misinformed, and largely incorrect. Other commenters have already pointed out several specifics. Two things I haven't seen yet that I'll add:

> And I pity anyone who miscounts spaces and accidentally puts in three spaces instead of four somewhere -- this can take hours to debug and track down.

This is just absolutely batshit ridiculous. You'll get an IndentationError pointing to the exact line in question. In all my years of working with people at all levels, I've never seen anybody spend more than a few minutes working out an IndendationError.

> <Pass by reference/object> is one of the big differences between procedural, functional, and object-oriented programming languages.

This is a non-sequitur, and it's plain to see. It's possible (in fact common) to write with all three of these orientations in python, and object transit has absolutely nothing to do with it.


Came to a similar conclusion. I use Python a lot, and it certainly has its problems. However, the article's analysis doesn't even begin to scratch the surface of this topic in an informed manner.

Instead, we're presented with paragraphs written by someone who can't read a stack trace:

    In [7]: def foo():
       ...:   print('foo')
       ...:     print('bar')
    
      File "<ipython-input-7-47f5b52e9e07>", line 3
        print('bar')
        ^
    IndentationError: unexpected indent


Isn't that a strawman? Wouldn't a more apt example be:

    def foo():
        nums = [...]
        sum = 0
        for x in nums:
            print(x)
        sum += x
        
I don't know python well, but it seems like there could be subtle issues with indentation that wouldn't cause a compiler error, but would cause the wrong output.


1) This is not the case that the author presented, though - it was specifically about 3 spaces instead of 4. This tells me that the author simply doesn't hang out with python people.

2) On the substance of your concern: I think the evidence is clear (although I'm aware of no study) that having both syntactical control characters (typical curlies) and style-only indentation is more likely to lead to the outcome that you present, because the eye will always gravitate to read by indentation, whether it's syntactical or not. So have the indentation be the syntax makes this problem more easily avoided, not less.

IE:

    def foo() {
        nums = [...]
        sum = 0
        for x in nums {
            print(x)
            }
            sum += x
        }


The difference here is that the former (your example) is automatically correctable whereas the latter is not.

If I highlighted that entire function in my editor of choice and hit TAB I'd get exactly the "correct" indentation.

Almost any modern editor can automate this for you. It's one of the benefits of having a syntax for blocks. Yeah, it's extra typing for something you're going to do anyways, but now the computer can manage the style for you.

Also, and this is just my personal experience, I've learned to read by those control characters. I have a really hard time reading python because I'm subconsciously looking for the control characters!

I really think that part is really down to how you learned to program and what language you use on a daily basis.

As an aside, I think something like this is much more demonstrative of the issue you're suggesting:

    def foo() {
        nums = [...]
        sum = 0
        for x in nums {
            print(x)}
            sum += x
        }
It's still automatically fixable, but it's way less immediately apparent that something is wrong with the indentation.


Right, so you do your automatic fix and you end up with indentation that's the same as the python example. Isn't this a case for syntactical indentation?


Consider C, where {} are optional for single-statement bodies:

    void foo() {
        ...
        for( ... )
            printf(..)
            sum += x
    }
In python it's at least more visually obvious that the second statement isn't part of the for-body.


No, your example is not an issue of using 3 spaces for indentation versus 4 spaces like the author described.

That snippet is an example of not understanding indentation-based scoping at all.


I think it's very clear the author uses atypical tools or workflow. Nothing wrong with that, they just have to accept the fact that most hackers use tools that auto indent their code intelligently and find a way to get their method/scheme/whatever in such a way to deal with it like others do.


A list of well informed and articulated concerns about python probably wouldn't be titled "Reasons Python Sucks".


I 100% agree with you, but as a minor counterpoint, let me present you where an indentation might be a problem. I teach python (to my friends lol, not professionally) and a new learner apparently debugged this "for hours" (probably just 20 minutes).

   class A:
       def some_method(self):
            return # something

   def some_func(foo, bar):
       return # something
   
       def some_other_method(self):
           return # something
They intended to make `some_other_method` a method of class `A` instead they ended up writing `some_func` the wrong place and python parser was happy. For everyone except extreme python beginners debugging this will take 10 seconds. But just a datapoint.


Yes, now that's a legitimate demonstration of a possible weak point of syntactic indentation.

But it's easily fixed with code collapsing tools or a structure viewer (both of which are provided in all of the major python IDEs).


A number of these points seem like reasonable opinions to have. But two which had me questioning the breadth of the author's experience were "Most programming languages pass function parameters by value." and "In every other language, arrays are called 'arrays'. In Python, they are called 'lists'."

To the author:

1. Java, JavaScript and C# all have types which are passed by reference. (They also have types which are passed by value.)

2. Python lists are not arrays, if by arrays you mean a C- or FORTRAN-style block of non-resizable memory which is indexed by position. Python lists are much more like the Java List or C# IList interface.


Python lists are not arrays

In addition python also does have arrays if you want/need them: https://docs.python.org/3.7/library/array.html


Python (and Java) do not pass anything by reference. They pass by pointer-value. (If they passed by reference, you could change to which value a caller's variable was bound, like you can in C++).


Those languages use references rather than pointers (that is, their referring-things do not support arithmetic), so any name for what they do that involves "pointer" is a poor one.

The distinction between passing the value of a reference being used by the caller and passing a reference to the caller's stack is so rarely important or useful that there's no consensus terminology for it. The distinction that's actually relevant and useful is whether, when we write f(a), f receives the (thing we would call the) value of a or a reference to the value of a (and in particular whether f can change the value of a). In Python, f can change the value of a (that is to say, the thing Python programmers understand to be the value of a); therefore Python is pass by reference as the term is usually understood (and certainly any term for its call style that uses "value" is more misleading than calling it "pass by reference").


"The distinction between passing the value of a reference being used by the caller and passing a reference to the caller's stack is so rarely important or useful that there's no consensus terminology for it."

And the reason for that is essentially all modern languages make copies of something when passing argument parameters. The only question is what they are passing and exactly how hard the language layer works to make it look as if you are or are not passing something by reference.

I have to reach to something as obscure as Forth to find a language that does not work that way, and truly does not copy parameters into a function. (I haven't dug into Factor enough to know if under the hood it still copies parameters.) It's a very unusual choice to not copy something for a function call.

(This is one of the ways our CPUs have been optimized for the languages we run; they make this intuitively expensive operation otherwise cheaper than it would be on a naively designed CPU.)


> And the reason for that is essentially all modern languages make copies of something when passing argument parameters.

Then of course someone turns on whole program optimization for C++, half the code gets inlined into one terrifying large function, nothing is copied anywhere, and the code runs 2x faster.

Then some poor SOB has to debug the assembly code for all of this and has nightmares for years after.

Not like I'd know or anything. :)`


"Pass by value" and "pass by reference" are both wrong with respect to Python. They denote semantics that Python does not have, and coders believe and do wrong things if they believe Python follows one or the other. Hence a 3rd term is needed. I don't care what you call it, because it's just a name, but it is a distinct idea, and I'm not the only one who thinks this: https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sh...

> In Python, f can change the value of a

but it can't. `f` can't change the object which `a` references:

  def f(x): x = 5
is a function with no visible effect. When called as `f(a)`, `a` retains whatever value it had previously.

People who believe Python is "call-by-reference" don't understand this and write incorrect or overcomplicated code as a consequence. The distinction is important.


> They denote semantics that Python does not have

There is no consensus that "pass by reference" denotes that, whatever a wikipedia editor says. Normal working programmers do not understand "pass by reference" to mean specifically "pass a reference to the caller's stack" but only the more general concept of "pass a reference to the value of a". If you want to talk specifically about what kind of reference gets passed, you need some new terms for that, and both the new terms you come up with will be subtypes of what most of the world will continue to understand as a general category of "pass by reference". No-one outside of these arguments on HN gets confused about whether you can write a swap function in Python; people understand "pass by reference" to mean something more general and entirely true of Python (and Java and so on). You can tell by how often we see those languages described as "pass by reference".

> but it can't. `f` can't change the object which `a` references:

Yes it can. It can't make a to refer to a different object (as your f tries to), but it can change the object a refers to just fine.


> (If they passed by reference, you could change to which value a caller's variable was bound, like you can in C++)

I might be misunderstanding you, but I don't think you can do this in C++. References can't be changed to point to a different object after initialization. If you have code like:

  void MyFunc(Foo& ref_param) {
    Foo new_foo;
    ref_param = new_foo;
  }
The assignment above isn't "changing the value to which a caller's variable is bound". Instead, it's running the '=' operator on the Foo object to copy the state from new_foo to ref_param. To demonstrate this, you could run the following to see that the addresses are the same:

  Foo original_object;
  Foo& object_ref = original_object;
  MyFunc(object_ref);
  // object_ref still points to the same address
  assert(&original_object == &object_ref);
Under the hood, C++ reference params are basically syntactic sugar for passing by pointer-value, so this behavior isn't surprising.


The proper analogy for a Python object is `std::shared_ptr<Foo>`: in Python, all "objects" are actually reference-counted pointers to objects (thus making them shareable). And then yes, in C++, you can change to which object the caller's "object" (pointer-to-object) points to, if you pass a reference to that. This is not possible in Python: all you can do is pass that shared-pointer value around, not the value of the object itself, nor a reference to the shared pointer.

This is a well-known distinction that goes by several names, see e.g. https://en.m.wikipedia.org/wiki/Evaluation_strategy#Call_by_...


Ah, got it. Your basic point is that C++ params can use two layers of indirection (pointer-to-ref, ref-to-pointer, pointer-to-pointer, etc.), while doing so in Python is clunky. Makes sense.


> They pass by pointer-value.

No, Python doesn't even do that, because Python variables aren't names for storage locations to begin with. They're namespace bindings. Passing a variable to a Python function means transferring a namespace binding from the caller's namespace to the function's local namespace.


What is a namespace binding? Can you explain at a low level what that means?


Compare a variable assignment in C and Python.

In C:

  int i = 17;
Means "set aside one int's worth of storage and fill it with the bit pattern for the number 17".

In Python:

  i = 17
means "create an int object with the value 17 and bind it in the current namespace dictionary to the name i".


The semantics are identical.


If you think this, I'm confused about what you mean by "semantics".


What Java, JavaScript and C# call "arrays" are each very much like what Python calls "lists'. In particular the indexing after push() and pop() type operations and the bigO of indexing.

More abstractly, Historically, in computing "list" connotes pointers and "array" connotes sequential memory. The connotations imply engineering tradeoffs. [1] "List" may make more sense for a beginning programmer. It is an arbitrary context switch for programmers writing in multiple languages. Considering that one of the driving use cases of Python has been systems programming, "list" is misleading regarding performance characteristics. [2]

[1]: For example as in Scala https://docs.scala-lang.org/overviews/collections/performanc...

[2]: googling "python arrays" returns a lot of results explaining the difference between Python's Arrays and Python's Lists. "List" is "foo => spam" and "bar => eggs" Pythonism gone too far.


Not true for Java or C#. In both of those languages, arrays are fixed-size and don't have anything like a push or pop operation. Both have an automatically resizing container backed by an array that is referred to as a list. Python's use of "list" matches the use in Java and C# perfectly.


Requiring a type definition, Python's Array type more closely matches Java and C# Lists than Python's List type.

In Python, Arrays are sequence types and behave very much like lists[1] In other words, even in Python, arrays have similar semantics to Javascript Arrays not Java Arrays.

[1] https://docs.python.org/3.4/library/array.html


  What Java, JavaScript and C# call "arrays"
  are each very much like what Python calls
  "lists'.
In Java and C#, 'arrays' are fixed size at creation time.

Both Java and C# provide 'lists' which are variable sized*

Python 'lists' are variable-sized.

Seems like a consistent naming scheme to me?

*there might be an underlying array that gets reallocated - but it's encapsulated within the list object; the reference to the list object is unchanged when this happens.


Fair point, technically.

But Python predates C# and Java.


In C#, all variables ( reference or value types ) are passed by value. Yes, even reference types are passed by value. If you want to pass variables by reference, you need to use special modifiers ( out or ref ).


Yes, but for a reference types that value IS a reference, so if you change a complex type, that change will persist after return to the calling code.


You are correct that changes to the object will be reflected in the calling code. But that's the nature of the reference, not whether it is called by value or called by reference. Whether you pass a reference by value or by reference, the changes to the object will be reflected in the calling code. And as I noted, it's all passed by value in C# unless you use modifiers ( ref or out ).

The difference between "pass by value" and "pass by reference" for reference variables is how the variables themselves are handled. For example, if you pass a reference "by reference" to a function and set it to null, then the reference variable in the calling code is also set to null and will cause null ref exception if you try to access the object. Whereas if you pass a reference "by value", if you set the variable to null, the calling code variable isn't affected.

Pass by value and pass by reference is not about objects or types really, it's about variables.


static void Change(int[] myArray) { myArray = new int[5] {3, 1, 2}; }

this will not change the array which was passed in and is a purely local change in C#.

static void Change(ref int[] myArray) on the other side would change the array which was passed in.


But it’s still pass by value. Passing by ref allows swapping out the entire object, which is not possible in a language such as JavaScript.


Right, but this is the same way Python works. Jumping back to the original argument in the post, the author claimed that Python was "going out of it's way to be different" by passing objects by reference value. C# provides a counterxample to that claim.


Java passes by value. The value being passed happens to be a copy of a pointer (properly called a "reference" in the spec, which confuses people). A copy of a pointer points to the same place as the original pointer. For normal use cases this works very well.

However, if you reassigned your copy of a pointer in the body of a function, the original pointer would still point to the same place it did before the function was called.

That's not the same thing as actual pass-by-reference in languages like C++.


Also, in javascript the thing that's called "an array" is actually a hashmap, because everything in javascript is secretly just a hashmap.


And python does have arrays in the array module.


If I remember VB6/VBA passes everything by reference by default, even value types.


> Most programming languages pass function parameters by value

The biggest irony here is that if you wrote your code in C instead, you would actually pass more arguments by reference than in equivalent python, because you're going to use pointers for everything but primitive integer values.


No, the biggest irony is that C as a language does not even have a syntax for passing by reference. Everything, including pointers, is passed by value. References were introduced in C++.


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

Search: