Git Product home page Git Product logo

pywat's Introduction

Python wats

A "wat" is what I call a snippet of code that demonstrates a counterintuitive edge case of a programming language. (The name comes from this excellent talk by Gary Bernhardt.) If you're not familiar with the language, you might conclude that it's poorly designed when you see a wat. Often, more context about the language design will make the wat seem reasonable, or at least justified.

Wats are funny, and learning about a language's edge cases probably helps you with that language, but I don't think you should judge a language based on its wats. (It's perfectly fine to judge a language, of course, as long as it's an informed judgment, based on whether the language makes it difficult for its developers to write error-free code, not based on artificial, funny one-liners.) This is the point I want to prove to Python developers.

If you're a Python developer reading these, you're likely to feel a desire to put them into context with more information about how the language works. You're likely to say "Actually that makes perfect sense if you consider how Python handles X", or "Yes that's a little weird but in practice it's not an issue." And I completely agree. Python is a well designed language, and these wats do not reflect badly on it. You shouldn't judge a language by its wats.

The Python wat quiz

Are you unimpressed by these wats? Do you think these edge cases are actually completely intuitive? Try your hand at the Python wat quiz!

The wats

The undocumented converse implication operator

>>> False ** False == True
True
>>> False ** True == False
True
>>> True ** False == True
True
>>> True ** True == True
True

Mixing numerical types

>>> x = (1 << 53) + 1
>>> x + 1.0 < x
True

Think this is just floats being weird? Floats are involved, but it's not their fault this time. Check out the explanation to see why.

Operator precedence?

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

Source.

Iterable types in comparisons

>>> a = [0, 0]
>>> (x, y) = a
>>> (x, y) == a
False
>>> [1,2,3] == sorted([1,2,3])
True
>>> (1,2,3) == sorted((1,2,3))
False

See the explanation if you think this is not a wat.

Types of arithmetic operations

The type of an arithmetic operation cannot be predicted from the type of the operands alone. You also need to know their value.

>>> type(1) == type(-1)
True
>>> 1 ** 1 == 1 ** -1
True
>>> type(1 ** 1) == type(1 ** -1)
False

Fun with iterators

>>> a = 2, 1, 3
>>> sorted(a) == sorted(a)
True
>>> reversed(a) == reversed(a)
False
>>> b = reversed(a)
>>> sorted(b) == sorted(b)
False

Circular types

>>> isinstance(object, type)
True
>>> isinstance(type, object)
True

Source.

extend vs +=

>>> a = ([],)
>>> a[0].extend([1])
>>> a[0]
[1]
>>> a[0] += [2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> a[0]
[1, 2]

Indexing with floats

>>> [4][0]
4
>>> [4][0.0]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: list indices must be integers, not float
>>> {0:4}[0]
4
>>> {0:4}[0.0]
4

all and emptiness

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

sum and strings

>>> sum("")
0
>>> sum("", ())
()
>>> sum("", [])
[]
>>> sum("", {})
{}
>>> sum("", "")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: sum() can't sum strings [use ''.join(seq) instead]

NaN-related wats

>>> x = float("nan")
>>> y = float("nan")
>>> x == x
False
>>> [x] == [x]
True
>>> [x] == [y]
False

Source.

>>> x = float("nan")
>>> len({x, x, float(x), float(x), float("nan"), float("nan")})
3
>>> len({x, float(x), float("nan")})
2
>>> x = float("nan")
>>> (2 * x).imag
0.0
>>> (x + x).imag
0.0
>>> (2 * complex(x)).imag
nan
>>> (complex(x) + complex(x)).imag
0.0

Explanations

Want to learn more about the inner workings behind these wats? Check out the explanation page for clarity.

pywat's People

Contributors

cosmologicon avatar moreati avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pywat's Issues

"Fun with iterators" section is not a WAT

The sorted function returns a list instead of an iterator. It's explicitly written in the documentation and therefore explains the behavior (you can compare lists structurally, but you can't do that obviously with the iterators without iterating them).

