8 Reasons Python Sucks
Saturday, 15 December 2018
Occasionally I go out to lunch with some of my techie friends and we have a great time geeking together. We talk about projects, current events, and various tech-related issues. Inevitably, the discussion will turn to programming languages. One might lament "I have to modify some Java code. I hate Java. (Oh, sorry, Kyle.)" (It probably doesn't help that we gave Kyle the nickname "Java-boy" over a decade ago.) Another will gripe about some old monolithic shell code that nobody wants to rewrite.
And me, well... I just blurted it out: I hate Python. I hate it with a passion. If I have the choice between using some pre-existing Python code or rewriting it in C, I'd rather rewrite it in C.
When I finished shouting, Bill humorously added, "But what do you really think about Python, Neal?" So I'm dedicating this blog entry to Bill.
Here's my list of "8 reasons Python sucks".
I'm all for adding new functionality to languages. I don't even mind if some old version becomes obsolete. However, Python installs in separate installations. My code for Python 3.5 won't work with the Python 3.7 installation unless I intentionally port it to 3.7. Enough Linux developers have decided that porting isn't worth the effort, so Ubuntu installs with both Python2 and Python3 -- because they are needed by different core functions.
This lack of backwards compatibility and split versions is usually a death knell. Commodore created one of the first home computers (long before the IBM PC or Apple). But the Commodore PET wasn't compatible with the subsequent Commodore CBM computer. And the CBM wasn't compatible with the VIC-20, Commodore-64, Amiga, etc. So either you spent a lot of time porting code from one platform to another, or you abandoned the platform. (Where's Commodore today? It died out as users abandoned the platform.)
Similarly, Perl used to be very popular. But when Perl3 came out, it wasn't fully backwards compatible with a lot of Perl2 code. The community griped, good code was ported, and the rest was abandoned. Then came Perl4 and the same thing happened. When Perl5 came out, a lot of people just switched to a different programming language that was more stable. Today, there's only a small community of people actively using Perl to maintain existing Perl projects. I haven't seen any major new projects based on Perl.
By the same means, Python has distinct silos of code for each version. And the community keeps dragging along the old versions. So you end up with a lot of old, dead Python code that keeps getting dragged along because nobody wants to spend the time porting it to the latest version. As far as I can tell, nobody creates new code for Python2, but we drag it along because nobody's ported the needed code to Python3.x. At the official Python web site, their documentation is actively maintained and available for Python 2.7, 3.5, 3.6, and 3.7 -- because they can't decide to give up on the old code. Python is like the zombie of programming languages -- the dead just keep walking on.
So instead, you install the version of Python you need. For one of the projects I was on, we used Python. But we had to use Python3.5 (the latest at that time). My computer ended up with Python2, Python2.6, Python3, and Python3.5 installed. Two were from the operating system, one was for the project, and one came in because of some unrelated software I installed for some other reason. Even though they are all "Python", they are not all the same.
If you want to install packages for Python, you're supposed to use "pip". (Pip stands for "Pip Installs Packages", because someone thinks recursive acronyms are still funny.) But since there's a bunch of versions of Python on the system, you have to remember to use the correct version of pip. Otherwise, 'pip' might run 'pip2' and not the 'pip3.7' that you need. (And you need to specify the actual path for pip3.7 if the name doesn't exist.)
I was advised by one teammate that I needed to configure my environment so that everything uses the Python 3.5 base. This worked great until I started on a second project that needed Python 3.6. Two concurrent projects with two different versions of Python -- no, that wasn't confusing. (What's the emoticon for sarcasm?)
The pip installer places files in the user's local directory. You don't use pip to install system-wide libraries. And Gawd forbid you make the mistake of running 'sudo pip', because that can screw up your entire computer! Running sudo might make some packages install at the system level, some install for the wrong version of Python, and some files in your home directory might end up being owned by root, so future non-sudo pip installs may fail due to permissions. Just don't do it.
By the way, who maintains these pip modules? The community. That is, no clear owner and no enforced chain of provenance or accountability. Earlier this year, a version of PyPI was found to have a backdoor that stole SSH credentials. This doesn't surprise me at all. (I don't use Node.js and npm for the same reason; I don't trust their community repositories.)
Most programming languages use some kind of notation to identify scope -- where a function begins and ends, actions contained in a conditional statement, range of a variable's definition, etc. With C, Java, JavaScript, Perl, and PHP, braces {...} define the scope. Lisp uses parenthesis (...). And Python? It uses spaces. If you need to define a scope for complex code, then you indent the next few lines. The scope ends when the indent ends.
The Python manual says that you can use any number of spaces or tabs for defining the scope. However, ALWAYS USE FOUR SPACES PER INDENT! If you want to indent twice for nesting, use eight spaces! The Python community has standardized on this nomenclature, even though it isn't in the Python manual. Forget the fact that the examples in the documentation use tabs, tabs + 1 space, and other indents. The community is rabid about using four spaces. So unless you plan to never show your code to anyone else, always use four spaces for each indent.
When I first saw Python code, I thought that using indents to define the scope seemed like a good idea. However, there's a huge downside. Deep nesting is permitted, but lines can get so wide that they wrap lines in the text editor. Long functions and long conditional actions may make it hard to match the start to the end. 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.
For other languages, I've picked up the habit of putting debug code without any indents. This way, I can quickly browse the code and easily identify and remove debugging code when I'm done. But with Python? Anything not indented properly generates an indention error. This means debugging code must blend in to the active code.
Python's import permits including an entire module, part of a module, or a specific function from a module. Finding a list of what can be imported is non-intuitive. With C, you can just look in /usr/include/*.h. But with Python? It's best to use 'python -v' to list all of the places it looks, and then search every file in every directory and subdirectory from that list. I have friends who love Python and I've seen them grep through standard modules as they look for the thing they want to import. Seriously.
The import function also allows users to rename the imported code. They basically define a custom namespace. At first glance, this might seem nice, but it ends up impacting readability and long-term support. Renaming modules is great for small scripts, but really bad for long programs. People who use 1-2 letter namespaces, like "import numpy as n" should be shot (or forced to convert all of their code to Perl5).
But that's not the worst part. With most languages, including code just includes the code. A few languages, like object-oriented C++, may execute code if there's a global object with a constructor. Similarly, some PHP code may define global variables, so an import could run code -- but that's typically considered a bad practice. In contrast, many Python modules include initialization functions that run during the import. You don't know what's running, you don't know what it does, and you might not notice. Unless there's a namespace conflict, in which case you get to spend many fun hours tracking down the cause.
And then there are the names of libraries. 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?
Some common libraries just gave up on the pun-like "Py" naming convention. This includes, matplotlib, nose, Pillow, and SQLAlchemy. And while some of the names may give you a hint to the purpose (e.g., "SQLAlchemy" contains SQL, so it's probably an SQL interface), others are just random words. If you didn't know what "BeautifulSoup" did, could you tell from the name that it's an HTML/XML parser? (As an aside, BeautifulSoup is well documented and easy to use. If every Python module was like this, I wouldn't be complaining so much. Unfortunately, this is the exception and not the norm. Most Python libraries seriously suck at documentation.)
Overall, I view Python as a collection of libraries with horrible and inconsistent naming conventions. I have a standing gripe that open source projects typically have horrible names. Unless you know the project, you'll never figure out what it does by the name. And unless you know what to look for, you'll probably only find it by accidentally stumbling across someone who mentions it in passing. Most of Python's libraries reinforce this negative criticism.
This is one of the big differences between procedural, functional, and object-oriented programming languages. If every variable is passed by object reference, and any change to the variable changes the reference everywhere, then you might as well use globals for everything. Calling the same object by different names doesn't change the object, so it is effectively global. And as C programmers learned long ago, global variables are evil and should not be used.
In Python, you have to work to pass variables by value. Saying "a=b" just assigns another name to the same object space; this doesn't copy the value of b into a. If you actually meant to copy the value, then you need to use a copy function. Usually this is "a=b.copy()". However, notice that I said "usually". Not all data types have a 'copy' prototype. Or maybe the copy function is incomplete. In those cases, there is a separate library called 'copy' that you can use: "a=copy.deepcopy(b)".
With C, Java, JavaScript, Perl, PHP, etc., this works fine because the language can easily distinguish resource libraries from the local program; they have different paths. But with Python? Don't do this. Never do this. Why? Python assumes you want to import the local code first. If I have a program called "screencapture.py" that uses "import screencapture", then it will import itself rather than the system library. At minimum, you should call your local program "myscreencapture.py" instead.
My friends often cite all of the really cool Python libraries that exist. And I agree that some of the libraries are really useful. For example, BeautifulSoup is one of the best HTML parsers I've ever used, NumPy makes multidimensional arrays and complex mathematics easier to implement, and TensorFlow is very useful for machine learning. However, I'm not going to make a monolithic program in Python just because I like TensorFlow or SciPy. I'm not going to give up readability and maintainability for a free pony; it's not worth the effort.
Usually when I write negative criticisms about a topic, I also try to write something positive. I followed my blog entry on "Open Source Sucks" with "Open Source Rocks". And when I wrote about limitations with FFmpeg, I explicitly mentioned how it's the best video processing library out there. But I can't make a list of good things about Python because I really think that Python sucks.
(Hey Bill, does this answer your question?)
And me, well... I just blurted it out: I hate Python. I hate it with a passion. If I have the choice between using some pre-existing Python code or rewriting it in C, I'd rather rewrite it in C.
When I finished shouting, Bill humorously added, "But what do you really think about Python, Neal?" So I'm dedicating this blog entry to Bill.
Here's my list of "8 reasons Python sucks".
Reason 1: Versions
If you install a default Linux operating system, there's a really good chance that it will install multiple versions of Python. It will probably have Python2 and Python3, and maybe even some fractional versions like 3.5 or 3.7. There's a reason for this: Python3 is not fully compatible with Python2. Even some of the fractional versions are distinct enough to lack backwards compatibility.I'm all for adding new functionality to languages. I don't even mind if some old version becomes obsolete. However, Python installs in separate installations. My code for Python 3.5 won't work with the Python 3.7 installation unless I intentionally port it to 3.7. Enough Linux developers have decided that porting isn't worth the effort, so Ubuntu installs with both Python2 and Python3 -- because they are needed by different core functions.
This lack of backwards compatibility and split versions is usually a death knell. Commodore created one of the first home computers (long before the IBM PC or Apple). But the Commodore PET wasn't compatible with the subsequent Commodore CBM computer. And the CBM wasn't compatible with the VIC-20, Commodore-64, Amiga, etc. So either you spent a lot of time porting code from one platform to another, or you abandoned the platform. (Where's Commodore today? It died out as users abandoned the platform.)
Similarly, Perl used to be very popular. But when Perl3 came out, it wasn't fully backwards compatible with a lot of Perl2 code. The community griped, good code was ported, and the rest was abandoned. Then came Perl4 and the same thing happened. When Perl5 came out, a lot of people just switched to a different programming language that was more stable. Today, there's only a small community of people actively using Perl to maintain existing Perl projects. I haven't seen any major new projects based on Perl.
By the same means, Python has distinct silos of code for each version. And the community keeps dragging along the old versions. So you end up with a lot of old, dead Python code that keeps getting dragged along because nobody wants to spend the time porting it to the latest version. As far as I can tell, nobody creates new code for Python2, but we drag it along because nobody's ported the needed code to Python3.x. At the official Python web site, their documentation is actively maintained and available for Python 2.7, 3.5, 3.6, and 3.7 -- because they can't decide to give up on the old code. Python is like the zombie of programming languages -- the dead just keep walking on.
Reason 2: Installation
With most software packages, you can easily run apt, yum, rpm, or some other install base and get the most recent code. That isn't the case with Python. If you install using 'apt-get install python', you don't know what version you're actually installing, and it may not be compatible with all of the code you need.So instead, you install the version of Python you need. For one of the projects I was on, we used Python. But we had to use Python3.5 (the latest at that time). My computer ended up with Python2, Python2.6, Python3, and Python3.5 installed. Two were from the operating system, one was for the project, and one came in because of some unrelated software I installed for some other reason. Even though they are all "Python", they are not all the same.
If you want to install packages for Python, you're supposed to use "pip". (Pip stands for "Pip Installs Packages", because someone thinks recursive acronyms are still funny.) But since there's a bunch of versions of Python on the system, you have to remember to use the correct version of pip. Otherwise, 'pip' might run 'pip2' and not the 'pip3.7' that you need. (And you need to specify the actual path for pip3.7 if the name doesn't exist.)
I was advised by one teammate that I needed to configure my environment so that everything uses the Python 3.5 base. This worked great until I started on a second project that needed Python 3.6. Two concurrent projects with two different versions of Python -- no, that wasn't confusing. (What's the emoticon for sarcasm?)
The pip installer places files in the user's local directory. You don't use pip to install system-wide libraries. And Gawd forbid you make the mistake of running 'sudo pip', because that can screw up your entire computer! Running sudo might make some packages install at the system level, some install for the wrong version of Python, and some files in your home directory might end up being owned by root, so future non-sudo pip installs may fail due to permissions. Just don't do it.
By the way, who maintains these pip modules? The community. That is, no clear owner and no enforced chain of provenance or accountability. Earlier this year, a version of PyPI was found to have a backdoor that stole SSH credentials. This doesn't surprise me at all. (I don't use Node.js and npm for the same reason; I don't trust their community repositories.)
Reason 3: Syntax
I'm a strong believer in readable code. And at first glance, Python seems very readable. That is, until you start making large code bases.Most programming languages use some kind of notation to identify scope -- where a function begins and ends, actions contained in a conditional statement, range of a variable's definition, etc. With C, Java, JavaScript, Perl, and PHP, braces {...} define the scope. Lisp uses parenthesis (...). And Python? It uses spaces. If you need to define a scope for complex code, then you indent the next few lines. The scope ends when the indent ends.
The Python manual says that you can use any number of spaces or tabs for defining the scope. However, ALWAYS USE FOUR SPACES PER INDENT! If you want to indent twice for nesting, use eight spaces! The Python community has standardized on this nomenclature, even though it isn't in the Python manual. Forget the fact that the examples in the documentation use tabs, tabs + 1 space, and other indents. The community is rabid about using four spaces. So unless you plan to never show your code to anyone else, always use four spaces for each indent.
When I first saw Python code, I thought that using indents to define the scope seemed like a good idea. However, there's a huge downside. Deep nesting is permitted, but lines can get so wide that they wrap lines in the text editor. Long functions and long conditional actions may make it hard to match the start to the end. 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.
For other languages, I've picked up the habit of putting debug code without any indents. This way, I can quickly browse the code and easily identify and remove debugging code when I'm done. But with Python? Anything not indented properly generates an indention error. This means debugging code must blend in to the active code.
Reason 4: Includes
Most programming languages have some way to include other chunks of code. For C, it's "#include". For PHP, there's 'include', 'include_once', 'require', and 'require_once'. And for Python, there's "import".Python's import permits including an entire module, part of a module, or a specific function from a module. Finding a list of what can be imported is non-intuitive. With C, you can just look in /usr/include/*.h. But with Python? It's best to use 'python -v' to list all of the places it looks, and then search every file in every directory and subdirectory from that list. I have friends who love Python and I've seen them grep through standard modules as they look for the thing they want to import. Seriously.
The import function also allows users to rename the imported code. They basically define a custom namespace. At first glance, this might seem nice, but it ends up impacting readability and long-term support. Renaming modules is great for small scripts, but really bad for long programs. People who use 1-2 letter namespaces, like "import numpy as n" should be shot (or forced to convert all of their code to Perl5).
But that's not the worst part. With most languages, including code just includes the code. A few languages, like object-oriented C++, may execute code if there's a global object with a constructor. Similarly, some PHP code may define global variables, so an import could run code -- but that's typically considered a bad practice. In contrast, many Python modules include initialization functions that run during the import. You don't know what's running, you don't know what it does, and you might not notice. Unless there's a namespace conflict, in which case you get to spend many fun hours tracking down the cause.
Reason 5: Nomenclature
In every other language, arrays are called 'arrays'. In Python, they are called 'lists'. And an associative array is sometimes called a 'hash' (Perl), but Python calls it a 'dictionary'. Python seems to go out of it's way to not use the common terms found throughout the computer and information science field.And then there are the names of libraries. 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?
Some common libraries just gave up on the pun-like "Py" naming convention. This includes, matplotlib, nose, Pillow, and SQLAlchemy. And while some of the names may give you a hint to the purpose (e.g., "SQLAlchemy" contains SQL, so it's probably an SQL interface), others are just random words. If you didn't know what "BeautifulSoup" did, could you tell from the name that it's an HTML/XML parser? (As an aside, BeautifulSoup is well documented and easy to use. If every Python module was like this, I wouldn't be complaining so much. Unfortunately, this is the exception and not the norm. Most Python libraries seriously suck at documentation.)
Overall, I view Python as a collection of libraries with horrible and inconsistent naming conventions. I have a standing gripe that open source projects typically have horrible names. Unless you know the project, you'll never figure out what it does by the name. And unless you know what to look for, you'll probably only find it by accidentally stumbling across someone who mentions it in passing. Most of Python's libraries reinforce this negative criticism.
Reason 6: Quirks
Every language has its quirks. With C, there's the weird nomenclature of using & and * for accessing address space and values. C also has that increment/decrement shortcut using ++ and --. With Bash, there's the whole "when to use a backslash" when quoting special characters like parenthesis and periods for regular expressions. And JavaScript has issues around compatibility (not every browser supports every useful function). However, Python has more quirks than any other language I've ever seen. Consider strings:- In C, double quotes enclose strings. Single quotes enclose characters.
- In PHP and Bash, both types of quotes can enclose strings. However, a double quote can have variables embedded in the string. In contrast, single quoted strings are literals; any embedded variable-like names are not expanded.
- In JavaScript, there's really no difference between single quotes and double quotes.
- In Python, there's no difference between single quotes and double quotes. However, if you want your string to span lines, then you need to use triple quotes """string""" or '''string'''. And if you want to use binary, then you need to preference the string with b (b'binary') or r (r'raw'). And sometimes you need to cast your strings as strings using str(string), or convert it to utf8 using string.encode('utf-8').
Reason 7: Pass By Object Reference
Most programming languages pass function parameters by value. If the function alters the value, the results are not passed back to the calling code. But as I've already explained, Python goes out of its way to be different. Python defaults to doing functions with pass-by-object-reference parameters. This means that changing the source variable may end up changing the value.This is one of the big differences between procedural, functional, and object-oriented programming languages. If every variable is passed by object reference, and any change to the variable changes the reference everywhere, then you might as well use globals for everything. Calling the same object by different names doesn't change the object, so it is effectively global. And as C programmers learned long ago, global variables are evil and should not be used.
In Python, you have to work to pass variables by value. Saying "a=b" just assigns another name to the same object space; this doesn't copy the value of b into a. If you actually meant to copy the value, then you need to use a copy function. Usually this is "a=b.copy()". However, notice that I said "usually". Not all data types have a 'copy' prototype. Or maybe the copy function is incomplete. In those cases, there is a separate library called 'copy' that you can use: "a=copy.deepcopy(b)".
Reason 8: Local Names
It's a common programming technique to name the program after the library or function being used. For example, if I'm testing a screen capture program with a C library called "libscreencapture.so", I would call my program "screencapture.c" and compile into "screencapture.exe".gcc -o screencapture.exe screencapture.c -lscreencaptureWith C, Java, JavaScript, Perl, PHP, etc., this works fine because the language can easily distinguish resource libraries from the local program; they have different paths. But with Python? Don't do this. Never do this. Why? Python assumes you want to import the local code first. If I have a program called "screencapture.py" that uses "import screencapture", then it will import itself rather than the system library. At minimum, you should call your local program "myscreencapture.py" instead.
Not All Bad
Python is a very popular language and has a huge following. I even have a handful of friends who really like Python -- it's their preferred programming language. Over the years, I've discussed these issues with them, and each time they nod their heads and agree. They don't disagree that these are problems with Python; they just think it's not bad enough for them to stop loving the language.My friends often cite all of the really cool Python libraries that exist. And I agree that some of the libraries are really useful. For example, BeautifulSoup is one of the best HTML parsers I've ever used, NumPy makes multidimensional arrays and complex mathematics easier to implement, and TensorFlow is very useful for machine learning. However, I'm not going to make a monolithic program in Python just because I like TensorFlow or SciPy. I'm not going to give up readability and maintainability for a free pony; it's not worth the effort.
Usually when I write negative criticisms about a topic, I also try to write something positive. I followed my blog entry on "Open Source Sucks" with "Open Source Rocks". And when I wrote about limitations with FFmpeg, I explicitly mentioned how it's the best video processing library out there. But I can't make a list of good things about Python because I really think that Python sucks.
(Hey Bill, does this answer your question?)


Some nitpicking: You probably did not end up with 4 versions of Python. python2 and python3 are usually symlinks to the (latest) minor Python version. Making Python 3 backwards incompatible in so many (subtle) ways has, I think, generally been considered a problem and is unlikely to be repeated. To my knowledge, Python versions have been backwards compatibility in general lately, with the exception of modules like asyncio which have been marked as experimental.
Regarding nomenclature: While I do agree that "dict(ionary)" is a bit off, "list" makes perfect sense to me. An array usually has the property in terms of runtime complexity that you can't insert at an arbitrary position without moving elements. This is not true for lists in Python.
I also think that strings and their implementation makes sense. Granted, I don't know if distinguishing multi and single line strings is really necessary (or useful) but I really like the possibility to prefix a string literal. There are good reasons that strings are not just bytes (and the other way around) and disabling of escape sequences (raw strings) makes also sense to me.
Thanks for the feedback.
I ran off and checked my dev server. (The project is long over, but the VM is still lying around.)
python == 3.5.2 (from OS)
python2 == 2.7.6 (from OS)
python3 == 3.5.2 (from OS)
python3.5 == 3.5.6 (manually installed for project #1, resides in a separate directory/environment)
python3.7 == 3.7.0 (manually installed for project #2, separate directory/environment)
(I had python3.6 installed, but we eventually updated to 3.7.)
As you can tell, I'm not a huge python fan. But when one of my teammates says "you must run this specific version of python", that's what I do.
Pipenv, Virtualenv or even condas environments will change your life... Well if your life revolves around Python
The PyCharm IDE (JetBrains team) on both Windows and Linux does a very nice job of keep versions separate, and projects in their own virtualenv as well. You don't need an IDE for that, but I find it helps. I might die without PyCharm's ease of refactoring.
Here I am hating on the language I love, but I remember when I would have to review this question on StackOverflow every time I wanted to create a new virtual environment:
https://stackoverflow.com/questions/41573587/what-is-the-difference-between-venv-pyvenv-pyenv-virtualenv-virtualenvwrappe What a mess.
I was grateful that PyCharm finally ended that pain, as it handles it for both version 2 and 3.
Strings are thankfully much better in version 3, finally default utf-8!
Yay that all the packages I've ever needed have been ported to 3 by now. It was bad for a while. I can imagine that some specialized packages remain left behind.
"Use an IDE" isn't the best defense for a language's flaws, but then again JetBrains keeps delivering what I didn't even realize I needed. I don't code in C# nearly as much today but ReSharper was (is?) a complete game changer. That was a purchase for me back then; PyCharm Community Edition is free however, and works a treat.
Having read your blog quite a long time now, I doubt you're about to run open-armed to it... but sometimes I'm wrong.
I manage my non-OS versions with `pyenv` but sadly, there doesn't seem to be much in the way of alternatives to the virtualenv system for vendoring packages. Everyone seems to agree that virtualenv is terrible, but they all insist on building on top of it.
You're wrong about Perl and versions. As a long-time coder and (non-obsessive) fan of the language. IIRC Perl's backward compatibility has always been pretty good. Perl 5 quickly came to dominance, especially with mod_perl. And Perl 5's backward compatibility in particular, despite many new features, is legendary.
import numpy as np
import pandas as pd
This is pretty standard across the board. I would hazard to guess if the reader of code hasn't read this sort of code, they are very junior to this area of python. Not sure why the shooting is needed.
If your function is too large it means that it is a strong candidate to be refactored in smaller functions.
In any programming language large functions are a bad practice.
That is, the indentation of python encourages the programmer to write clean code with smaller and atomic functions.
Are you serious?
Type "collection list" and "collection dictionary" into Google, for starters.
Here's but one example.
>>> [f(1) for f in [ lambda(x) : x+1 for x in range(10)]]
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
Why is this scoping strange @lab27? Obviously function arguments defined within the function scope should override variables defined outside the function, this is true in all block and function-scoped languages.
>>> [f(1) for f in [lambda y: x+y for x in range(10)]]
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
It's really just a lexical closure... you shouldn't be expecting this expression to create a "new y" for the lambda expression every time through the loop. The behavior may be a bit surprising in this context, yes, but if you're going to use a lambda expression in a nested list comprehension you'd better understand enough about your language to think through what it actually means.
Python's only crime here is to give you enough rope to hang yourself, which is something normally Perl is more famous for. In truth Python is more powerful than people often think, but using this power in "clever" ways is usually discouraged, in part by syntax and in part by convention.
In the case of lambda for example, they are rather crippled by syntax (being limited to a single expression) to discourage this kind of thing; don't write LISP code using Python, use lambdas only for things like returning a sorting key, etc. But they are in the language, so if you REALLY insist you can abuse them.
BeautifulSoup derives its name from "Tag Soup" (https://www.wikiwand.com/en/Tag_soup).
I've worked on many Python codebases in the 100KLOC+ range, and readability does not decrease with size. If you're nesting your code so deep that it's going off the edge of your screen, that's a code smell in any language.
Import errors that are hidden are indeed a problem, I hope to see a solution. But code inclusion, where other code is context-dependent is even harder to debug and less predictable.
And you made good point about pass-by-object: it does create side effects.
The other thing I dearly miss in Python is JIT compilation. The existing JIT projects all have their own set of compromises. Why is there no standard @jit decorator? Or why is it not just done automatically, like .Net? Once we have first-class strict static type support, compilation can produce very good code where it matters.
Python's list are not called arrays, because python has actual arrays in the traditional sense: https://docs.python.org/3/library/array.html (It's in the standard library, but it has been there from the very beginning). Classically, 'arrays' are fixed-size collections of data of the same type. Python arrays are not at all like this, neither their behaviour, nor their interface let alone their implementation. The term's meaning has been extended by many other modern languages. But i think python shouldn't be blamed for sticking to classical terminology. If you really want to blame someone, it's all the other languages doing to wrong
There are alternatives also, you could use Haxe, which provides for a more JSish stynax and target Python 3.
https://pipenv.readthedocs.io/
it might help with some of your versioning/environment issues.
A dict(ionary) is relatively common, as is a map, both of which make more sense that a hash, which is just something that most dictionaries use as an implementation detail.
Passing by object reference is fairly standard in high-level OO languages. In fact, all the popular ones do it except C, AFAIK.
When it comes to library names, there are terrible names for things throughout the industry. If everything used a name that made it easy to tell what it is, you'd run into the problem of everything either having way too similar of names or super long names being used to be distinguishable.
Also PyPy and PyPI are not pronounced the same (officially).
I don't understand your bit about the local names thing. What are you trying to import with that opening import that has the same name as the module?
1. Versions
- I like Python to take deprecation seriously. It is not a bug, it is a feature.
- If code is not maintained, you probably should not be using it anyway.
2. Installation
- Just use virtual environments. You have the venv module integrated in vanilla Python or you can use conda from the Anaconda distribution.
3. Syntax
- Every decent Python editor (including the most basic gedit) inserts 4 spaces when pressing the TAB key.
- Yes, you can use any kind of indentation you want: the rule is to be consistent. Either you always use TABs or spaces, just do not mix them.
4. Includes
- Have you seen the official Python documentation? The standard library is comprehensive and well documented. Yeah: you can import that.
- If the standard library doesn't meet up with your needs just ask. The Hitchhiker’s Guide to Python! is a great resource to community-developed packages.
5. Nomenclature
- I like Python nomenclature for native data types such as lists, bytes, strings, dictionaries, and so on. In fact, I would argue that the Python nomenclature makes more sense than those implementation-based technicalities.
- As for the horrible names in OSS, that is a feature: people can call their software whatever they like, and reinvent the wheel how many times they want. This freedom is what makes OSS a strong and diverse community, and also the reason why Python is growing so much.
6. Quirks
- Are you kidding? This quotation system is great! You can stick to "double quotes" if you want; nobody forces you otherwise, but I usually write strings with quotations, line returns, and utf8 symbols. This quotation system makes a lot easier to do that.
- As for the binary strings, that is because we live in the era of Unicode, were a character is NOT a byte. Separating implementation from concept makes string processing hell of a lot easier.
- As for the raw strings, just don't use it; they are totally optional: I'd love to see your regular expressions encumbered with double backlashes.
7. Pass By Object Reference
- Whenever you learn a new language you have to deal with the object model. In Python, objects are passed as reference, that's it!
- I like it because I have to deal with massive data objects and I don't want them to be copied over and over.
8. Local Names
- Same as before. Also, as I said in the beginning, Python is not C; I certainly don't want it to be C.
if True:
____print("It must be indented...");(
print("Really??"))
____print("Haha")
"""With C, you can just look in /usr/include/*.h. But with Python?"""
---
Sorry but I see you knows nothing about _import_ and the importlib. The python has much more tools to import than the PHP. Please read the following docs!
https://docs.python.org/3/library/functions.html#_import_
https://docs.python.org/3/library/importlib.html
Python3 versions are generally backwards compatible unless you depend on bugs or experimental new modules such as asyncio so not sure what you refer to there?
2. See #1
3. False. Python3 automatically validates inconsistent whitespace. You can't mix 3 and 4 spaces in the same file. In python 2 you can use the -tt command line flag to do this. You should also be running automatic code linter (pylint, flake8, pycharm, etc) which catches this as well. The official recommendation about tabs vs spaces is https://www.python.org/dev/peps/pep-0008/#tabs-or-spaces which is very clear on recommending spaces so not sure why you claim it's ambigous which one to use.
Using flat indentation for debugging code is also very weird, most editors like Visual Studio for C# even auto indent the last block whenver you type semicolon or closing bracket.
Nesting so deep you run out of screen space sounds like a problem with your code and not the enforced indentation .
4. Imports running code is possible but most high quality modules don't do this and it's considered bad practice. Your comparision is also strange because PHP include works in exactly the same way as python, there you even have to create include guards unless you use include_once.
For finding modules and navigating in modules just install PyCharm and you get auto complete on all possible imports and can ctrl-click your way into them. People who grep through standard modules are not friends.
"import as" is supported in most languages, like TS, JS, C++, C#, etc... It can be abused and i rarely seen it be.
5. C# has Dictionary and List. Java has ArrayList. C++ has std::list. These are not strange names. And some third party modules have ugly names, so what, don't use them. Who searches and randomly installs packages based on names anyway? Regardless of language always google for "best library to do X in $PROGRAMMING_LANGUAGE" then go by reputation.
6. All of the differently quoted strings create the same object, there is nothing strange whatsoever about the == operator. Simply: "foo" == 'foo' == """foo""" == r"foo"
You NEVER have to cast a string with string(string) and the only time you need to decode() it is if you receive a raw byte-stream over network or open a file in binary mode. (actually in that case you are not decoding a string, you are decoding a bytestring)
b"-strings are different though, they are not strings, they are bytes.
7. C#, Java, PHP and Javascript all behave the exact same way as Python here. This is very normal behavior.
8. Valid complaint but i would say naming your module the same as another one is a bit strange. C has the same quirk btw. Not sure i see any obvious solution for it either.
I disagree in this point. The speed of a software is a part of a the high quality. So use it if you need it to avoid the too slow running time.
It's rare for 3.x and 3.y to be incompatible, as well as for 2.x to 2.y and 1.x to 1.y.
The changes from 2.7 to 3.0 were relatively large, but still not that big of a deal, and important for the long-term health of the language. The main change was that unicode became str, and str became bytes.
I choose to keep around many versions of Python for ease of comparison. It's not confusing at all. I build them using http://stromberg.dnsalias.org/~strombrg/cpythons/
Deep nesting is a mistake in any language.
Most text editors make it easy to use 4 spaces.
Having 3 spaces somewhere isn't a big problem, and a linter will likely catch it.
Adding debugging print's without indentation is an interesting idea. I just prefix all my debugging prints with "fred: ".
Finding a list of what can be imported is easy in a Python REPL: import foo; dir(foo) or help(foo)
To find what file an imported module comes from in a REPL: import foo; foo._file_
from foo import creates namespace conflicts. import foo does not. Do not use from foo import - it confuses linters too much.
Module names that aren't 100% clear are far from unique to Python. This is what Google is for, where a unique name is easier to search for.
Searching on Pypi is generally a pretty painless way to find modules related to a topic.
r'' is not for binary strings, it's for strings that don't do backslash escapes.
Yes, Python uses b'' for a byte string - like Rust.
Explicit type conversions are a defining aspect of strong typing.
Python is pass-by-assignment.
It's very much like pass by value, except that sometimes you pass a pointer by value.
If you change the pointer itself, nothing changes in the caller.
If you change what the pointer points at, of course things are changed in the caller.
This is nothing like using globals for everything.
Naming a script foo.py is poor practice for more than one reason.
One is, the nit you pointed out.
Another is, it unnecessarily ties your script to being forever implemented in Python, even if you decide it should be rewritten in Rust. Last I heard, Debian recommended against this. It supposedly originated in the Perl world, where Perl developers felt that CGI scripts should have the language they were written in, baked in.
Answer: Just don't.
It is broadly available, there is a large community, it often allows to express things in a beautiful way due to nice concepts. I am open for alternatives.
And you may not like the indents, but on the other hand i see it rather like a school programming language, if you dont type nice you get blamed. Thats great to achieve clean coding. Although i'm more a fan of C++ and C#, Python is great.
I love to go to kaggle.com write a few python lines and see how quickly you can do something with it.
Python jupyter notebooks i love them wish more coding languages had something alike it. (setting them up is easy too).
I am far from a python fanboy, and consider myself a true polyglot, but I have always felt if you try to write c in python you are gonna have a bad time, the same way is if you tried to write fortan in C or C++ in Cobol. If you stop trying to force the languages to your preexisting paradigms and allow your self to write the language in a way that is idiomatic I think you would find it much less frustrating. I find that python for scripting, web back end, business logic is among the more productive options available. Embedded or Systems programmers should maybe consider something else.
1. Imports, you barely scratched the surface on this. Python's import system is an absolute mess.
2. Threading. By default you get a single thread, and while some libraries like numpy are threaded rolling your own is a mess. The multiprocessing module seems to be the go to, but largely unmentioned in the docs is that it can only handle functions that can be pickled. Sure, there are workarounds but it makes anything threaded seem like an absolute hack.
3. pep8. Why should the language care how many spaces I put before a comment. At least this one's easy to fix.
4. The docs. They're fantastic for the basic topics, but rather lacking on more advanced stuff. On the same note, "advanced" in the title of any python tutorial seems to guarantee it will cover topics common to any second or third semester computer science course.
5. Reloading modified packages. This depends how an IDE is set up, but it's very easy to inadvertently end up with old versions of objects when packages are reloaded. The only sure way to know you have the latest is a complete restart of the interpreter.
6. Namespaces. Since import* is frowned on, code is full of the shorten abbreviations criticized in the article. Similarly, objects require 'self' to be used throughout. It's something you get used to eventually, but still annoying.
That being said, Python is still the best tool for most of what I'm working on right now. Numpy is a fantastic tool - especially if you're used to matlab's arrays. The flexibility of python is fantastic, but it certainly has some rough edges.
Thanks for the feedback.
I probably could have done a 12 item list, but I thought that 8 was enough. I had ruled out a few things, like performance. "Slow" and "memory hog" are relative. (Compared to bash, everything is fast. And try debugging the sudden delay from some languages randomly doing garbage collection while you're trying to capture all packets on a gigabit network.) I do think that #9 is documentation. The docs for PHP are absolutely fantastic. Python is good for basic concepts, but most third party libraries just suck at docs.
The whole thing about threading... I actually view this as a feature of the language (regardless of whether it sucks or not). They've had 30 years to implement real thread support. I just assumed that the language developers don't want it.
I've read some of the comments that people have left at Reddit and news.ycombinator.com. There's a lot of negative (expected), but a surprisingly large number of people agree with some (or all) of my gripes. Different items seem to strike a chord with different people. I get a kick out of the people who say things like "#2 is just whining" and someone else says "I agree with #2 -- pip is a mess!"
I also got a chuckle form the people who are so certain I'm wrong about broken backwards compatibility. (Does TensorFlow install under Python3.7 yet without building it from source? It works under 3.5 and 3.6, but 3.6 has performance and resource issues.)
https://github.com/tensorflow/tensorflow/issues/20517
https://github.com/tensorflow/tensorflow/issues/20444
https://github.com/tensorflow/tensorflow/issues/23478
A lot of people have suggested different IDEs for writing Python. But here's my issue: if the language really requires an IDE to be properly written, then shouldn't it include a default IDE? (As far as I can tell, every IDE is a third-party addition for working around the syntax limitations.)
And then there's all of the people who have suggested different virtual environments for keeping the different versions straight. Other people complained about how screwed up the virtual environments are. However, I think they're missing the point: why do I need to have separate environments for different projects? And maintaining different environments means more things to maintain. The whole need for virtual environments is just a workaround for Python's design limitations.
Wow, I sure went off on a tangent. Anyway, thanks for the feedback.
Python hates you back.
A lot of efforts just to slay a language; perhaps you should write one better. It sounds like you're afraid of something new.
Python was created in December 1989. That's nearly 30 years ago. (Technically, it's 29 years ago.) It's not "new".
If it were "new", I would give it a lot more leniency and consider the issues to be growing pains. Python is nearly 30 years old and it has no plans to move out of it's mother's basement.
My first encounter with Python was Anaconda, back in 2002. Horrible crap. No comments, space delimited, the whole insane "dictionary list" bs for a simple key-value hash. It gave me bad TCL flashbacks. Plus, the python2 vs python3 thing still drives me nuts. Also, why does it have to import a regex library? Perl comes with it built in,, FFS.
[quote]
Well, the original article wasn't all bad, but the author got a lot of things wrong about Python. I can understand getting frustrated about a thing and not wanting to pick it up and actually understand more because you're so frustrated with it, but I hope the author 1) reads this reply and 2) understands Python enough to gripe about the real problems with Python.
I'm totally fine with people disliking Python for the legitimate problems that Python has, or even because they really just like to have static binding on their variables (or even, like Rust, at least static binding until it's explicitly rebound to a new type) and they really hate that Python does that!
But I do get bothered when people dislike a thing and they're mostly or entirely wrong about the thing they dislike! That's just silly!
[/quote]
2. Stacktraces: totally unreadable.
3. Terrible regular expression library: take a look at Ruby and see how that can be smoothly integrated.
4. join syntax: Despite using Python day to day it still happens to me that I get this wrong. Why is join not a method of array-like types? No other language does it this way.
5. Iterations: not cleanly implemented and not a reasonable object hierarchy. See Java (!), Scala or Ruby how to design that properly.
6. List comprehensions: after 30 years SQL has invented CTE to read statements from left to right. Python goes the other way round and we must now read from right to left. Why not have map for all iterable types?
7. No functional style: why can't I call sort() on an array-like type and get the sorted array? The only option is to sort in place. Ancient.
The Python infrastructure and libraries are fantastic and code is highly optimized. The API though is often not elegant (pandas, numpy) and fosters more of a hacking attitude towards programming than a clean design.
I am always surprised that Python is praised as a fantastic entry-level programming language. I wonder if people supporting this have worked with languages which sport a really clean and consistent design. Although e.g. Ruby is much slower the design of the language AND of the standard library is so much better that it's really beyond comparison.
It is just that Python 3 evolves now and Python 2 in in maintenance mode.
You are also mixing different aspects: if apt get does not offer you the python version you want, than this is an issue of the linux distribution, not Python itself. So in your case Ubuntu sucks, not Python. And there are the mentioned tools like pyenv and virtualenv to handle Python installations pretty well.
The critics of syntax sound for me like "Python and its eco system is not c-ish enough so I don't like it."
You also should not try to compare compiled languages with a static type system to an interpreted language with a dynamic type system. In Python you can solve things more efficient than in C, but the other way around Python can perform bad for tasks where C shines.
I appreciate this discussion a lot, and I am aware Python has good and not so good features but you should try to separate criticism about the language from the interpreters and from the community and from linux distributions.
i start writing something in python sometimes because of reasons I resent afterwards, but i always switch to golang before finishing. it's worth it.
http://www.hackerfactor.com/blog/index.php?/archives/415-Open-Source-Sucks.html
Almost all modern languages support true multi-threading, but python cannot.
Don't talk about multi-process, that's another thing also supported by other languages.
I find it a fatal defect if a language lets me add properties/fields/member variables/functions/methods to an existing object. Because that way lies madness.
How am I supposed to know what an object can do? Check its type? Good luck with that if someone has souped the object up! Heck, python makes defining types optional to begin with.
Static type checking is vital to productivity. The earlier an error is caught in the development process, the less orders of magnitude it costs to fix it. Going from compile time to runtime is at least one order of magnitude.
It also makes it a pain to reason about the code programatically. Code completion gets ugly. Efficient implementation at runtime gets ugly. Remember shadow classes in javascript engines?
In short, duck typing was a mistake. Not just in python, but as a concept itself.
What does LWP ( Perl ) do?
What does capybara ( Ruby ) do?
What does Boost ( C++ ) do?
It's not immediately obvious what these packages do, from just the package name.
I am a PHP & JavaScript developer however I have long wanted to learn Python.
Of all the issues you mentioned what troubles me the most is the pass-by-object-referrence thing. Has anyone every responded to this criticism with a reasonable justification?
Try to do something trivial like have a pytest test script print a string
or "trace" the execution of a python script (i.e. in BASH "set -x")
Aaarggghhh !