Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'll be that person

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:

  >>> a, b = (1, 2)
  >>> a
  1
  >>> b
  2

  >>> a, b = 1, 2
  >>> a
  1
  >>> b
  2

2. Division by zero:

  >>> 1 / 0
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  ZeroDivisionError: division by zero

Though this one is interesting:

4. Using the is operator:

  >>> x = 256
  >>> y = 256
  >>> x is y
  True

  >>> x = 257
  >>> y = 257
  >>> x is y
  False


See https://github.com/satwikkansal/wtfpython

"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.


Sadly the core team decided to not learn from that mistake, and did the exact same thing with the dict merge operator.


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".


That strikes me as a turbocharged version of the sunk cost fallacy.


(4) is because Python interns small integers and the "is" operator tests identity, not equivalence. Any Python linter will catch that mistake.

Python does not coerce types, so it doesn't have very many wats like the ones in this talk.


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)`

    >>> dis.dis('y = 257+1; print(y is 258)')
  1           0 LOAD_CONST               0 (258)
              2 STORE_NAME               0 (y)
              4 LOAD_NAME                1 (print)
              6 LOAD_NAME                0 (y)
              8 LOAD_CONST               0 (258)
             10 IS_OP                    0
             12 CALL_FUNCTION            1
             14 POP_TOP
             16 LOAD_CONST               1 (None)
             18 RETURN_VALUE


In Python 2 you can redefine the True and False “constants”, which is pretty WAT-y though admittedly tampering.

  > True = False
  > if not True: print “can’t happen”
    can’t happen


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


There are quite a few assignable keywords in modern JavaScript. This is actually cromulent JavaScript:

    of = "wat";
    for (of of of) of


I think the only reason is because it is not specifically prohibited.


Heh I think that’s the reason for JS undefined too


My favorite python quirk is this expression:

    -1/3 == -(1/3)
Where I would ask people what it evaluates to, and give them the choices: "True", "False", or "Python 2 or Python 3?"


When would this evaluate to False ?


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.


> because the default value for the target_list parameter is mutable, it gets shared between function calls

bit of a weird way to phrase it, I think they're always shared between function calls, you just don't notice when they're not mutable.


That is correct, default values are initialised at compilation time and stored in the function object.


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.


The whole function just makes no sense, it's just `target_list.append(value)` with extra steps.


You're missing the point if you're looking at how "useful" this example function is


No, the example is just not good.


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.

Hint: list.append(value) returns None


`[] + []` is unlikely to happen except by accident if you don't know that's how array concatenation is done in JavaScript.

But wanting to provide default values of [] is quite common in Python. Seeing this workaround is quite common in Python:

    def func(arg=None): 
        if arg is None: 
            arg = [] 
        ...


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 Wat part is this:

    print(list1)  # [1, 2]
    print(list2)  # [1, 2]
Some would expect it to be:

    print(list1)  # [1]
    print(list2)  # [2]
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:

    function addToList(value, targetList=[]) { 
      targetList.push(value); 
      return targetList; 
    } 
 
    const list1 = addToList(1); 
    const list2 = addToList(2); 
    console.log(list1); // [1] 
    console.log(list2); // [2]


You'll have better results with https://github.com/satwikkansal/wtfpython


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.


The first example here is one common gotcha I know of: https://docs.python-guide.org/writing/gotchas/

(Didn’t read the rest, but based on the title it may also be relevant)




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

Search: