Functions as First-Class Objectsπ
First-class-objects is a program entity.
- created at runtime
- assigned a variable or element in a data structure
- passed as an argument to a function
- returned as the result of a function
Treating a Function Like an Objectπ
def factorial(n):
"""return n!"""
return 1 if n < 2 else n * factorial(n-1)
factorial(42) # 140500...
factorial.__doc__ # returns doc string
type(factorial) # <class 'function'>
fact = factorial # <function factorial at 0x..>
fact(5) # 120
map(factorial, range(11)) # <map object at 0x..>
list(map(factorial, range(11))) # [1, 1, 2, 6, ....]
Higher Order Functionsπ
A function that takes a function as an argument or returns a function as the result is a higher-order function. One example is map
, another is sorted
the optional key
argument lets you provide a function to be applied to each item for sorting. sorted(fruits, key = len)
Some of the best know higher-order functions are map
, filter
, reduce
and apply
. apply
was deprecated in python2.3 and removed in python3. As we can do fn(*args, **kwargs)
instead or apply(fn, args, kwargs)
Modern Replacements for map, filter and reduceπ
map
andfilter
functions are still built-ins in python3 but since introduction of listcomp and genexp they are not as important.
list(map(factorial, range(6)))
[factorial(n) for n in range(6)]
# another
list(map(factorial, filter(lambda n : n%2, range(6))))
[ factorial(n) for n in range(6) if n%2 ]
reduce
was demoted from built-in in python2 tofunctools
module in python3. Its most common use case, summation is better server bysum
built-in
- Other reducing built-ins are
all
orany
all(iterable)
: returnTrue
if there are no falsy elements in iterableany(iterable)
: returnsTrue
if any element of theiterable
is truthy.
Anonymous Functionsπ
The lambda
keyword creates an anonymous function within a Python expression.
However, the simple syntax of Python limits the body of lambda
functions to be pure expressions. In other words, the body cannot contain other Python statements such as while
, try
, etc. Assignment with =
is also a statement, so it cannot occur in a lambda
.
The new assignment expression syntax using :=
can be usedβbut if you need it, your lambda
is probably too complicated and hard to read, and it should be refactored into a regular function using def
.
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
sorted(fruits, key=lambda word: word[::-1])
The Nine Flavors of Callable Typesπ
- User-defined functions
- created with
def
statements orlambda
expressions
- created with
- Built-in functions
- A function implemented in C(for CPython), like
len
ortime.strftime
- A function implemented in C(for CPython), like
- Built-in methods
- Methods implemented in C, like
dict.get
- Methods implemented in C, like
- Methods
- Functions defined in the body of a class
- Classes
- when invoked ad class runs its
__new__
method to create an instance then__init__
to initialize it, and finally instance is returned to caller. There is nonew
operator in python
- when invoked ad class runs its
- Class instances
- If a class defines
__call__
method, then its instances may be invoked as functions
- If a class defines
- Generator Functions
- Functions or methods that use the
yield
keyword in their body. When called, they return a generator object.
- Functions or methods that use the
- Native coroutine functions
- Functions or methods defined with
async def
. When called, they return a coroutine object. Added in Python 3.5.
- Functions or methods defined with
- Asynchronous generator functions
- Functions or methods defined with
async def
that haveyield
in their body. When called, they return an asynchronous generator for use withasync for
. Added in Python 3.6.
- Functions or methods defined with
Given the variety of existing callable types in Python, the safest way to determine whether an object is callable is to use the callable()
User Defined Callable Typesπ
import random
class BingoCage:
def __init__(self, items):
self._items = list(items) # build local copy to avoid mutating items
random.shuffle(self._items)
def pick(self):
try:
return self._items.pop()
except IndexError:
raise LookupError('pick from empty BingoCage')
def __call__(self): # shortcut to bingo.pick() : bingo()
return self.pick()
From Positional to Keyword-only Parametersπ
One of the best features of Python functions is the extremely flexible parameter handling mechanism. Closely related are the use of *
and **
to unpack iterables and mappings into separate arguments when we call a function.
def tag(name, *content, class_=None, **attrs):
"""Generate one or more HTML tags"""
if class_ is not None:
attrs['class'] = class_
attr_pairs = (f' {attr}="{value}"' for attr, value
in sorted(attrs.items()))
attr_str = ''.join(attr_pairs)
if content:
elements = (f'<{name}{attr_str}>{c}</{name}>'
for c in content)
return '\n'.join(elements)
else:
return f'<{name}{attr_str} />'
# we can invoke it in multiple ways
tag('br') # '<br />'
tag('p', 'hello') # '<p>hello</p>'
print(tag('p', 'hello', 'world'))
# <p>hello</p>
# <p>world</p>
tag('p', 'hello', id=33) # <p id="33">hello</p>'
print(tag('p', 'hello', 'world', class_='sidebar')) 4
# <p class="sidebar">hello</p>
# <p class="sidebar">world</p>
tag(content='testing', name="img") # '<img content="testing" />'
my_tag = {'name': 'img', 'title': 'Sunset Boulevard', 'src': 'sunset.jpg', 'class': 'framed'}
tag(**my_tag) #'<img class="framed" src="sunset.jpg" title="Sunset Boulevard" />'
Keyword-only arguments are a feature of Python 3. In Example above, the class_ parameter can only be given as a keyword argumentβit will never capture unnamed positional arguments. To specify keyword-only arguments when defining a function, name them after the argument prefixed with *. If you donβt want to support variable positional arguments but still want keyword-only arguments, put a * by itself in the signature, like this:
def f(a, *, b):
return a, b
# now we cannot call b without keyword args
f(1, b = 2) # (1, 2)
f(1, 2) # raises exception
Positional Only Parametersπ
Since Python 3.8, user-defined function signatures may specify positional-only parameters. This feature always existed for built-in functions, such as divmod(a, b)
, which can only be called with positional parameters, and not as divmod(a=10, b=4)
.
To define a function requiring positional-only parameters, use /
in the parameter list.
Packages for Functional Programmingπ
The operator Moduleπ
Often in functional programming it is convenient to use an arithmetic operator as a function. For example, suppose you want to multiply a sequence of numbers to calculate factorials without using recursion. To perform summation, you can use sum
, but there is no equivalent function for multiplication.
from functools import reduce
from operator import mul
def factorial(n):
return reduce(mul, range(1, n+1))
# itemgetter
metro_data = [
('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
('SΓ£o Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
]
from operator import itemgetter
for city in sorted(metro_data, key=itemgetter(1)):
print(city)
Partial list of functions defined in operator:
>>> [name for name in dir(operator) if not name.startswith('_')]
['abs', 'add', 'and_', 'attrgetter', 'concat', 'contains',
'countOf', 'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt',
'iadd', 'iand', 'iconcat', 'ifloordiv', 'ilshift', 'imatmul',
'imod', 'imul', 'index', 'indexOf', 'inv', 'invert', 'ior',
'ipow', 'irshift', 'is_', 'is_not', 'isub', 'itemgetter',
'itruediv', 'ixor', 'le', 'length_hint', 'lshift', 'lt', 'matmul',
'methodcaller', 'mod', 'mul', 'ne', 'neg', 'not_', 'or_', 'pos',
'pow', 'rshift', 'setitem', 'sub', 'truediv', 'truth', 'xor']
Method callerπ
from operator import methodcaller
s = 'The time has come'
upcase = methodcaller('upper')
print(upcase(s)) # Output: 'THE TIME HAS COME'
hyphenate = methodcaller('replace', ' ', '-')
print(hyphenate(s)) # Output: 'The-time-has-come'
Freezing Arguments with functools.partialπ
partial
: given a callable, it produces a new callable with some of the arguments of the original callable bound to predetermined values. This is useful to adapt a function that takes one or more arguments to an API that requires a callback with fewer arguments.