Some Python gotchas and interesting details for functional programmers.
# None is the sole *value* of the class NoneType and None == None.
a = None
b = None
assert a == b
assert None == None
assert a is b
assert None is None
print(id(a), id(b), id(None))
print(type(None))
# Interesting (but probably useless) implementation details:
a = 'tony'
b = 'tony'
assert 'tony' is 'tony'
assert a is b
a = 7
b = 7
assert 7 is 7
assert a is b
a = 7.0
b = 7.0
assert 7.0 is 7.0
assert a is not b
# = binds to an existing object, it does not create a new copy of it
a = 1
b = a
assert b is a
a = (1, 2)
b = a
assert b is a
a = [1, 2]
b = a
assert b is a
# In fact, if you think = as a function, it does different things on different occasions:
# a = [] : use a to reference []
# b = a : use b to reference the object that a references to
# But you can always argue that: a "deep-references" the RHS
# Again.
a = []
b = a
b.append(1)
print(a, b)
assert a == b
assert a is b
# Again.
# And value of the global x is not copied as the argument of f.
def f(x):
y = x
print(('id of y', id(y)))
return y
# x is immutable
x = 1
print(('id of x', id(x)))
y = f(x)
print(x, y)
assert x is y
# Again.
def f(x):
y = x
print(('id of y', id(y)))
y.append(1)
return y
# x is mutable
x = []
print(('id of x', id(x)))
y = f(x)
print(x, y)
assert x is y
# Just because it got class, doesn't mean it will make a difference.
class C:
def __init__(self, a):
self.a = a
print(('id of self.a', id(self.a)))
self.a.append(1)
a = []
print(('id of a', id(a)))
c = C(a)
print(a, c.a)
assert a is c.a
# Don't use a mutable object as the default argument
# - unless you want to do something like memorization.
def f(a=[]):
a.append(1)
return (id(a), a)
print(f())
print(f())
print(f(a=[2, 3]))
print(f())
# But function-as-default-value seems OK, but I'm not sure about this.
evil = []
def f():
global evil
return evil
f.data = []
f.atad = 1
def h1(g=f):
print(f'id of the function being called: {id(g)}')
return (g(), g.data, g.atad)
f.data.append(1)
f.atad = 2
def h2(g=None):
if g is None:
g = f
print(f'id of the function being called: {id(g)}')
return (g(), g.data, g.atad)
evil.append('evil')
print(h1(), h2())
# Names in lambdas are looked up when the lambda is called,
# pretty acceptable and harmless, right? See the next cell.
try:
del c
except:
pass
f = lambda x: c * x
c = 3
print(f(2))
c = 4
print(f(2))
fs = [lambda x: c * x for c in range(2, 5)]
for f in fs:
print(f(2))
print('-')
fs = [lambda x, c=c: c * x for c in range(2, 5)]
for f in fs:
print(f(2))
import numpy as np
# np.array can be configured to return the same or a different object
xs = np.array([[11, 12, 13], [21, 22, 23]])
ys = np.array(xs, copy=True)
print(id(xs), id(ys))
print(xs)
print(ys)
print('-')
xs = np.array([[11, 12, 13], [21, 22, 23]])
ys = np.array(xs, copy=False)
print(id(xs), id(ys))
xs[0, 0] = 0
print(xs)
print(ys)
# Slicing references to the original array
xs = np.array([[11, 12, 13], [21, 22, 23]])
ys = xs[0]
print(xs)
print(ys)
print('-')
# changing the slice changes the original
ys[0] = 0
print(xs)
print(ys)
print('-')
# changing the original changes the slice
xs = np.array([[11, 12, 13], [21, 22, 23]])
ys = xs[0]
xs[0, 0] = 0
print(xs)
print(ys)
# To be continued