name = '2017-01-20-function-quirks'
title = 'Some peculiarities of using functions in Python'
tags = 'basics'
author = 'Denis Sergeev'
from nb_tools import connect_notebook_to_post
from IPython.core.display import HTML, Image
html = connect_notebook_to_post(name, title, tags, author)
def my_super_function():
pass
def even_better():
print('This is executed within a function')
even_better()
This is executed within a function
type(even_better)
function
aka mandatory parameters
import numpy as np
def uv2wdir(u, v):
"""Calculate horizontal wind direction (meteorological notation)"""
return 180 + 180 / np.pi * np.arctan2(u, v)
a = uv2wdir(10, -10)
a
315.0
type(a)
numpy.float64
aka optional parameters
def myfun(list_of_strings, separator=' ', another=123):
result = separator.join(list_of_strings)
return result
words = ['This', 'is', 'my', 'Function']
myfun(words, another=456, separator='-------')
'This-------is-------my-------Function'
default_number = 10
def double_it(x=default_number):
return x * 2
double_it()
20
double_it(2)
4
default_number = 100000000
double_it()
20
But what if we used a mutable type as a default argument?
def add_items_bad(element, times=1, lst=[]):
for _ in range(times):
lst.append(element)
return lst
mylist = add_items_bad('a', 3)
print(mylist)
['a', 'a', 'a']
another_list = add_items_bad('b', 5)
print(another_list)
['a', 'a', 'a', 'b', 'b', 'b', 'b', 'b']
def add_items_good(element, times=1, lst=None):
if lst is None:
lst = []
for _ in range(times):
lst.append(element)
return lst
mylist = add_items_good('a', 3)
print(mylist)
['a', 'a', 'a']
another_list = add_items_good('b', 5)
print(another_list)
['b', 'b', 'b', 'b', 'b']
Variables declared outside the function can be referenced within the function:
x = 5
def add_x(y):
return x + y
add_x(20)
25
But these global variables cannot be modified within the function, unless declared global in the function.
def setx(y):
global x
x = y
print('x is {}'.format(x))
x
5
setx(10)
x is 10
print(x)
10
def foo():
a = 1
print(locals())
foo()
{'a': 1}
Special forms of parameters:
*args
: any number of positional arguments packed into a tuple**kwargs
: any number of keyword arguments packed into a dictionarydef variable_args(*args, **kwargs):
print('args are', args)
print('kwargs are', kwargs)
if 'z' in kwargs:
print(kwargs['z'])
variable_args('foo', 'bar', x=1, y=2)
args are ('foo', 'bar') kwargs are {'y': 2, 'x': 1}
def smallest(x, y):
if x < y:
return x
else:
return y
smallest(1, 2)
1
# smallest(1, 2, 3) <- results in TypeError
def smallest(x, *args):
small = x
for y in args:
if y < small:
small= y
return small
smallest(11)
11
Unpacking a dictionary of keyword arguments is particularly handy in matplotlib
.
import matplotlib.pyplot as plt
%matplotlib inline
arr1 = np.random.rand(100)
arr2 = np.random.rand(100)
style1 = dict(linewidth=3, color='#FF0123')
style2 = dict(linestyle='--', color='skyblue')
plt.plot(arr1, **style1)
plt.plot(arr2, **style2)
[<matplotlib.lines.Line2D at 0x7fd3e796a550>]
Functions are first-class objects. This means that functions can be passed around, and used as arguments, just like any other value (e.g, string, int, float).
def find_special_numbers(special_selector, limit=10):
found = []
n = 0
while len(found) < limit:
if special_selector(n):
found.append(n)
n += 1
return found
def check_odd(a):
return a % 2 == 1
mylist = find_special_numbers(check_odd, 25)
for n in mylist:
print(n, end=',')
1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,
But lots of small functions can clutter your code...
Highly pythonic!
check = i -> return True if i % 6 == 0
check = lambda i: i % 6 == 0
#check = lambda
Lambdas usually are not defined on their own, but inserted in-place.
find_special_numbers(lambda i: i % 6 == 0, 5)
[0, 6, 12, 18, 24]
lyric = "Never gonna give you up"
words = lyric.split()
words
['Never', 'gonna', 'give', 'you', 'up']
sorted(words, key=lambda x: x.lower())
['give', 'gonna', 'Never', 'up', 'you']
Just using sorted()
gives us not what we want:
lst = ['20', '1', '2', '100']
sorted(lst)
['1', '100', '2', '20']
But we can use a lambda
-expression to overcome this problem:
Option 1:
sorted(lst, key=lambda x: int(x))
['1', '2', '20', '100']
Option 2:
sorted(lst, key=lambda x: x.zfill(16))
['1', '2', '20', '100']
By the way, what does zfill()
method do? It pads a string with zeros:
'aaaa'.zfill(10)
'000000aaaa'
HTML(html)
This post was written as an IPython (Jupyter) notebook. You can view or download it using nbviewer.