Iterable types in comparisons

You're trying to compare different types. It is obvious that a list and a tuple are completely different objects.

>>> a = [0, 0]  # a is a list
>>> (x, y) = a  # unpacking a list; (x, y) is a tuple
>>> (x, y) == a  # comparing a tuple to a list 
False

Proof:

>>> a = [0, 0]
>>> type(a)
<class 'list'>
>>> (x, y) = a
>>> type((x, y))
<class 'tuple'>

sorted() always returns a list.

>>> type((1, 2, 3))
<class 'tuple'>
>>> type(sorted((1, 2, 3)))
<class 'list'>

Type mixing wat is not a wat

Sorry to bother you, but there is no type mixing here:

>>> int(2 * 3)
6
>>> int(2 * '3')
33
>>> int('2' * 3)
222

3 * '2' is a shorthand for '2' + '2' + '2':

>>> 3 * '2'
'222'
>>> '2' + '2' + '2'
'222'

You can't actually mix types in Python like you do in PHP. This is a magic type mixing:

$foo = "0";  // $foo is a string
$foo += 2;   // $foo is now an integer (2)

This is not:

>>> foo = '0'
>>> foo += 2
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly

There is no implicit str<->int type conversion in Python. When you do int('222') you get integer 222, which is exactly what int(str) intended to do.

It's not so confusing when you write it in more than one line:

>>> times = 4
>>> substring = '5'
>>> full_string = substring * times
>>> integer = int(full_string)
>>> integer
5555

I think this "wat" is not "wat enaugh", so it should be removed.

Thank you.

Converse implication operator

Right now there's a section titled:

The undocumented converse implication operator

In case it helps clarify things, I feel it makes a lot more sense if you think of False and True implemented as 0 and 1 underneath, and ** is a power operator.

>>> True == 1
True
>>> False == 0
True
>>> 0 ** 0 == True
True
>>> 0 ** 1 == False
True
>>> 1 ** 0 == True
True
>>> 1 ** 1 == True
True

Whether that is in and of itself a wat is up to you. :)

fun with mappings

Know thine equalities

>>> d = {1: int, 1.0: float, True: bool}
>>> d[1.0]
<class 'bool'>
>>> 1+0j in d
True

especially when intransitive

>>> from collections import OrderedDict
>>> d = dict([(0, 1), (2, 3)])
>>> od = OrderedDict([(0, 1), (2, 3)])
>>> od2 = OrderedDict([(2, 3), (0, 1)])
>>> d == od
True
>>> d == od2
True
>>> od == od2
False
>>> class MyDict(dict):
...   __hash__ = lambda self: 0
...
>>> class MyOrderedDict(OrderedDict):
...   __hash__ = lambda self: 0
...
>>> d = MyDict([(0, 1), (2, 3)])
>>> od = MyOrderedDict([(0, 1), (2, 3)])
>>> od2 = MyOrderedDict([(2, 3), (0, 1)])
>>> len({d, od, od2})
1
>>> len({od, od2, d})
2

❤️ ❤️ ❤️

Question 5: zero sum is "possible" ?

So we have that question

>>> x, y = ???
>>> sum(0 * x, y) == y
False

if x equal one of

[], '' or ()

and

y = float('nan')

that snippet is possible.
For ex:

>>> x, y = '', float('nan')
>>> sum(0 * x, y) == y
False

Please correct me if I'm wrong.

Question 2

Snippet is possible
x = iter(range(10))

Out of scope? Copy/pasteable functions in standard library... documentation!

The standard library contains copy/pasteable functions in the comments, instead of just including them in the standard library. wat?

https://docs.python.org/3/library/itertools.html#itertools-recipes

Same for unicode support in the csv module, but maybe not fair since thats fixed in Python 3. Here goes anyway:

https://docs.python.org/2/library/csv.html

Not sure if these are out of scope - they're not in the language itself, but the standard library is an important part.

