Define a function with no arguments and no return values:
In [2]:
def print_text():
print('this is text')
In [32]:
# call the function
print_text()
Use pass as a placeholder if you haven't written the function body:
In [16]:
def stub():
pass
In [31]:
def say_hello():
return 'hello'
say_hello()
Out[31]:
Return two values from a single function:
In [17]:
def min_max(nums):
return min(nums), max(nums)
In [18]:
# return values can be assigned into multiple variables using tuple unpacking
nums = [3, 6, 5, 8, 2, 19, 7]
min_num, max_num = min_max(nums)
print(min_num)
print(max_num)
In [4]:
def print_this(x):
print (x)
print_this(3)
Define a function with a default value:
In [34]:
def calc(a, b, op='add'):
if op == 'add':
return a+b
elif op == 'sub':
return a-b
else:
print('valid operations are add and sub')
calc(10, 4)
Out[34]:
In [35]:
calc(10,4, op='add')
Out[35]:
In [15]:
# unnamed arguments are inferred by position
calc(10, 4, 'add')
Out[15]:
In [16]:
x = 42
def spam(a, b=x):
print(a, b)
spam(1)
In [18]:
x = 23 # Has no effect
spam(1)
Default values should always be const values, or you can get in trouble
In [23]:
def spam(a, b=[]): # b escapes the function as a return variable, which can be altered!
return b
x = spam(1)
x
Out[23]:
In [22]:
x = spam(1)
x.append(99)
x.append('Yow!')
spam(1) # Modified list gets returned!
Out[22]:
Function taking an arbitrary number of arguments
In [1]:
# arbitrary positional arguments
def print_all(seperator, *args):
print(seperator.join(args))
print_all(',', 'first','second','third')
In [4]:
# arbitrary positional AND keyword arguments
def anyargs(*args, **kwargs):
print(args) # A tuple
print(kwargs) # A dict
anyargs(3, 'ddddd', 5.666, foo='bar', blah='zed')
In [6]:
# keyword arguments have access to attribute name
import html
def make_element(name, value, **attrs):
keyvals = [' %s="%s"' % item for item in attrs.items()]
attr_str = ''.join(keyvals)
element = '<{name}{attrs}>{value}</{name}>'.format(
name=name,
attrs=attr_str,
value=html.escape(value))
return element
# Example
# Creates '<item size="large" quantity="6">Albatross</item>'
make_element('item', 'Albatross', size='large', quantity=6)
Out[6]:
Define a function that only accepts keyword arguments
In [7]:
def recv(maxsize, *, block):
'Receives a message'
pass
recv(1024, block=True) # Ok
# the following will fail if uncommented
#recv(1024, True) # TypeError
Define a function that take a callable (function) as a parameter):
In [37]:
def dedupe(items):
seen = set()
for item in items:
if item not in seen:
yield item
seen.add(item)
a = [1, 5, 2, 1, 9, 1, 5, 10]
list(dedupe(a))
Out[37]:
In [13]:
def calc(a, b, op='add'):
"""
calculates the result of a simple math operation.
:param a: the first parameter in the math operation
:param b: the first parameter in the math operation
:param op: which type of math operation (valid values are 'add', 'sub')
:returns: this result of applying the math argument to the two parameters
:raises keyError: raises an exception
"""
if op == 'add':
return a+b
elif op == 'sub':
return a-b
else:
print('valid operations are add and sub')
In [14]:
help(calc)
Attaching additional metadata to a function definition
In [11]:
# the compiler does not check any of this, it is just documentation!
def add(x:int, y:int) -> int:
return x + y
add('hello', 'world')
Out[11]:
In [12]:
help(add)
Unpacking iterables into positional function arguments (star operator)
In [21]:
# range takes start and stop parameters
list(range(3, 6)) # normal call with separate arguments
[3, 4, 5]
Out[21]:
In [22]:
# can also pass start and stop arguments using argument unpacking
args = [3,6] #can be used on tuple too
list(range(*args))
Out[22]:
Unpacking dictionaries into named arguments (double-star operator)
In [39]:
# a dictionary can be unpacked into names arguments with the ** / double star operator
from collections import namedtuple
Stock = namedtuple('Stock', ['name', 'shares', 'price', 'date', 'time'])
# Create a prototype instance
stock_prototype = Stock('', 0, 0.0, None, None)
# Function to convert a dictionary to a Stock
def dict_to_stock(s):
return stock_prototype._replace(**s)
a = {'name': 'ACME', 'shares': 100, 'price': 123.45}
dict_to_stock(a)
Out[39]:
Generator functions with yield
In [25]:
def myrange(n):
for i in range(n):
yield i
max(myrange(5))
Out[25]:
Generator function that uses an internal iterator
In [26]:
#using yield from
def myrange(n):
yield from range(n)
print(max(myrange(5)))
In [42]:
squared = lambda x: x**2
squared(3)
Out[42]:
In [47]:
simpsons = ['bart', 'maggie', 'homer', 'lisa', 'marge']
sorted(simpsons, key = lambda word: word[-1])
Out[47]:
In [51]:
# no parameter lambda
say_hello = lambda : 'hello'
say_hello()
Out[51]:
Default Parameters in Lambdas
In [49]:
talkback = lambda message='hello' : message
talkback()
Out[49]:
In [50]:
talkback('hello world')
Out[50]:
Capturing local variables in lambdas
In [52]:
test = 'hello world'
talkback = lambda : test
talkback()
Out[52]:
In [53]:
# parameters are resolved when the code runs, not when lambda is declared
test = 'what???'
talkback()
Out[53]:
In [54]:
# to prevent this, use a default parameter set to the local variable
test = 'hello world'
talkback = lambda message = test: message
test='nope'
talkback()
Out[54]:
Partial allows you convert an n-parameter function into a function with less arguments
In [24]:
def spam(a, b, c, d):
print(a, b, c, d)
from functools import partial
s1 = partial(spam, 1) # a = 1
s1(2, 3, 4)
In [28]:
s1(4, 5, 6)
In [29]:
s2 = partial(spam, d=42) # d = 42
s2(1, 2, 3)
In [30]:
s2(4, 5, 5)
In [31]:
s3 = partial(spam, 1, 2, d=42) # a = 1, b = 2, d = 42
s3(3)
In [32]:
s3(4)
In [33]:
s3(5)
In [36]:
# this inner closure is used to carry state around
def name_func_from_family(last_name):
def print_name(first_name):
print('{} {}'.format(first_name, last_name))
return print_name #the key here is that the outer function RETURNS the inner function / closure
print_saltwell = name_func_from_family('saltwell')
print_saltwell('erik')
print_saltwell('kasia')
print_saltwell('jacob')
nonlocal allows you to modify a variable outside your scope (but not global scope)
In [37]:
def outside():
msg = "Outside!"
def inside():
msg = "Inside!"
print(msg)
inside()
print(msg) # this prints 'Outside!' even though Inside() mosifies a variable called msg (its a local copy)
outside()
In [41]:
# to have a variable refer to something outside local scope use nonlocal
def outside():
msg = "Outside!"
def inside():
nonlocal msg
msg = "Inside!"
print(msg)
inside()
print(msg)
outside()
In [42]:
# the global keyword makes a variable reference a global variable rather then a copy
msg = 'Global!!'
def outside():
msg = "Outside!"
def inside():
global msg
msg = "Inside!"
print(msg)
inside()
print(msg) # this prints 'Outside!' because the copy in Inside() references the global variable
outside()
msg
Out[42]: