In [1]:
def sagan():
    quote = "With insufficient data it is easy to go wrong."

In [2]:
def null():
    pass

In [3]:
# define the function
def forty_two():
    return 42

In [4]:
# call the function
forty_two()


Out[4]:
42

In [5]:
# call the function, and print the result
print(forty_two())


42

In [6]:
# call the function, assign the result 
# to x, and print x
x = forty_two()
print(x)


42

In [7]:
def forty_two_or_bust(x):
    if x:
        print(42)
    else:
        print(0)

In [8]:
# call the function
forty_two_or_bust(True)


42

In [9]:
bust = False
forty_two_or_bust(bust)


0

In [10]:
def power(base, x):
    return base**x

In [11]:
from math import sin

def sin_inv_x(x):
    if x == 0.0:
        result = 0.0
    else:
        result = sin(1.0/x)
    return result

In [12]:
def power(base, x):
    """Computes base^x. Both base and x should be integers,
    floats, or another numeric type.
    """
    return base**x

In [13]:
def line(x, a=1.0, b=0.0):
    return a*x + b

In [14]:
line(42)            # no keyword args, returns 1*42 + 0


Out[14]:
42.0

In [15]:
line(42, 2)         # a=2, returns 84


Out[15]:
84.0

In [16]:
line(42, b=10)      # b=10, returns 52


Out[16]:
52.0

In [17]:
line(42, b=10, a=2) # returns 94


Out[17]:
94

In [18]:
line(42, a=2, b=10) # also returns 94


Out[18]:
94

In [19]:
# Do not do this!
def myappend(x, lyst=[]):
    lyst.append(x)
    return lyst

In [20]:
myappend(6)  # seems right


Out[20]:
[6]

In [21]:
myappend(42) # hmmm...


Out[21]:
[6, 42]

In [22]:
myappend(12, [1, 16])


Out[22]:
[1, 16, 12]

In [23]:
myappend(65) # nope, not right!


Out[23]:
[6, 42, 65]

In [24]:
def myappend(x, lyst=None):
    if lyst is None:
        lyst = []
    lyst.append(x)
    return lyst

In [25]:
myappend(6)


Out[25]:
[6]

In [26]:
myappend(42)


Out[26]:
[42]

In [27]:
myappend(12, [1, 16])


Out[27]:
[1, 16, 12]

In [28]:
myappend(65)


Out[28]:
[65]

In [29]:
max(6, 2)


Out[29]:
6

In [30]:
max(6, 42)


Out[30]:
42

In [31]:
max(1, 16, 12)


Out[31]:
16

In [32]:
max(65, 42, 2, 8)


Out[32]:
65

In [33]:
def minimum(*args):
    """Takes any number of arguments!"""
    m = args[0]
    for x in args[1:]:
        if x < m:
          m = x
    return m

In [34]:
minimum(6, 42)


Out[34]:
6

In [35]:
data = [65, 42, 2, 8]
minimum(*data)


Out[35]:
2

In [36]:
def blender(*args, **kwargs):
    """Will it?"""
    print(args, kwargs)

In [37]:
blender("yes", 42)


('yes', 42) {}

In [38]:
blender(z=6, x=42)


() {'z': 6, 'x': 42}

In [39]:
blender("no", [1], "yes", z=6, x=42)


('no', [1], 'yes') {'z': 6, 'x': 42}

In [40]:
t = ("no",)
d = {"mom": "ionic"}
blender("yes", kid="covalent", *t, **d)


('yes', 'no') {'mom': 'ionic', 'kid': 'covalent'}

In [41]:
def momentum_energy(m, v):
    p = m * v
    e = 0.5 * m * v**2
    return p, e

In [42]:
# returns a tuple
p_e = momentum_energy(42.0, 65.0)
print(p_e)


(2730.0, 88725.0)

In [43]:
# unpacks the tuple
mom, eng = momentum_energy(42.0, 65.0)
print(mom)


2730.0

In [44]:
# global scope
a = 6
b = 42

def func(x, y):
    # local scope
    z = 16
    return a*x + b*y + z

# global scope
c = func(1, 5)

In [45]:
# global scope
a = 6
b = 42

def outer(m, n):
    # outer's scope
    p = 10 

    def inner(x, y):
        # inner's scope
        return a*p*x + b*n*y

    # outer's scope
    return inner(m+1, n+1)

# global scope
c = outer(1, 5)

In [46]:
a = 6

def a_global():
    print(a)