Other wats suggestions

First: thanks for this list, and the amazingly difficult AND fun quiz !

Now, a few suggestions to add to your collection of gotchas:

[] = () # OK
() = [] # KO

bool(datetime.time()) is False # cf. "a false midnight" http://lwn.net/Articles/590299/

x = 256
y = 256
assert (x is y) is True
x = 257
y = 257
assert (x is y) is False
x = 257; y = 257
assert (x is y) is True
# cf. http://stackoverflow.com/q/306313/636849

i=0;a=[0,0]
i, a[i] = 1, 10
print i, a  # 1, [0,10] - cf. https://news.ycombinator.com/item?id=8091943 - Original blog post from http://jw2013.github.io

def create_multipliers(n):
    return [lambda x : i * x for i in range(1,n+1)]
for multiplier in create_multipliers(2):
    print multiplier(3) # Late Binding Closure : prints 6 twice

issubclass(list, object) # True
issubclass(object, collections.Hashable) # True
issubclass(list, collections.Hashable) # False - There are 1449 such triplets (4.3% of all eligible triplets) in Python 2.7.3 std lib

And I found out about those last 3 myself:

d = {'a':42}
print type(d.keys()[0]) # str
class A(str): pass
a = A('a')
d[a] = 42
print d # {'a':42}
print type(d.keys()[0]) # str

f = 100 * -0.016462635 / -0.5487545  # observed on the field, in a real-world situation
print 'float     f:', f              # 3.0
print 'int       f:', int(f)         # 2

class O(object): pass
O() == O()             # False
O() is O()             # False
hash(O()) == hash(O()) # True !
id(O()) == id(O())     # True !!!
# ANSWER: http://stackoverflow.com/a/3877275/636849

Iterable types in comparisons is wrong

>>> a = [0, 0]
>>> (x, y) = a
>>> (x, y) == a
False
>>> type(a)
list
>>> type((x, y))
tuple

>>> [1,2,3] == sorted([1,2,3])
True
>>> (1,2,3) == sorted((1,2,3))
False
>>> type(sorted((1, 2, 3))
list

You are comparing different data types, that makes it unequal.

>>> a = (0, 0)
>>> (x, y) = a
>>> (x, y) == a
True

>>> [1,2,3] == sorted([1,2,3])
True
>>> [1,2,3] == sorted((1,2,3))
True
>>> (1,2,3) == tuple(sorted((1,2,3)))
True

Indexing with floats - not really an index

I get how this one can seem weird to newcomers, but it's because dictionaries don't use indices.

>>> {1:4}[0]

Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    {1:4}[0]
KeyError: 0

May be nit-picky, but I don't think indices and keys not being the same thing is a "wat."

This "wat" could be better shown as how two equivalent values are not equivalent in use:

>>> 0 == 0.0
True
>>> [4][0]
4
>>> [4][0.0]

Traceback (most recent call last):
  File "<pyshell#34>", line 1, in <module>
    [4][0.0]
TypeError: list indices must be integers, not float

Converting to a string and back wat is not a wat

String is considered false when it is empty, so as other sequences (lists, tuples, sets, etc.). It's described in right here. str(False) is 'False'. 'False' is non-empty string, so bool('False') is True, same as bool('any other non-empty string'). Seems reasonable to me.

Translate to pt-br

First of all, your repository is amazing! So much information and knowledge about the Python wats.

So can I fork and translate it to Portuguese? This will help people that doesn't know English so much.

all and emptiness

This is as expected. all takes in one iterable argument. By each case:

  1. True because the iterable is empty and therefore all (0) statements within the iterable are True (trivially).
  2. False because the only item in the iterable, an empty list, is falsy.
  3. True because the only item in the iterable, a list with one item in it, is thruthy.

The undocumented converse implication operator wat is not a wat

** is a power operator. The power operator works with numbers, so all booleans seems to be converted to integers (True to 1, False to 0) and then the power operation on them is calculated. Check this out:

>>> False ** False == True
True

But:

>>> False ** False is True
False

Whoopsie :) Seems like the result of the expression does not return the boolean True. Let's look closely:

