Here’s a catalogue of some fun “unintuitive” behaviour in Python.

Integer Memory Preallocation

256 is int("256")257 is int("257")

This is because Python preallocates the integers from -5 to 256 at initialisation, meaning they are always at the same address in memory, while integers beyond this range can have multiple different references.

Floating Point Weirdness

int((3.0 * 0.1 - 0.3) ** (-0.1))

This maps to an integer, despite the fact that (3×0.10.3)0.1=00.1(3 \times 0.1 - 0.3)^{-0.1} = 0^{-0.1} is undefined. Can you guess the integer?

In fact, it is forty-two (42). This is because 0.3 is not representable exactly in binary.

Lambda Arguments Are Captured at Definition Time

funcs = [(lambda: i) for i in range(3)][f() for f in funcs]# [2, 2, 2] rather than [0, 1, 2]

The i in the lambda definition references the variable, rather than its value at that point in time!

Default Arguments Can Be Mutated

def append_to_list(item, my_list=[]):    my_list.append(item)    return my_list# now run the function twiceappend_to_list(1)  # [1] as expectedappend_to_list(2)  # [1, 2] rather than [2]

The list my_list=[] in the function definition defines an empty list. Since lists are mutable, the first time we call the function, 1 is appended to this default list, and remains there indefinitely.

Attributes Have Their Name Mangled

class MyClass:    def __init__(self):        self.__private = 42# create a member of the classobj = MyClass()obj.__private# AttributeError: 'MyClass' object has no attribute '__private'obj._MyClass__private  # 42

When beginning attribute names with a double underscore, their names are mangled to mark them as private.

And / Or

'a' and 'b'  # 'b''a' or 'b'   # 'a'

In Python, left and right is actually equivalent to right if left else left: it returns the first false-like argument or the last true argument. Conversely, left or right is equivalent to left if left else right.

String Interning

a = "hello"b = "hello"a is b  # Truea = "hello world"b = "hello world"a is b  # False

As an optimisation technique, Python stores single words and other short strings in a string intern pool to re-use their addresses in memory.

Bonus Quirk: JavaScript

['0'].map(parseInt)['1', '0'].map(parseInt)['2', '1', '0'].map(parseInt)['3', '2', '1', '0'].map(parseInt)['4', '3', '2', '1', '0'].map(parseInt)['5', '4', '3', '2', '1', '0'].map(parseInt)['6', '5', '4', '3', '2', '1', '0'].map(parseInt)['7', '6', '5', '4', '3', '2', '1', '0'].map(parseInt)

Try these in turn, trying to predict each before you do it. You can look at the previous results before guessing. They map to:

[0] for just 0
[1, NaN] for 1 down to 0
[2, NaN, 0] for 2 down to 0
[3, NaN, 1, 0] for 3 down to 0
[4, NaN, NaN, 1, 0] for 4 down to 0
[5, NaN, NaN, 2, 1, 0] for 5 down to 0
[6, NaN, NaN, NaN, 2, 1, 0] for 6 down to 0
[7, NaN, NaN, NaN, 3, 2, 1, 0] for 7 down to 0

This is because parseInt takes a second argument for the radix (base): by default the list index.