Why are functions atomic?

Dustan DustanGroups at gmail.com
Fri May 4 07:13:14 EDT 2007


On May 4, 1:36 am, Michael <michael.for... at gmail.com> wrote:
> 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

>>> g()

4

>>> g()

4

>>> g() # what is going on here????

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