[Tutor] nested functions

Kirby Urner urnerk@qwest.net
Tue, 09 Apr 2002 19:26:57 -0700


At 05:11 PM 4/9/2002 -0500, Cameron Stoner wrote:
>Why would you want to define functions inside functions?
>It seems kinda like a class with methods.  When would you
>want to do something like this?

Indeed, as Danny pointed out, you might use a function
in a function to create a custom function.

Here's another math example -- also links to matrices,
an earlier topic.

To rotate point around the x-axis in the XYZ coordinate
system, we use the rotation matrix:

def xmatrix(theta):
     return [[1            ,0      , 0],
             [0, cos(theta),-sin(theta)],
             [0, sin(theta), cos(theta)]]

where theta is the angle you want to rotate by.  Notice
we use a function with theta as an argument.  Other,
similar matrices are used to rotate around the Y and
Z axes respectively:

def ymatrix(theta):
     return [[cos(theta), 0,-sin(theta)],
             [0         , 1         , 0],
             [sin(theta), 0, cos(theta)]]


def zmatrix(theta):
     return [[ cos(theta),-sin(theta),0],
             [ sin(theta), cos(theta),0],
             [ 0         ,0         , 1]]

Notice the matrix data structure:  rows are lists
within a list.  Each row has the same number of
entries.  These happen to be 3x3 square matrices.

Now suppose we frequently need to rotate points a fixed
amount, say 45 degrees around Z.  We can manufacture a
function that does this.

The function called mkrot() below takes one of the
above matrix functions, and the angle of rotation, as
its inputs:

def mkrot(matrix,theta):
     """
     Makes functions that rotate a point around a
     fixed axis by theta degrees
     """
     theta = theta*(pi/180)
     R = matrix(theta)  # R is a specific rotation matrix
     def rotate(v):
        "Matrix multiplication of R by v (a vector)"
        newcoords = []
        for row in R:
           newcoords.append(
                 reduce(add,map(mul,v.xyz,row)))
        return Vector(newcoords)  # vector points in new direction
     return rotate

Now let's use this function.  we pass zmatrix as the first
argument.  Recall that zmatrix is a function, which expects
and angle as its argument.  So we also pass the angle (45.)
which gets converted from degrees to radians in the first
line of mkrot().

zrot45  = mkrot(zmatrix,45.)

So theta gets passed to the matrix function (zmatrix in
this case -- might have been xmatrix or ymatrix) to create
rotation matrix R, and R features inside the internal
rotate() function.  It's this internal rotate() function
which mkrot() returns, i.e. it's returning a *function*
which rotates a point 45 degrees around the Z axis.

So zrot45 is now a function.  It expects a Vector object
as input (defined elsewhere).  Let's use it:

  >>> zrot45
  <function rotate at 0x00B003F0>
  >>> v = Vector([1,0,0])  # Vector an imported class
  >>> zrot45(v)
  Vector (0.70710678118654757, 0.70710678118654746, 0.0)

The arrow has moved.  (1,0,0) points to 3'oclock, but
after zrot45 does its job, the arrow has moved counter
clockwise by 45 degrees (the Z axis sticks through the
clock face, so rotations around it are in the plane of
the clock).

The guts of zrot45 is a simple matrix multiplication of
R (fixed by mkrot) times the single argument vector.

In a module I added to recently, I use this system to
compose two rotation functions, i.e. to make a single
rotation out of components:

theta = acos(-1/3.)*180/pi/2
xrot54  = mkrot(xmatrix,theta)
zrot45  = mkrot(zmatrix,45.)

def rotxz(v):
     return xrot54(zrot45(v))

rotxz will return a function that rotates any input vector
45 degrees around the Z axis, and then about 54 degrees
around the X axis.  I can now use rotxz(v) anywhere I
like in my program, knowing what it's been pre-programmed
to do.  And I can use mkrot() to manufacture other fixed
rotation functions, simply by handing it a matrix and an
angle.

Kirby