|
 |
Python |
11.1 Functions:
Introduction |
|
11.1.0 Python as a Functional Language:
Here is a quote from Guido van Rossum, Python's creator and
BDFL:
"I have never considered Python to be heavily influenced by functional
languages, no matter what people say or think. I was much more familiar
with imperative languages such as C and Algol 68 and although I had made
functions first-class objects, I didn't view Python as a functional
programming language. However, earlier on, it was clear that users wanted
to do much more with lists and functions.
It is also worth noting that even though I didn't envision Python
as a functional language, the introduction of closures has been useful
in the development of many other advanced programming features.
For example, certain aspects of new-style classes, decorators,
and other modern features rely upon this capability.
Lastly, even though a number of functional programming features
have been introduced over the years, Python still lacks certain
features found in 'real' functional programming languages. For
instance, Python does not perform certain kinds of optimizations
(e.g., tail recursion). In general, because of Python's extremely
dynamic nature, it is impossible to do the kind of compile-time
optimization known from functional languages like Haskell or ML.
And that's fine."
11.1.1 Functions as first-class objects:
Functions are first-class objects: they can be passed as
arguments, placed in data structures, and returned.
In fact: All objects in Python are said to be "first-class":
All objects that can be named by an identifier have equal status
and can be treated as data. (Beazley)
The example below shows functions as the values in a
dictionary.
Evaluate Reverse
Polish Notation |
# rpn.py: evaluate RPN
import sys
from operator import *
ops = {'+':add, '-':sub, '*':mul, '/':div,
'^':pow, '%':mod}
def error(n):
sys.stdout.write("Err: " + str(n) + ", ")
def eval_rpn(s): # s is input rpn
st = [] # evaluate stack
for c in s:
if c.isdigit(): # operand, single dig
st.append(float(c))
elif len(st) >= 2:
if not c in ops.keys():
error(1)
else: # operator
# do: next-to-top ops top
arg = st.pop()
st.append(ops[c](st.pop(),arg))
# ops['+'] is function "add"
else:
error(2)
if len(st) != 1:
error(3)
else:
return st[0]
|
def rpn(s):
sys.stdout.write("rpn:"+s+" \t")
sys.stdout.write("res:" +
str(eval_rpn(s)) + "\n")
rpn("23+") # res: 5.0
rpn("234*+") # res: 14.0
rpn("34*5+") # res: 17.0
rpn("23+4*") # res: 20.0
rpn("324+*51+/2-") # res: 1.0
rpn("53+21+2^^") # res: 134217728.0
rpn("2345^*6*+7+") # res: 18441.0
rpn("32^41*2*-12/^3-21*/") # res: -1.0
rpn("23-41+5*^6/24-7*-8-") # res:5.833
rpn("23/") # res: 0.666666666
rpn("83%") # res: 2.0
rpn("99+5%") # res: 3.0
# erroneous inputs
rpn("") # Err: 3
rpn("23+*") # Err: 2
rpn("2+3") # Err: 2, 3
rpn("23+4") # Err: 3
rpn("+23") # Err: 2, 3
rpn("+*") # Err: 2, 3
rpn("23") # Err: 3
rpn("%@") # Err: 2, 3
rpn("2 3 +") # Err: 1, 2
|
Items of interest or for study:
- Inside a class, the operators: +,
*, etc. are also represented by:
__add__,
__mul__, etc.
See for example:
Complex class.
- The operator module provides these special identifiers
that are defined in classes as above, and also provides names
without underscores, as in the example above.
11.1.2 Parameters:
This part shows formal Parameters (those in the function
definition) with default values, and actual parameters
(those in the function call) that are named.
For this page you should look at the early parts of:
Functions, which is a part of:
Building Skills in Python.
Parameter demo + output |
# shorten.py
import sys
def shorten(text, length=25, ind="..."):
if len(text) > length:
text = text[:length - len(ind)] + ind
return text
shorten("The Silkie") # returns: "The Silkie"
shorten(length=7, text="The Silkie") # returns: "The ..."
shorten("The Silkie", ind="&", length=7) # returns: "The Si&"
shorten("The Silkie", 7, "*") # returns: "The Si*"
|
Items of interest or for study:
- The example above (from Summerfield's book) takes an input
string (the text) and if necessary shortens its length to the
value of the second parameter minus the length of the third
parameter. If the text is shortened, the
text of the third parameter is added at the end.
- This example shows:
- default values for formal parameters in a function's
definition (the "=25" and the "='...' "), and
- named actual parameters in a function's call
("length=7", "text='The Silkie' ", "ind='&' ").
- Of the four calls above:
- The first shows both defaults used, with only the
first positional parameter.
- The fourth shows all three positional parameters used, with no
named parameters and ignoring the defaults.
- The second shows two named parameters used (out of order),
and one default value used.
- The third shows the first positional parameters used,
and the other two as named parameters (out of order).
- Because the first formal parameter is just an identifier,
the actual parameters must have either a simple first parameter,
or a parameter of the form: text = something.
11.1.3. *args and **kwargs:
Python has special features for passing arbitrarily many normal
arguments (*args) and a dictionary of keyword-value
arguments (*kwargs).
Program,
showing *args and **kwargs |
# test_args.py: test of *arg and **kwargs
import sys
def func(first_arg, *args, **kwargs):
# first_arg is positional-only param
print first_arg
# args is a tuple of positional args,
# because the param name has * first
if args: # If args is not empty.
print args
# kwargs = dictionary of keyword args,
# because the param name has ** first
if kwargs: # If kwargs is not empty.
print kwargs
|
>>> from test_args import *
>>> n = 4
>>> func("item")
item
>>> func("item", 1, 2, '3')
item
(1, 2, '3')
>>> func("item", 1, 2, '3', k1=4, k2="foo")
item
(1, 2, '3')
{'k2': 'foo', 'k1': 4}
>>> func("item", k1=-4.5E-13, k2=2*n+5)
item
{'k2': 13, 'k1': -4.5e-13}
>>> func(k2=True, first_arg="item")
item
{'k2': True}
|
Items of interest or for study:
- Here the * and the ** are not
operators, but part of the syntax. The names args and
kwargs can be any identifiers.
(Kind of flaky C-like notation.)
- These two items more-or-less encompass all possibilities for
a sequence of Python formal parameters. For decorators and elsewhere
this allows Python to handle arbitrary parameters in functional
programming, but this looks pretty complicated.
See Decorators
for use of this notation.
11.1.4. Using *args and **kwargs:
See
Advanced Parameter Handling For Functions.
First use *args to handle arbitrarily many parameters
(note that you don't have to use the identifier "args").
Then use a dictionary for the params:
Python
Parameters |
n |
Arbitrary Number of Params |
Dictionary of Params |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 |
# arb_num_params.py:
def myMax(*args):
max = args[0]
for a in args[1:]:
if a > max: max= a
return max
print myMax(2, 3, 7, 1) # prints 7
data = (2, 3, 7, 1)
print myMax(*data) # prints 7
print myMax(*(2,3,7,1)) # prints 7
print myMax(5) # prints 5
print myMax(None) # prints None
def myAve(*args):
tot = 0
for a in args:
tot += a
# float needed for Python 2.x
return tot / float(len(args))
print myAve(2, 3, 4, 5) # prints 3.5
print myAve(*data) # prints 3.25
print myAve(5) # prints 5.0
| # dict_of_params.py:
import sys
def avg3(a, b, c):
return (a+b+c)/3.0
# tuple of parameters
data = (5, 6, 7)
print avg3(*data) # prints 6.0
print avg3(a=5,b=6,c=7) # prints 6.0
# dictionary for parameters
d = {'a':5, 'b':6, 'c':7}
print avg3(**d) # prints 6.0
def myAve(**dict):
tot = 0
for a in dict:
tot += dict[a]
return tot / float(len(dict))
print myAve(**d) # prints 6.0
d2 = {'w':2, 'x':3, 'y':7, 'z':1}
print myAve(**d2) # prints 3.25
print myAve(a=5,b=6,c=7)# prints 6.0
|
Items of interest or for study:
- The program on the left above shows how *args
(or *anything) can stand in for an arbitrary list or
tuple of parameters.
- Python has a built-in function max( ) that works fine
in place of myMax( ) above.
- The three lines 14-16 can be accomplished fine with just
sum(args). In the next section we'll see a more general
way to do this. So myAve could be just:
def myAve(*args):
return sum(args) / float(len(args))
|
- You can use a sequence of actual parameters (line 7), or use a tuple as
in line 8, use a list in place of this tuple. You can even put a *
in front of an actual tuple (line 10).
- The program on the right shows how
**kwargs
(or **anything) can stand in for an arbitrary dictionary
of parameters. This dictionary gives named parameters.
- Lines 3-4 (on the right) give almost the worst possible way
to define an "average" function, since it will only work for three
ordinary parameters, and will only work for dictionaries that
use the variables a, b, and c. Surprisingly, this
terrible definition of myAve( ) does work with a general
tuple of length 3, and even works with a dictionary as long as
the keys use only the three letters.
- The boxed-in definition
above is a reasonable, functional definition of myAve( ).
However, this definition doesn't work for a dictionary.
- The definition of myAve( ) on the right works for
an arbitrary dictionary. It is presumably possible to write a
functional version of this.
11.1.5. The lambda operator:
This allows one to define an anonymous function in the form of an
expression. There are significant restrictions on these expressions.
lambdas are important in functional programming languages,
but in Python they seem to be only a convenient shortcut
that avoids using an extra function name.
The lambda statement has the form (from Beazley):
where args is a comma-separated list of arguments, and
expression is an expression involving those arguments.
(Can't have multiple statements, or non-expression statements.)
Here is an example:
a = lambda x, y : x+y
r = a(2, 3) # r gets 5
|
lambda used for case-insensitive sort:
Case-Insensitive
Sort |
# test_sort.py: mixed upper- lowercase
n = ["nale","name","Neal","Neil","NEAL",
"nile","Nile","nada","Nick","nick"]
n2 = list(n) # make copy
# could also write: n2 = n[:]
n2.sort() # sort n2 in place
print n2
|
n2 = list(n) # copy again
# sort case-insensitive
n2.sort(key=lambda n: n.lower())
print n2
# sort without lambda
n2 = list(n) # copy third time
def mytolower(n):
return n.lower()
n2.sort(key=mytolower)
print n2
|
Output
|
---|
['NEAL', 'Neal', 'Neil', 'Nick', 'Nile', 'nada', 'nale', 'name', 'nick', 'nile']
['nada', 'nale', 'name', 'Neal', 'NEAL', 'Neil', 'Nick', 'nick', 'nile', 'Nile']
['nada', 'nale', 'name', 'Neal', 'NEAL', 'Neil', 'Nick', 'nick', 'nile', 'Nile']
|
The sort library function has an optional function to apply to each
sort key. In the first case above it's an anonymous lambda function,
while in the second case it's explicitly defined.
Other uses of lambda:
The items given below are "almost always better expressed
using a generator expression or a list comprehension" (Beazley).
Still, these features are prominent in true functional
programming languages.
Case-Insensitive
Sort |
v = [1, 3, 5, 7]
map(lambda x:x+x, v) # [2, 6, 10, 14]
map(lambda x:x*x, v) # [1, 9, 25, 49]
reduce(lambda x,y: (x + y), v) # 16
reduce(lambda x,y: (x * y), v) # 105
reduce(operator.mul, v) # 105
reduce(operator.mul, v, 2) # 210
# Python's BDFL recommends this instead:
res = 2
for x in v:
res = operator.mul(res, x) # 210
|
(Revision date: 2015-08-10.
Please use ISO 8601,
the International Standard.)
|