You might ask "what is a wat?" wats are not trick questions wats are not bugs in the language A 'wat' is an edge-case in a language that makes you say: wat‽ Mind-Bending Edge Cases (in Python)
they make sense we're going to look at ten different wats but to make things interesting, a few are actually impossible and you'll need to decide if they're real or not wat‽
possible or not If it is possible, what can we replace the ellipsis with To give us the desired result The missing values in these wats are limited to the built-in primitive types and collections booleans, integers, strings as well as collections like lists and sets, and combinations of these No lambdas, partials, classes, or other tricky wat #0 >>> x = ... >>> x == x False
nan is not equal to itself, or anything else It's because it's not a number! NaN is designed to propagate through all calculations, so if somewhere in your deep, complex calculations you hit upon a NaN, you don't bubble out a seemingly sensible answer. So because of this, NaN is definitely the source of many wats NaN
which is definitely inspiration for this talk A brief word on NaN This is not Python: > Array(16).join("wat" - 1) + " Batman!" "NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN Batman!" https://www.destroyallsoftware.com/talks/wat
NaN in Python None of them are equal to themselves For the purposes of this talk, none of the "solutions" to the wats involve using a NaN A brief word on NaN >>> 0*1e309 nan >>> float('nan') nan >>> from decimal import Decimal; Decimal('nan') Decimal('NaN') >>> complex('nan') (nan+0j)
with some subset of the list no matter how you slice it wat #1 - Not Possible >>> x = ... >>> a = ... >>> b = ... >>> c = ... >>> max(x) < max(x[a:b:c]) True
function might look like If we implemented it in python wat #2 - Possible! >>> min({0}, {1}) set([0]) >>> min({1}, {0}) set([1]) >>> min({0, 1}, {0}) set([0])
less than minitem wat #2 - Possible! >>> def min(*args): ... has_item = False ... min_item = None ... for x in args: ... if not has_item or x < min_item: ...
wat #2 - Possible! >>> def min(*args): ... has_item = False ... min_item = None ... for x in args: ... if not has_item or x < min_item: ... has_item = True ... min_item = x ...
really simplified approach -- doesn't handle no args, etc. What's the key? It's that less than operator comparing x to min_item wat #2 - Possible! >>> def min(*args): ... has_item = False ... min_item = None ... for x in args: ... if not has_item or x < min_item: ... has_item = True ... min_item = x ... return min_item ...
at least one element in x is true But when appended with y, none of them are true? This wat is impossible wat #3 >>> x = ... >>> y = ... >>> any(x) and not any(x + y) True
effect on those in x If anything in x is true, something in x+y will be true as well wat #3 - Not Possible >>> x = ... >>> y = ... >>> any(x) and not any(x + y) True
an empty list of indexes wat #4 - Possible! >>> def count(s='foo', sub='foobar'): ... result = 0 ... for i in range(len(s) + 1 - len(sub)): ... # range(3 + 1 - 6) ... # range(-2) ... # []
as our possible match wat #4 - Possible! >>> def count(s, sub): ... result = 0 ... for i in range(len(s) + 1 - len(sub)): ... possible_match = s[i:i + len(sub)] ...
substring We increment the result counter wat #4 - Possible! >>> def count(s, sub): ... result = 0 ... for i in range(len(s) + 1 - len(sub)): ... possible_match = s[i:i + len(sub)] ... if possible_match == sub: ... result += 1 ... return result ...
through 6, for seven total And the possible_match matches every time! Giving us a count of 7 wat #4 - Possible! >>> def count(s, sub): ... result = 0 ... for i in range(len(s) + 1 - len(sub)): ... possible_match = s[i:i + len(sub)] ... if possible_match == sub: ... result += 1 ... return result ... >>> count('foobar', '') 7
which yields an empty sequence of the same type as s. in this case, an empty list wat #5 - Possible! >>> x = [0] >>> y = -1 >>> z = -1 >>> x * (y * z) == (x * y) * z False >>> x * (y * z) == [0]*(-1*-1) == [0]*1 == [0] True >>> (x * y) * z == ([0]*-1)*-1 == []*-1
to the way floats are, by their nature, imprecise basically the two results are off by the difference of the least significant bit wat #5 - Possible! >>> x = 5e-234 >>> y = 3 >>> z = 9007199254740993 >>> x * (y * z) == (x * y) * z False >>> x * (y * z) 1.335044315104321e-307 >>> (x * y) * z 1.3350443151043208e-307
x < y But when we compare each individual element, every element in x > y This wat is possible wat #6 >>> x = ... >>> y = ... >>> x < y and all(a >= b for a, b in zip(x, y)) True
before 'dogfish' Empty list comes before non empty lists wat #6 - Possible! >>> x = '' >>> y = 'foobar' >>> x < y and all(a >= b for a, b in zip(x, y)) True >>> '' < 'foobar' True
of the shorter length wat #6 - Possible! >>> x = '' >>> y = 'foobar' >>> x < y and all(a >= b for a, b in zip(x, y)) True >>> '' < 'foobar' True >>> zip('', 'foobar') []
if anything is false return false, otherwise true wat #6 - Possible! >>> x = '' >>> y = 'foobar' >>> x < y and all(a >= b for a, b in zip(x, y)) True >>> '' < 'foobar' True >>> zip('', 'foobar') [] >>> all([]) True
set might reduce the length of x But converting a set to a list will never add elementes wat #7 - Not Possible >>> x = ... >>> len(set(list(x))) == len(list(set(x))) False
series of arguments Or just an iterable? This wat exists because of the different ways min handles its args wat #8 - Possible! >>> x = [[0]] >>> min(x) == min(*x) False
performs a substring search wat #10 - Possible! >>> x = 'aa' >>> y = 'aa' >>> y > max(x) and y in x True >>> max('aa') 'a' >>> 'aa' > 'a' True >>> 'aa' in 'aa' True
almost guarantee that you'll never encounter these in the wild In fact, I've been writing Python for a long time, and I've only seen one of these The only reason I have them to share with you is because a smart fellow named Christopher Night collected a bunch of them together in a repo somewhere Which is why I must implore you, please One last thing...