Why are functions atomic?

Michael michael.forbes at gmail.com
Fri May 4 02:36:11 EDT 2007


On May 2, 6:08 am, Carsten Haese <cars... at uniqsys.com> wrote:
> On Tue, 2007-05-01 at 22:21 -0700, Michael wrote:
> > Is there a reason for using the closure here?  Using function defaults
> > seems to give better performance:[...]
>
> It does? Not as far as I can measure it to any significant degree on my
> computer.

I agree the performance gains are minimal.  Using function defaults
rather than closures, however, seemed much cleaner an more explicit to
me.  For example, I have been bitten by the following before:

>>> def f(x):
...     def g():
...         x = x + 1
...         return x
...     return g
>>> g = f(3)
>>> g()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in g
UnboundLocalError: local variable 'x' referenced before assignment

If you use default arguments, this works as expected:
>>> def f(x):
...     def g(x=x):
...         x = x + 1
...         return x
...     return g
>>> g = f(3)
>>> g()
4

The fact that there also seems to be a performance gain (granted, it
is extremely slight here) led me to ask if there was any advantage to
using closures.  It seems not.

> An overriding theme in this thread is that you are greatly concerned
> with the speed of your solution rather than the structure and
> readability of your code.

Yes, it probably does seem that way, because I am burying this code
deeply and do not want to revisit it when profiling later, but my
overriding concern is reliability and ease of use.  Using function
attributes seemed the best way to achieve both goals until I found out
that the pythonic way of copying functions failed.  Here was how I
wanted my code to work:

@define_options(first_option='abs_tol')
def step(f,x,J,abs_tol=1e-12,rel_tol=1e-8,**kwargs):
   """Take a step to minimize f(x) using the jacobian J.
   Return (new_x,converged) where converged is true if the tolerance
   has been met.
   """
   <compute dx and check convergence>
   return (x + dx, converged)

@define_options(first_option='min_h')
def jacobian(f,x,min_h=1e-6,max_h=0.1):
   """Compute jacobian using a step min_h < h < max_h."""
   <compute J>
   return J

class Minimizer(object):
    """Object to minimize a function."""
    def __init__(self,step,jacobian,**kwargs):
        self.options = step.options + jacobian.options
        self.step = step
        self.jacobian = jacobian

    def minimize(self,f,x0,**kwargs):
        """Minimize the function f(x) starting at x0."""
        step = self.step
        jacobian = self.jacobian

        step.set_options(**kwargs)
        jacobian.set_options(**kwargs)

        converged = False
        while not converged:
            J = jacobian(f,x)
            (x,converged) = step(f,x,J)

        return x

    @property
    def options(self):
        """List of supported options."""
        return self.options

The idea is that one can define different functions for computing the
jacobian, step etc. that take various parameters, and then make a
custom minimizer class that can provide the user with information
about the supported options etc.

The question is how to define the decorator define_options?

1) I thought the cleanest solution was to add a method f.set_options()
which would set f.func_defaults, and a list f.options for
documentation purposes.  The docstring remains unmodified without any
special "wrapping", step and jacobian are still "functions" and
performance is optimal.

2) One could return an instance f of a class with f.__call__,
f.options and f.set_options defined.  This would probably be the most
appropriate OO solution, but it makes the decorator much more messy,
or requires the user to define classes rather than simply define the
functions as above.  In addition, this is at least a factor of 2.5
timese slower on my machine than option 1) because of the class
instance overhead.  (This is my only real performance concern because
this is quite a large factor.  Otherwise I would just use this
method.)

3) I could pass generators to Minimize and construct the functions
dynamically.  This would have the same performance, but would require
the user to define generators, or require the decorator to return a
generator when the user appears to be defining a function.  This just
seems much less elegant.

...
@define_options_generator(first_option='min_h')
def jacobian_gen(f,x,min_h=1e-6,max_h=0.1):
   """Compute jacobian using a step min_h < h < max_h."""
   <compute J>
   return J

class Minimizer(object):
    """Object to minimize a function."""
    def __init__(self,step_gen,jacobian_gen,**kwargs):
        self.options = step_gen.options + jacobian_gen.options
        self.step_gen = step_gen
        self.jacobian_gen = jacobian_gen

    def minimize(self,f,x0,**kwargs):
        """Minimize the function f(x) starting at x0."""
        step = self.step_gen(**kwargs)
        jacobian = self.jacobian_gen(**kwargs)

        converged = False
        while not converged:
            J = jacobian(f,x)
            (x,converged) = step(f,x,J)

        return x
    ...

4) Maybe there is a better, cleaner way to do this, but I thought that
my option 1) was the most clear, readable and fast.  I would
appreciate any suggestions.  The only problem is that it does use
mutable functions, and so the user might be tempted to try:

new_step = copy(step)

which would fail (because modifying new_step would also modify step).
I guess that this is a pretty big problem (I could provide a custom
copy function so that

new_step = step.copy()

would work) and I wondered if there was a better solution (or if maybe
copy.py should be fixed.  Checking for a defined __copy__ method
*before* checking for pre-defined mutable types does not seem to break
anything.)

Thanks again everyone for your suggestions, it is really helping me
learn about python idioms.

Michael.




More information about the Python-list mailing list