"Mutating the immutable" is my personal favourite - the second line of code fails with an exception despite having already succeeded:
>>> t = ([1, 2], 5, 6)
>>> t[0] += [3, 4]
TypeError: 'tuple' object does not support item assignment
>>> print(t)
([1, 2, 3, 4], 5, 6)
I've never been a fan of the decision to have `a += b` sometimes do `a = a + b` and sometimes update `a` in place. My objection was initially just due to theoretical inelegance - but this code shows that decision directly causing a concrete problem.
Bryce Adelstein Lelbach, the ISO C++ Library evolution chair, says that he would rather be consistently wrong than inconsistently right. Maybe that's what the Python core team thought, "we'd better make this consistent with +=, whose quirks people already know".
Python itself warns you nowadays too, it will print out
<dis>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
Some more weird/fun stuff about that: If you throw that code into one file instead of a repl, `x is 257` will be True. If you instead instantiate `x = 257` in a different file, and import it, `x is 257` be False again. In a repl if you run them on the same line it ends up as True!
>>> x = 257
>>> print(x is 257)
False
>>> y = 257; print(y is 257)
True
If they're in the same file (or same line in a repl), the integer literals are loaded into the `co_consts` object during the initial parse to bytecode, and so they refer to the same object. This only works for integer literals and simple statements that can be optimized to literals by python (e.g. it also works for `x = 257+1; print(x is 258)`
And here I was thinking it’s hilarious that JS `undefined` is assignable! I’m sure there’s good historical reason for this, but being able to change booleans to mean their opposite is something extra special to me
Apparently it's False in python2. Python2 does integer division that rounds towards negative infinity. It makes the order of operations important in -1/3. (-1)/3 (and hence -1/3) evaluates to -1, and -(1/3) evaluates to 0.
I gave it a try as well, and GPT-4 taught me something new about Python (I don't normally write Python, so it's not surprising):
def add_to_list(value, target_list=[]):
target_list.append(value)
return target_list
list1 = add_to_list(1)
list2 = add_to_list(2)
print(list1) # [1, 2]
print(list2) # [1, 2]
In this example, one might expect list1 to contain only 1 and list2 to contain only 2. However, because the default value for the target_list parameter is mutable, it gets shared between function calls, leading to unexpected behavior.
The other examples it gave me were not that unexpected. Prompt was:
> Show me examples of where Python type inference works in unexpected ways.
Some people might not know this yet, the common idiom is to have the default value be None, and then start your function with checking if it's None and initializing to empty list.
Ah, yeah, compared to the examples in the video that makes so much sense in a normal day to day application. I commonly do the following operations without much thought:
[] + []
{} + {}
[] + {}
{} + []
Great examples :)
The point is not what the data or naming of the functions/variables is, but what's happening when you run it...
[] + [] is more likely to happen than taking list.append(value) and defining a function that does list.append(value) and then returns whatever list.append(value) returns.
I'm talking about the following where we don't intend to do [] + [] but we feed some lists to some functions that do something to the lists and we end up with empty lists in some cases without inspecting the contents of a and b after we feed it to the functions.
Why would you define a default argument of an empty list if you're not gonna check if it's a list and if it's empty?
a = [1,2]
someFunction(a)
b = [3,4]
someOtherFunction(b)
a + b
The reason it's weird is because Python evaluates default arguments at function definition time rather that function call. This means that it's unsafe to use mutable values as default values in params. See the equivalent in JavaScript:
I remember encountering number 4 in the wild, so I think it deserves to be a genuine "wat". That said, I'm not sure why I (or anyone else) thought I would need to use the identity operator on numbers, but it's certainly a head-scratcher until you dig into the underlying reason.
Maybe such code is written by people scarred by JavaScript and trying to avoid ==. But not sure: I saw this in the wild as well — actually, the victim brought the broken function to chat scratching his head — and he was a quite experienced backend guy (and the experience wasn't in nodejs, AFAIK).
A pretty funny kind of bug, actually: it was some relatively simple function, passing the unit tests but doing some nonsense on real data. Thing is, it was comparing some sequential ids and working perfectly as long as there were no more than 256 objects.
I asked ChatGPT:
> Gary Berhnhardt gave a famous talk called "Wat" about Javascript. Can you create similar "Wat" style examples for Python?
The examples it gave me were... not that 'wat'-y
Sure, here are some "Wat"-style examples for Python:
1. Unpacking tuples:
2. Division by zero: Though this one is interesting:4. Using the is operator: