
Python wats - luu
https://github.com/cosmologicon/pywat
======
ak217
Of these, only += on tuple elements and indexing with floats strike me as
WTFs, and many others are evidence of the author really going out of their way
to look for behaviors to nitpick on. The absolute majority of these are things
that Python programmers either will never encounter, or can easily explain
from Python "first principles" (e.g. most of the nitpicks on string and
sequence behaviors).

I can tell you what I consider WTF worthy - things that I and others have
undoubtedly been bitten by because they are ubiquitous. Those are mutable
defaults, lack of loop scoping, and "global"/"nonlocal" scoping. Oh, and add
the crazy "ascii" I/O encoding default on 2.7 (and the inability to cleanly
reset it) to that list.

~~~
crdoconnor
Truthiness values are also WTF worthy (although not mentioned above).

    
    
        if not x:
            do_something()
    

Where do_something will occur if x is None, zero, 0.0, a blank string or even
_midnight_ in some versions of python.

This has bitten me multiple times. It's a violation of python's explicit over
implicit. It's an example of weak typing in an otherwise strongly typed
language. There's really no good reason for it.

IMO, if x isn't a boolean, that line should just throw an exception. If you
want to check for x being 0.0 or "", you should do x == "" or x == 0.0.

~~~
josegonzalez
Why not do `x == False` then? Arguing about a language not being as strongly
typed when you're not being as explicit about your own types seems pretty
roundabout.

~~~
crdoconnor
>Why not do `x == False` then

Because it's no more explicit than "not x" and because it's 5 characters
longer.

There's always a trade off to be made between clarity and verbosity.

Judging by the number of bugs I've squashed caused by the developer either not
realizing or simply forgetting that ("", 0, 0.0, midnight, False, [], {},
None) _all_ resolve to false, I've come to believe this is one of those times
where the clarity should probably take precedence.

You can go to the other extreme by making everything explicitly statically
typed which will make your code _much_ more verbose (e.g. see Java). I believe
that trade off isn't worth it either, despite the small increase in type
safety.

~~~
masklinn
> You can go to the other extreme by making everything explicitly statically
> typed which will make your code much more verbose (e.g. see Java).

Java is not an example of a good static typing implementation and is not
exemplar of verbosity in modern statically typed languages.

------
ris
I would argue that the vast majority of behaviours described here are
precisely the desired behaviour.

In fact, I think the ones he picks reflect tremendously on his skill as a
programmer.

Take for instance his apparent insistence that int(2 * '3') should produce 6.
Cool. Magic "special" behaviour for the multiply operator if the string
happens to contain a number.

The same goes for his desire for the string "False" to be falsey. Wonderful
magic special behaviour dependent on the (possibly unknown) content of a
string means I've got to go round checking my strings for magic values.

~~~
d23
I agree, but there's enough weird shit going on here that it's still a
worthwhile "article". For me, the `mixing numerical types`, `Operator
precedence?`, `extend vs +=`, `Indexing with floats`, `all and emptiness`, and
`Comparing NaNs` were all interesting.

------
polemic
I don't find many of these wat-worthy.

In python 2.x you could redefine True and False, and this is fun:

    
    
        >>> object() < object()
        True
        >>> object() < object()
        False
        >>> object() < object()
        True
    

But python3 dropped a lot of wat-ness.

This one is fun too:
[https://bugs.python.org/issue13936](https://bugs.python.org/issue13936)

------
yokohummer7
I interpret "wat" in the original "wat" presentation as "certain behaviors
that can't be predicted by reading the code". In this regard, I don't think
many things pointed out in the article can be considered as "wat". I could
predict the behavior of many items fairly easily. The same thing could not be
applied when I watched the original "wat" presentation.

The only exception I think is the bool coercion. Even though I _can_ predict
the behavior, I find it fairly disturbing. I believe that should have been
removed when transitioning to Python 3. It is very unfortunate, considering
Python is generally reluctant to implicit type conversion.

------
pvdebbe
Besides the mentioned debunks of wat, the

    
    
       all([[[]]]) == True
    

makes perfect sense, because the all all does is check the truthiness of the
argument's elements. An empty list is false, non-empty is true, no matter the
contents.

~~~
agf
By itself, yes, but

    
    
      >>> all([[[]]]) == True
      True
      >>> all([[]]) == True
      False
      >>> all([]) == True
      True
    

is pretty counter-intuitive at first glance.

~~~
pvdebbe
At first glance, yes. Needs a moment to think this through. But not a wat
because this works exactly as the logic says.

If we denote

    
    
      x = []
    

then

    
    
      bool(x) == False    # empty list is falsy
      bool([x]) == True   # nonempty list is truthy
      bool([[x]]) == True # nonempty again
    

and finally,

    
    
      [bool(elem) for elem in [[x]]] == [True]  # all True!
    

which is the thing `all' is interested in. It is more like a newbie mistake or
careless document reading if the user thinks `all' runs through the lists and
all of the nesting too.

------
jayflux
Can someone explain why the first one is a wat? To me it seems perfectly
logical

Cast false to a string, then check the Boolean value of that string. It's not
an empty string so of course it would be true.

~~~
lostmsu
Because people from normal langs or without cs background expect type cast
roundtrip to return original value if temp type can represent any value of
source type (e.g. string can represent any value of bool)

~~~
StavrosK
So someone would expect that an int cast of "2" would be 2? What would an int
cast of "a" be?

~~~
dalke
I'll add to that - In what "normal langs" is this not a problem?

What would a C or C++ programmer expect? Because my experience in those
languages says "casting" is a meaningless way to understand what's going on.

Perl doesn't have bareword true/falue values. Ruby doesn't use "casting", I
think. That is, I think the idiomatic way to express this in Ruby is:

    
    
      >> !!true.to_s
      => true
      >> !!false.to_s
      => true

------
ngoldbaum
I have a favorite NumPy wat:

    
    
       >>> import numpy as np
       >>> 'x'*np.float64(3.5)
       'xxx'
    

(on recent NumPy releases this raises a deprecation warning)

Clearly the correct answer is 'xxx>' ;)

~~~
aldanor
How about

>>> type(np.uint64(1) + 1) numpy.float64

------
jaibot
Okay, I don't get this one:

    
    
      >>> False == False in [False]
      True

~~~
julian37
Both "in" and "==" are comparison operators that can be chained and so it's
interpreted as

    
    
        False == False and False in [False]
    

[https://twitter.com/marcusaureliusf/status/55794887300903731...](https://twitter.com/marcusaureliusf/status/557948873009037312)

~~~
bpicolo
>>> 1 in [1] in [[1]]

True

~~~
ubernostrum
Again, chained comparisons. For any comparison operator (comparison operators
are <, >, ==, >=, <=, <>, !=, 'is', 'not', 'is not', 'in' and 'not in'):

    
    
        x (operator1) y (operator2) z
    

is defined by the language as equivalent to

    
    
        (x (operator1) y) and (y (operator2) z)
    

So

    
    
        1 in [1] in [[1]]
    

is equivalent to

    
    
        (1 in [1]) and ([1] in [[1]])
    

For any comparison operator

~~~
tcdent
Though, according to the docs, `y` in your first example is only evaluated
once when using the shorthand chained method.

------
hueving
The very first example is stupid. What sane language would assume a non-empty
string should be converted to a False boolean?

~~~
msane
isn't it doing the opposite? str(False) is the string "False", which bool()
casts to True because it is a non-empty string; Non-empty strings are cast to
True booleans.

This is much faster than checking if the string has some semantic meaning
first, which would have to be localized.

~~~
ketralnis
That's exactly what the grandparent is saying

------
agf
I thought this was going to be about the "Investigating Python Wats" talk Amy
Hanlon (my co-worker) gave at PyCon last year:
[https://www.youtube.com/watch?v=sH4XF6pKKmk](https://www.youtube.com/watch?v=sH4XF6pKKmk)

~~~
rhizome31
Excellent talk. Her use of the dis module to explain wats by showing bytecode
was particularly enlightening. It inspired me to give a similar talk next week
end at PyConFr: [https://github.com/makinacorpus/makina-
slides/blob/master/py...](https://github.com/makinacorpus/makina-
slides/blob/master/python/bizarreries/index.md) (in French but the meat of it
is Python code).

------
nv-vn
It seems like these all are exclusive to dynamically/weakly typed languages.
Anyone have examples of wats for languages with strong static typing?

~~~
Macha
Java:

* Reassigning interned integers via reflection

* private fields work on a class level, not an instance level, i.e. Instances of a class can read private fields of other instances of the same class.

* Arguably package-private fields as the docs like to avoid mentioning they exist.

* ==, particularly in regards to boxed types.

* List<String> x = new ArrayList<String>() {{ add("hello"); add("world"); }};

* The behaviour of .equals with the object you create above

* Type Erasure

~~~
dlwh
to add another:

    
    
        System.out.println(0.0 == -0.0); // true
        System.out.println(java.util.Arrays.equals(new double[] {0.0}, new double[] {-0.0})); // false
    

(It's documented in the contract for Arrays.equals, but still kind of
ridiculous.)

------
746F7475
To me the first two: "Converting to a string and back" and "Mixing integers
with strings" aren't "wats" at all.

------
lqdc13
actually almost none of these seem like wats to me.. Just what I would expect.

The one wat was adding a float to a huge integer. Granted, I assumed something
fishy might happen with such large numbers, so I steer away from doing those
operations.

~~~
khulat
Well to be fair it is only a wat if you don't know about floating point
imprecision. Because that is exactly what is causing this contrary to what is
stated. Also python just gets this behavior from C, so the other claim that
this is a special case of python is also not true.

Since the 1 is shifted 53 bits to the left the number is right at the start of
the number range where only even numbers can be represented.

You get 900719925470993 as the decimal number which you cast to float :
900719925470992.0

Then you add 1.0 and get 900719925470992.0 because of floating point
imprecision and rounding. The next floating point number would be
900719925470994.0.

92 is less than 93 and this gets you this seemingly weird x+1.0 < x = true.

~~~
Veedrac
It's still unfortunate that `x + 1.0` rounds incorrectly. Python is one of the
few languages that has correct (infinite-precision) float-int comparisons, so
it's reasonable to expect addition to work similarly.

That said, I'm not aware of a language that does better, and I'm aware of many
that do much worse.

~~~
renox
There is Frink
([http://futureboy.us/frinkdocs/](http://futureboy.us/frinkdocs/)) which has
interval arithmetic which seems so much more sane than floating point
numbers..

~~~
Veedrac
I looked at Frink a little; it doesn't seem to implement integer-floating
addition properly either.

    
    
        9999999999999999999002 + 49.0
    

rounds to 9.9999999999999999990e+21, whereas the lesser value

    
    
        9999999999999999999000 + 50.0
    

rounds to 9.9999999999999999991e+21.

Plus, I'm not a fan of computing with decimal arithmetic, since it's less
stable. For instance,

    
    
        min(a, b) <= (a + b) / 2 <= max(a, b)
    

doesn't always hold for decimal floats, whereas it does for (non-overflowing)
binary floats. Decimals are generally more prone to this kind of inaccuracy,
since they lose more bits when the exponent changes.

(Consider a = 1.00000000000000000001, b = 1.00000000000000000003.)

Interval arithmetic support is cool, but not useful without effort - bounds
like to grow. Plus, Python has bindings for them anyway ;).

------
eru
> all and emptiness [...]

If all would work any other way, it would be seriously broken. (Given the
rules for how to convert list to bool.)

------
ksikka
Anyone know the story behind the converse implication operator? What it's
useful for, and why it's undocumented?

~~~
johnsoft
∗∗ is the exponentiation operator. True is implicitly converted to 1, and
False to 0. It's a coincidence that 0^0, 0^1, 1^0, and 1^1 line up with
converse implication.

~~~
asgard1024
Maybe someone can explain better, but I don't think it's a coincidence.. In
category theory, there is a general notion of "exponential object". This works
like normal exponentiation in various categories of numbers, and like modus
ponens (and also reversed - given exponent and power, you can deduce the base)
in various categories of logic.

------
dllthomas
Working in Python, my biggest wat is that a test can cause a failed assertion
in the code and still succeed (if AssertionError is caught somewhere upwards
in the code, which will happen at any `except Exception`).

~~~
ketralnis
I've hit this too, it would be really nice if AssertError didn't inherit from
Exception. There's a whole Exception object hierarchy but it seems like none
of the parents of Exception are actually useable for anything

~~~
dllthomas
That would partially solve this problem (it's far rarer to see bare excepts or
`except BaseException`), but could cause others. The question there should be
what is (or should be) intended when someone says `except Exception` - it's
not clear to me whether that should or should not include AssertionError.

A more direct solution might be to have a means of storing off every assertion
failure into a (clearable) list. The test harness could then check that list
at the end of each test. If a particular test should succeed after triggering
an assertion failure, it can check the list to make sure it triggered exactly
the expected failures and then clear it.

~~~
ketralnis
> The question there should be what is (or should be) intended when someone
> says `except Exception` - it's not clear to me whether that should or should
> not include AssertionError

Right, I guess I'm not suggesting changing the language today to do this.
Rather, I'm suggesting that it would be better if it were already done that
way from the beginning. If it were designed that way from the start then there
would be no question about the intention because it would just be part of the
definition of Exception

~~~
dllthomas
_" If it were designed that way from the start then there would be no question
about the intention because it would just be part of the definition of
Exception"_

Heh, doesn't that argument apply currently? I think I was trying to ask, "when
someone fails to think deeply enough about it, what is likely to give the
'correct' results"?

------
amelius
I always run into trouble in Python with closures and assignment. E.g.,

    
    
      def f():
          x = 0
          def g():
              x += 1
          g()
      f()

~~~
Veedrac
Here's a quick interpretation of Python's method:

Any assignment operator (compound or not) follows the same namespace rules.
Scope lookups are consistent within a scope, regardless of order. Any
"namespaced" lookup (`x[y]` and `foo.bar`) will act inside that namespace.

Non-namespaced identifiers will always assign into the nearest enclosing scope
(a scope is always a named function) by default, since clobbering outer scopes
- particularly globals - is dangerous. You can opt in to clobbering with
`global` and `nonlocal`. If you don't ever assign to a name in the current
scope, lookups will be lexical (since a local lookup cannot succeed).

\---

Hopefully most of these value judgements should be relatively obvious.

------
fela
the behavior of is seems pretty hard to predict

    
    
      >>> 3.1 is 3.1
      True
      >>> a = 3.1
      >>> a is 3.1
      False

~~~
fela
It seems to be a case of leaky abstraction, where the way python caches small
objects becomes visible, another example:

    
    
      >>> a = 'hn'
      >>> a is 'hn'
      True
      >>> a = ''.join(['h', 'n'])
      >>> a is 'hn'
      False
      >>> a
      'hn'
      >>> a = 'h' + 'n'
      >>> a is 'hn'
      True
    

Edit: found another interesting case

    
    
      >>> a = 1
      >>> b = 1
      >>> a is b
      True
      >>> a = 500
      >>> b = 500
      >>> a is b
      False

~~~
odonnellryan
Second case is because of the range of integers that are cached, right?

~~~
Veedrac
It's actually more complicated than that. CPython will cache constants within
a scope, so

    
    
        a = 500
        b = 500
        a is b
    

will give `True`. But the REPL forces each line to compile separately and
CPython won't cache across them. However, there's a global cache for integers
less than 256 so in that case they're `is`-equivalent even in the REPL.

------
chris_wot
How are the converse implication operators worthy of a wat? That's completely
normal...

------
misiti3780
I thought I knew python .... I guess I do not know python ...

awesome idea - thank you

------
jakeogh
False as a valid fd: os.stat(False) (False == 0)

------
avinassh
Not sure if I can call this as `wat`, but this one puzzled me at first:

    
    
        foo = 1.0 + 2 # Foo is now 3.0
        foo = 1,0 + 2 # Foo is now a tuple: (1,2)
        foo = 3 # Foo is now 3
        foo = 3, # Foo is now a tuple: (3,)
    

source -
[https://wiki.theory.org/YourLanguageSucks#Python_sucks_becau...](https://wiki.theory.org/YourLanguageSucks#Python_sucks_because)

~~~
Retra
The ',' in always creates a tuple. Why is that puzzling?

