GitXplorerGitXplorer
p

python-horror-show

public
278 stars
14 forks
3 issues

Commits

List of commits on branch master.
Verified
a18fd1eb8f001d3b7393e6ebc67c26b45ec6abef

Merge pull request #2 from noviluni/master

ppablogsal committed 6 years ago
Verified
8f76ac720aa0eb3ca1bf6145baacd8ad7c6b5d47

Update README.md

nnoviluni committed 6 years ago
Verified
c4da36c134ffb7540a9d2ec93dcc3d5e1dea9a90

Merge pull request #1 from stsewd/patch-1

ppablogsal committed 6 years ago
Verified
d203969af7bc7673d15627e64b908f101d04d1ca

Update link

sstsewd committed 6 years ago
Unverified
57e4c4c3c1eaaf9be4f05fb7424c9987a14ffe1b

Moved snippets to other repo to keep this clean

ppablogsal committed 9 years ago
Unverified
0d6a10dc0ad2cff86cc23483416b0343cbb308db

Added new snippets about generators and recursive algorithms

ppablogsal committed 9 years ago

README

The README file for this repository.

Python's horror show

Here you will find a collection of strange and odd python snippets showing apparent odd behavior. The purpose of these scripts is to mess with your head but some people have reported strange new Python knowledge as a secondary effect.

Hidden memory things

>>> a = 5
>>> b = 5
>>> a is b
True

>>> a = -4
>>> b = -4
>>> a is b  
True

>>> a = 300
>>> b = 300
>>> a is b
False

>>> a = 300; b = 300
>>> a is b
True

From "Integer Objects":

The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behaviour of Python in this case is undefined. :-)

We can check this thing using the id operator:

>>> a = 5
>>> b = 5
>>> id(a)
4531116864
>>> id(b)
4531116864

>>> a = 300
>>> b = 300
>>> id(a)
4537522896
>>> id(b)
4537523216

>>> a = 300; b = 300
>>> id(a)
4537523696
>>> id(b)
4537523696

The brief explanation is that as in Python everything is an object, each time you use a number (integer, float...) it must be created so this could be very inefficient. So what Python does is pre-allocate integers from -5 to 256 because these are often used. The last trick (a = 300; b = 300) is interpreter-dependent, but in the basic Python interpreter (among others) as the two assignations occur in the same line both variables will refer to the same object to avoid wasting space.

Indexes for noobs

>>> a = [1,2]
>>> a.index(1)
0
>>> a.index(2)
1
>>> a[a.index(1)]
1
>>> a[a.index(2)]
2

>>> a[a.index(1)],a[a.index(2)] = 2,1
>>> a
[1, 2]

Woaaaa! WTF is happening here? Easy: we are forgetting that everything must be evaluated sequentially. Let's check this again but one statement after another:

>>> a = [1,2]

>>> a[a.index(1)] = 2
>>> a
[2, 2]
>>> a.index(2)
0
>>> a[a.index(2)] = 1
>>> a
[1, 2]

Aha! So the thing is that when we assign a[a.index(1)] = 2 as a.index(2) will give us the first index in which 2 appears it gives 1 and therefore a[a.index(2)] = 1 will reset a to its initial value.

Too many equals

This is one of my favourites:

>>> a, b = a[b] = {},5
>>> a
{5: ({...}, 5)}
>>> b
5

Hummmmmm.... I think the problem with this is that as soon as we see the double equal we think in the formal propositional logic implications of the statement. But as we have seen already, things must be evaluated sequentially. In this case the rules are two:

  1. Left before right
  2. a = b = c is sugar for a=c & b =c

So the only thing we have to do in order to undo this mess is execute the code following this rules as

>>> a, b = {},5
>>> a[b] = a,5 # As a is an empty dic and b=5 this is equivalent to {}[5] = ({},5)
>>> a
{5: ({...}, 5)}
>>> b
5

Hashable objects

>>> d = {1: 'a', True: 'b', 1.0: 'c'}
>>> d
{1: 'c'}

And... what’s happening here? The answer has already been given: Things must be evaluated sequentially.

>>> d = dict()
>>> d[1] =a>>> d[True] =b>>> d[1.0] =c>>> d
{1: 'c'}

Ok, but ¿why True is evaluated as 1?

>>> 1 is True
False

>>> 1 == True
True

The reason: any two strings, numbers, etc. that equate will have the same hash, allowing the dict (implemented as a hashmap) to find those strings very efficiently. dict keys are equaled on value rather on identity.

In fact, that’s the same reason why, as a general rule, we can’t use mutable objects as key in a dictionary: the hash of a list or another mutable object is not based on it's value, but rather the instance of the list, and changes when its content changes.

The same approach is true when creating sets:

>>> s = {1, True, 1.0}
>>> s
{1}

or using another values as 0 and False:

>>> d2 = {0: 'a', False: 'b'}
>>> d2
{0: 'b'}

Enter the void

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

When converted too a bool type, [] decay into False because it's empty, and [[]] becomes True since it's not empty. Therefore all([[]]) is equivalent to all([False]), and all([[[]]]) is the same as all([True]). As in all([]) there is no False then is trivially True.

Consumed by the iter method

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

Unlike sorted which returns a list, reversed returns an iterator. Iterators compare equal to themselves, but not to other iterators that contain the same values.

>>> b = reversed(a)
>>> sorted(b) == sorted(b)
False

The iterator b is consumed by the first sorted call. Calling sorted(b) once b is consumed simply returns [].

False is the new True

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

Neither the == nor the in happens first. They're both comparison operators, with the same precedence, so they're chained. The line is equivalent to False == False and False in [False], which is True.

Return to childhood

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

The value of x can be exactly represented by a Python int, but not by a Python float, which has 52 bits of precision. When x is converted from int to a float, it needs to be rounded to a nearby value. According to the rounding rules, that nearby value is x - 1, which can be represented by a float.

When x + 1.0 is evaluated, x is first converted to a float in order to perform the addition. This makes its value x - 1. Then 1.0 is added. This brings the value back up to x, but since the result is a float, it is again rounded down to x - 1.

Next the comparison happens. This is where Python differs from many other languages. In C, for instance, if a double is compared to an int, the int is first converted to a double. In this case, that would mean the right-hand side would also be rounded to x - 1, the two sides would be equal, and the < comparison would be false. Python, however, has special logic to handle comparison between floats and ints, and it's able to correctly determine that a float with a value of x - 1 is less than an int with a value of x.