Python sadness

Some things I don't like about the Python programming language and libraries.

+= semantics differing from assignment

The += operator can be overridden. For the built-in lists, it's overridden to actually mutate instead of reassigning:

>>> a = []
>>> b = a
>>> b += [0]
>>> print(a)
[0]

Compare it with the assignment operator:

>>> a = []
>>> b = a
>>> b = b + [0]
>>> print(a)
[]

readline monkey patches raw_input

Importing the readline module changes the behavior of the raw_input function.

>>> import readline
>>> foo = raw_input()

Now you can use readline features (history etc) from the prompt. This causes action at a distance among two seemingly completely unrelated modules and can cause issues that are very hard to debug.

Assigning values to email.mime.text.MIMEText actually appends them

If you treat MIMEMessage as a dictionary and assign header values to its string indexes, the headers are actually appended instead of replaced.

>>> from email.mime.text import MIMEText
>>> msg = MIMEText("")
>>> msg["To"] = "foo@example.com"
>>> msg["To"] = "bar@example.com"
>>> msg.as_string()
'Content-Type: text/plain; charset="us-ascii"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\n
To: foo@example.com\nTo: bar@example.com\n\n'

This poses a serious privacy risk if the program assigns other customers' emails to the To field before picking the final one.

Ambiguous tuple in sounddevice

The library sounddevice has a way to denote the number of channels either combined for input and output, or for input and output separately. The way this is done is by passing either a single value or a tuple to the relevant function or constructor.

stream = sounddevice.Stream(channels=2) # two channels for input and (or?) output
stream = sounddevice.Stream(channels=(1,2)) # one channel for input, two for output

Because the value passed is a tuple, and there's no universal convention whether input or output comes first, the API user doesn't know which tuple element is input and which is output. The documentation doesn't explicitly state it either, so it's a guessing game.

Default parameters are evaluated when defining the function

You can define default parameters for functions like this:

>>> def function(param="default"):
>>>     print("Got param:", param)

>>> function()
Got param: default
>>> function("something else")
Got param: something else

The problem arises when you define a mutable default parameter:

>>> def function(param=[]):
>>>     param.append("item")
>>>     for item in param:
>>>         print(item)

>>> function()
item
>>> function()
item
item
>>> function()
item
item
item

The default parameter is evaluated only once, during function definition, and is shared among all calls to the function. The usual solution is to instead use an immutable value (eg. None) as the default parameter, and replace it during function execution.

>>> def function(param=None):
>>>     if param is None:
>>>         param = []
>>>     param.append("item")
>>>     for item in param:
>>>         print(item)

>>> function()
item
>>> function()
item
>>> function()
item