Why Python sucks – why I hate Python: a Python rant

Suck #1

I started following a super-basic tutorial on Python as I’d learned the basics over the years and wanted to learn again from scratch.

The very first thing they do is:

python

>> import requests

Simple, right?

No!

I got:

>> import requests
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named requests

So now I’m spending half an hour trying to debug the most basic thing in a Python tutorial. This is clearly a sign of things to come.

Various StackOverflow answers describing complicated solutions that did not help:

https://stackoverflow.com/questions/38836249/error-when-import-requests-no-module-named-requests

https://stackoverflow.com/questions/46751905/import-error-importerror-no-module-named-requests

Turns out the solution was to do:

`pip install requests `

https://stackoverflow.com/questions/17309288/importerror-no-module-named-requests

Fortunately, I had pip installed. But basically a complete beginner would have that as an additional hurdle to overcome.

Note:

>>> import sys
>>> sys.path
['', 'etc....','etc...', '/usr/local/lib/python2.7/site-packages']

''=> current working directory

then searches through directories.

/usr/local/lib/python2.7/site-packages => third party packages

Note: on Debian this is called dist-packages.

Suck #2

Following a script that mentions `os.environ` I get:

`NameError: global name ‘os’ is not defined`

Turns out I need to import os. Python folks – can’t this be done automatically? It’s not hard. Just see a variable that matches a package you know and import it. DONE. Kind of like the Mac’s AutoSave. Let’s get out of the ’80s here…

https://stackoverflow.com/questions/23048129/python-name-os-is-not-defined

Suck #3

Again, following that script I copy and paste part into a REPL to try it out. As the indentation is different I get:

`IndentationError: unexpected indent`

Groan. So, I have to go through the script manually adding indents. Again, let’s get rid of indent restrictions. Stupid.

Suck #4

Gibberish logs and debug output.

A method I had copied was clearly wrong. When I attempted to use it from the REPL with `ec2 = get_client(“ec2”)` I got:

>> ec2 = get_client("ec2")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in get_client
File "/Users/snowcrash/.virtualenvs/my-project/lib/python2.7/site-packages/boto3/session.py", line 80, in __init__
self._setup_loader()
File "/Users/snowcrash/.virtualenvs/my-project/lib/python2.7/site-packages/boto3/session.py", line 120, in _setup_loader
self._loader = self._session.get_component('data_loader')
File "/Users/snowcrash/.virtualenvs/my-project/lib/python2.7/site-packages/botocore/session.py", line 729, in get_component
return self._components.get_component(name)
File "/Users/snowcrash/.virtualenvs/my-project/lib/python2.7/site-packages/botocore/session.py", line 946, in get_component
self._components[name] = factory()
File "/Users/snowcrash/.virtualenvs/my-project/lib/python2.7/site-packages/botocore/session.py", line 186, in <lambda>
lambda: create_loader(self.get_config_variable('data_path')))
File "/Users/snowcrash/.virtualenvs/my-project/lib/python2.7/site-packages/botocore/session.py", line 281, in get_config_variable
elif self._found_in_config_file(methods, var_config):
File "/Users/snowcrash/.virtualenvs/my-project/lib/python2.7/site-packages/botocore/session.py", line 308, in _found_in_config_file
return var_config[0] in self.get_scoped_config()
File "/Users/snowcrash/.virtualenvs/my-project/lib/python2.7/site-packages/botocore/session.py", line 385, in get_scoped_config
raise ProfileNotFound(profile=profile_name)
botocore.exceptions.ProfileNotFound: The config profile (sb) could not be found

What a pile of gibberish. To be clear – 21 lines and 1629 characters of gibberish.

Why not print it out in Assembly just to make it less clear?! Has anyone made any effort whatsoever on making this more user-friendly?

 

or  a line like:

testing = os.environ['testing']

creating:

Traceback (most recent call last):
  File "./mock.py", line 28, in <module>
    testing = os.environ['testing']
  File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/os.py", line 669, in __getitem__
    raise KeyError(key) from None
KeyError: 'testing'

Why not just output:

testing = os.environ['testing'] # Error generated. No such key "testing" 

Suck #5

I’ve got an object that’s been returned using a pretty standard library. i.e.

client = boto3.client('ec2')

There is no output when I run it from the REPL. Sounds OK.

Let’s inspect this object. Looking for introspectionI find:

https://stackoverflow.com/questions/1006169/how-do-i-look-inside-a-python-object

Ooh. Python has a strong set of introspection features. OK, let’s try

client.type()

>>> client.type()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/snowcrash/.virtualenvs/my-project/lib/python2.7/site-packages/botocore/client.py", line 555, in __getattr__
self.__class__.__name__, item)
AttributeError: 'EC2' object has no attribute 'type'

Jeez! Another 6 lines of gibberish.

How about client.getattr()? Surely that’s got to return something?!

>>> support.getattr()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/snowcrash/.virtualenvs/limit-monitor/lib/python2.7/site-packages/botocore/client.py", line 555, in __getattr__
self.__class__.__name__, item)
AttributeError: 'EC2' object has no attribute 'getattr'

Why can’t they just return messages like: AttributeError: 'EC2' object has no attribute 'getattr' and get it over with. Then you could have some debug parameter to find out more if any of the additional text above is actually useful.

At least the very user-friendly underscore underscore dict underscore underscore method (i.e. __dict__) helps figure out what’s going on behind the scenes. That was an easy one to figure out without digging through Stackoverflow. Not.

Suck #6

Let’s follow the 4 simple steps here:

https://github.com/ElasticHQ/elasticsearch-HQ

1. Download or clone the repository.

No problem.