>>> False ** False
1

Hmm. Let's check the actual type:

>>> type(False ** False)
<class 'int'>

Nope, no booleans here.

This issue needs some math to be recalled. It's commonly known that any number raised to the power of 0 is equal to 1. Also any number raised to the power of 1 is equal to itself. Let's apply this rule to the examples from README.md.

>>> False ** False
1
>>> 0 ** 0
1

Seems legit. 0 raised to power of 0 is equal to 1, which is also equal to True in Python.

>>> False ** True
0
>>> 0 ** 1
0

0 raised to power of 1 is equal to itself, which is also equal to False in Python.

Two more to go:

>>> True ** False
1
>>> 1 ** 0
1

1 raised to power of 0 is equal to 1, which is also equal to True in Python.

>>> True ** True
1
>>> 1 ** 1
1

1 raised to power of 1 is equal to 1, which is also equal to True in Python.

No magic here. The power operator is yielding results, which seems to be similar to the results of the converse implication on certain subset of input values. Point me if I am wrong.

Minor: 0^0 is not 0 (mathematically) - explanation.md

In your "inverse implication" operator rationale, you say 0^0 is equal to 1.
That's not what I've learned. It's actually an undefined symbol:
https://www.wolframalpha.com/input/?i=0%5E0
(like 0 * Inf) and so on. So for floats this would be NaN.

However, 1 is the limit of n^n as n->0:
https://www.wolframalpha.com/input/?i=lim+n%5En+as+n-%3E0

Given that ints can't represent NaN it was probably proper to return something, in this case the limit value. But saying that the value of 0^0 is actually 1 is like saying 0 / 0 == 1 - it's also the lim(n->0), but for that Python actually returns:

Python 3.7.3 (default, Mar 27 2019, 22:11:17) 
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 0 // 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

Scope

Here's one that blew my mind (and my co-workers):

def incrementer():
    i = 0
    def increment():
        i = i + 1
        return i
    return increment

Looks OK. Let's try it.

>>> i = incrementer()
>>> i()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-3-fa1b63ea38ec> in <module>()
----> 1 i()

<ipython-input-1-482b5332e967> in increment()
      2         i = 0
      3         def increment():
----> 4                 i = i + 1
      5                 return i
      6         return increment

UnboundLocalError: local variable 'i' referenced before assignment

Fuck, even JavaScript got this one right!

Mixing numerical types in other languages

Other languages that have floats and ints do not have this behavior

This might not be completely true, ruby:

irb(main):001:0> x = (1 << 53) + 1
=> 9007199254740993
irb(main):002:0> x + 1.0 < x
=> true

By simply trying to evaluate 9007199254740993 + 1.0 < 9007199254740993 in other languages, indeed I haven't been able to find another example... I tried for now:

  • Haskell (well, duh... you cannot compare int and fractionals in it)
  • Clojure (actually I tested the JVM floats, I suppose)
  • Perl5
  • NodeJS (Ok, ecmascript doesn't have integers, but I wanted to try nonetheless)

"It just happens to work out that the truth table is the same as for the converse implication operator."

This is not, in fact, an arbitrary coincidence; it follows naturally. If A and B are (finite) sets/types then the cardinality of the set/type of functions from A to B is |B|^|A|. If you use the Curry-Howard isomorphism and identify True and False as the unit and empty type respectively, then the function type A -> B is identified with logical implication, and we obtain the result from the original post.

Indexing with floats

This is not much of a wat. In the first case the example shows attempting to index a list at index 0.0, which does not make sense. In the second case, the example shows attempting to find the key 0.0 in a dictionary, which does make sense. The only wat here might be that Python coerces float to int if possible when using it in a dictionary key search.

Similarly:

{3:'foo'}[3.0]  # 'foo'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.