def a_local():
    a = 42
    print(a)

a_global()
a_local()
print(a)


6
42
6

In [47]:
a = "A"

def func():
    # you cannot use the global 'a' because...
    print("Big " + a)
    # a local 'a' is eventually defined!
    a = "a"
    print("small " + a)

func()


---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-47-fe5d3736f26c> in <module>()
      8     print("small " + a)
      9 
---> 10 func()

<ipython-input-47-fe5d3736f26c> in func()
      3 def func():
      4     # you cannot use the global 'a' because...
----> 5     print("Big " + a)
      6     # a local 'a' is eventually defined!
      7     a = "a"

UnboundLocalError: local variable 'a' referenced before assignment

In [48]:
a = "A"

def func():
    global a
    print("Big " + a)
    a = "a"
    print("small " + a)

func()
print("global " + a)


Big A
small a
global a

In [49]:
# DO NOT RUN THIS
#def func():
#    func()

In [50]:
def fib(n):
    if n == 0 or n == 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

In [51]:
import sys
sys.getrecursionlimit()      # return the current limit
sys.setrecursionlimit(8128)  # change the limit to 8128

In [52]:
# a simple lambda
lambda x: x**2


Out[52]:
<function __main__.<lambda>>

In [53]:
# a lambda that is called after it is defined
(lambda x, y=10: 2*x + y)(42)


Out[53]:
94

In [54]:
# just because it is anonymous doesn't mean we can't give it a name!
f = lambda: [x**2 for x in range(10)]
f()


Out[54]:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

In [55]:
# a lambda as a dict value
d = {'null': lambda *args, **kwargs: None}

In [56]:
# a lambda as a keyword argument f in another function
def func(vals, f=lambda x: sum(x)/len(x)):
    f(vals)

In [57]:
# a lambda as a keyword argument in a function call
func([6, 28, 496, 8128], lambda data: sum([x**2 for x in data]))

In [58]:
nums = [8128, 6, 496, 28]

In [59]:
sorted(nums)


Out[59]:
[6, 28, 496, 8128]

In [60]:
sorted(nums, key=lambda x: x%13)


Out[60]:
[496, 28, 8128, 6]

In [61]:
def countdown():
    yield 3
    yield 2
    yield 1
    yield 'Blast off!'

In [62]:
# generator
g = countdown()

In [63]:
next(g)


Out[63]:
3

In [64]:
x = next(g)
print(x)


2

In [65]:
y, z = next(g), next(g)
print(z)


Blast off!

In [66]:
next(g)


---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-66-5f315c5de15b> in <module>()
----> 1 next(g)

StopIteration: 

In [67]:
for t in countdown():
    if isinstance(t, int):
        message = "T-" + str(t)
    else:
        message = t
    print(message)


T-3
T-2
T-1
Blast off!

In [68]:
def square_plus_one(n):
    for x in range(n):
        x2 = x * x
        yield x2 + 1

In [69]:
for sp1 in square_plus_one(3):
    print(sp1)


1
2
5

In [70]:
# define a subgenerator
def yield_all(x):
    for i in x:
        yield i

# palindrome using yield froms
def palindromize(x):
    yield from yield_all(x)
    yield from yield_all(x[::-1])

# the above is equivalent to this full expansion:
def palindromize_explicit(x):
    for i in x:
        yield i
    for i in x[::-1]:
        yield i

In [71]:
def null(f):
    """Always return None."""
    return

def identity(f):
    """Return the function."""
    return f

def self_referential(f):
    """Return the decorator."""
    return self_referential

In [72]:
@null
def nargs(*args, **kwargs):
    return len(args) + len(kwargs)

In [73]:
def nargs(*args, **kwargs):
    return len(args) + len(kwargs)
nargs = null(nargs)

In [74]:
def plus1(f):
    def wrapper(*args, **kwargs):
        return f(*args, **kwargs) + 1
    return wrapper

In [75]:
@plus1
def power(base, x):
    return base**x

power(4, 2)


Out[75]:
17

In [76]:
@plus1
@identity
@plus1
@plus1
def root(x):
    return x**0.5

root(4)


Out[76]:
5.0

In [77]:
def plus_n(n):
    def dec(f):
        def wrapper(*args, **kwargs):
            return f(*args, **kwargs) + n
        return wrapper
    return dec

In [78]:
@plus_n(6)
def root(x):
    return x**0.5

root(4)


Out[78]:
8.0

In [79]:
max = plus1(max)