2. Open terminal and point to root of repository. Type: pip install -r requirements.txt

I get (in red):

awscli 1.15.53 has requirement botocore==1.10.52, but you’ll have botocore 1.12.23 which is incompatible.

I’ve no idea now whether I need to upgrade awscli, downgrade botocore. Or even why I’m getting this message.

It also fails with:

Could not install packages due to an EnvironmentError: [Errno 13] Permission denied: ‘/usr/local/man’
Consider using the `–user` option or check the permissions.

Why on earth didn’t the maintainers bother to mention that you might need to use --user in the install instructions.

I run:

pip install --user -r requirements.txt

and no longer see the red fails. I’ve no idea why the awscli, botocore errors have disappeared.

3. Run server with: python3 application.py. Alternatively: ./manage.py runserver

Great. 2 options to fail.

Let’s try the second:

./manage.py runserver

and the nice simple error message:

Traceback (most recent call last):
File "./manage.py", line 11, in <module>
from elastichq import create_app
File "/Users/snowcrash/Code/Elasticsearch/elasticsearch-HQ/elastichq/__init__.py", line 5, in <module>
from elastichq.api import api_blueprint, endpoints, public_blueprint, ws_blueprint
File "/Users/snowcrash/Code/Elasticsearch/elasticsearch-HQ/elastichq/api/endpoints.py", line 4, in <module>
from . import clusters
File "/Users/snowcrash/Code/Elasticsearch/elasticsearch-HQ/elastichq/api/clusters.py", line 11, in <module>
from elastichq.model import ClusterDTO
File "/Users/snowcrash/Code/Elasticsearch/elasticsearch-HQ/elastichq/model/__init__.py", line 3, in <module>
from elastichq.model.ClusterModel import *
File "/Users/snowcrash/Code/Elasticsearch/elasticsearch-HQ/elastichq/model/ClusterModel.py", line 7, in <module>
from ..globals import db, ma
File "/Users/snowcrash/Code/Elasticsearch/elasticsearch-HQ/elastichq/globals.py", line 12, in <module>
from .config import settings
File "/Users/snowcrash/Code/Elasticsearch/elasticsearch-HQ/elastichq/config/settings.py", line 2, in <module>
from functools import lru_cache
ImportError: cannot import name lru_cache

I don’t understand why they couldn’t make this less simple to understand. Perhaps another 1500 lines of gibberish to reduce legibility would have helped. E.g. they could have thrown in some random characters to help confuse the end user.

Given that the instructions say:

Requirements
Python 3.4+

and I find this issue buried on github:

https://github.com/ElasticHQ/elasticsearch-HQ/issues/352

which helpfully suggests ignoring the official (faulty) install instructions and using (the also incorrect):

pip3 install -r requirements.txt

The correct install instructions are:

pip3 install --user -r requirements.txt

https://github.com/ElasticHQ/elasticsearch-HQ/issues/352#issuecomment-370993165

Handily there’s also no mention that:

python3 application.py

will take 10 seconds before it starts generating output and so appearing it’s not working.

4. Access HQ with: http://localhost:5000

Finally!

Did I say I hate Python?

 

Suck #7

Why on earth use 4 spaces?!

Let me rephrase it. If you use one less or more of an invisible character your code will break. Or if you swap it out for another invisible character that looks exactly the same (i.e. the tab) it will also break.

Brilliant design idea.

Let’s find something analogous in the real world.

If you reach for the right glass you can have a drink of water.

But if you reach for the wrong glass (that looks exactly the same) it breaks.

 

Suck #8

Strong typing.

I refuse to believe strong typing is the future of coding. All the talk about catching bugs earlier is guff. Adding pre-compilation gates just slows down the coding process. The compiler and interpreter should be the ones doing the heavy lifting – not the programmer.

E.g.

print(“URL: + some_url)

if some_url is None then you get:

Traceback (most recent call last):
File "my-script.py", line 451, in <module>
lambda_handler(None, None)
File "my-script.py", line 442, in lambda_handler
print("Some URL: " + some_url)
TypeError: cannot concatenate 'str' and 'NoneType' objects

 

 

Suck #9

Stuff breaking.

I’m fed up of junk like this:

pyenv install 2.7.14
pyenv install 2.7.14
python-build: use openssl from homebrew
python-build: use readline from homebrew
Downloading Python-2.7.14.tar.xz...
-> https://www.python.org/ftp/python/2.7.14/Python-2.7.14.tar.xz
Installing Python-2.7.14...
python-build: use readline from homebrew
ERROR: The Python zlib extension was not compiled. Missing the zlib?

Please consult to the Wiki page to fix the problem.
https://github.com/pyenv/pyenv/wiki/Common-build-problems


BUILD FAILED (OS X 10.13.6 using python-build 20180424)

Inspect or clean up the working tree at /var/folders/q_/1rht_37j3vx9mwvq4wj48gd00000gn/T/python-build.20190115160947.69139
Results logged to /var/folders/q_/1rht_37j3vx9mwvq4wj48gd00000gn/T/python-build.20190115160947.69139.log

Last 10 log lines:
rm -f /Users/snowcrash/.pyenv/versions/2.7.14/share/man/man1/python.1
(cd /Users/snowcrash/.pyenv/versions/2.7.14/share/man/man1; ln -s python2.1 python.1)
if test "xno" != "xno"  ; then \
		case no in \
			upgrade) ensurepip="--upgrade" ;; \
			install|*) ensurepip="" ;; \
		esac; \
		 ./python.exe -E -m ensurepip \
			$ensurepip --root=/ ; \
	fi


 

Leave a Reply

Your email address will not be published. Required fields are marked *