[Python-ideas] 'Injecting' objects as function-local constants

Jan Kaliszewski zuo at chopin.edu.pl
Mon Jun 13 00:22:36 CEST 2011


Terry Reedy dixit (2011-06-11, 16:09):

> On 6/11/2011 9:30 AM, Jan Kaliszewski wrote:
[...]
> >In all that cases (and probably some other too) that technique appears
> >to be quite useful.
> >
> >
> >== The problem ==
> >
> >...is that it is not very elegant. We add arguments which:
> >a) mess up function signatures (both in the code and in auto-generated docs);
> >b) can be incidentally overriden (especially when a function has an "open"
> >    signature with **kwargs).
> 
> One problem with trying to 'fix' this is that there can be defaulted args
> which are not intended to be overwritten by users but which are intended to
> be replaced in recursive calls.

I think this is another case... Although I can imagine that such 'private'
arguments could be specified when calling -- after **{...}/bare **, e.g.:

    fun(1, b=3, **{'c':3}, my_secret_hidden_arg='xyz')
    fun(1, b=3, **, my_secret_hidden_arg='xyz')

Though at the first sight I don't like this (`after-** args in calls') idea
so much (contrary to `after-** args in definitions' idea).

[...]
> >2. (which personally I would prefer)
> >To add `dummy' (or `hidden') keyword arguments, defined after **kwargs
> >(and after bare ** if kwargs are not needed; we have already have
> >keyword-only arguments after *args or bare *):
> >
> >     def do_and_remember(val, verbose=False, **, mem=collections.Counter()):
> >         ...
> 
> I thought of this while reading 'the problem'. It is at least
> plausible to me.
[...]

Arnaud Delobelle dixit (2011-06-11, 21:47):

> On 11 Jun 2011, at 14:30, Jan Kaliszewski wrote:
[...]
> > 3.
> > To provide a special decorator, e.g. functools.within:
> >    @functools.within(mem=collections.Counter())
> >    def do_and_remember(val, verbose=False):
> >        ...
> 
> That's hard to do as (assuming the function is defined at the global
> scope), mem will be compiled as a global, meaning that you will have

Here mem is a keyword argument, not a variable. Though I understand that
making it local/closure would need some code/closures hacking... Unless
built in to the interpreter.

> to modify the bytecode.  Oh but this makes me think about something I
> wrote a while ago (see below).
> 
> 
> 4. Use closures.
> 
> def factory(mem):
>      def do_and_remember(val, verbose=False)
>          result = do_something(val)
>          mem[val] += 1
>          if verbose:
>              print('Done {} times for {!r}'.format(mem[val], val))         ....
>      return do_and_remember
> do_and_remember = factory(mem=collections.Counter())
> 
> Added bonus: you can create many instances of do_and_remember.

Yes, but this method makes code longer and more complex.
And simple is better :)

Consider my multi-factory example:

    def make_my_callbacks(callback_params):
        my_callbacks = []
        for params in callback_params:
            def fun1(*args, **kwargs, params=params):
                "...do something with args and params..."
            def fun2(*args, **kwargs, params=params):
                "...do something with args and params..."
            def fun3(*args, **kwargs, fun1=fun1, fun2=fun2):
                """...do something with args and with functions fun1, fun2,
                for example pass them as callbacks to other functions..."
            my_callbacks.append((fun1, fun2, fun3))
        return my_callbacks

...compared to:

    def make_fun1(params):
        def fun1(*args, **kwargs):
            "...do something with args and params..."
        return fun1

    def make_fun2(params):
        def fun2(*args, **kwargs):
            "...do something with args and params..."
        return fun2

    def make_fun3(fun1, fun2):
        def fun3(*args, **kwargs):
            """...do something with args and with functions fun1, fun2,
            for example pass them as callbacks to other functions..."
        return fun3

    def make_my_callbacks(callback_params):
        my_callbacks = []
        for params in callback_params:
            fun1 = make_fun1(params)
            fun2 = make_fun2(params)
            fun3 = make_fun3(fun1, fun2)
            my_callbacks.append((fun1, fun2, fun3))
        return my_callbacks

Though, maybe it'a a matter of individual taste...

> Related to this, here's a "localize" decorator that I wrote some time
> ago for fun (I think it was from a discussion on this list).  It was
> for python 2.x (could easily be modified for 3.x I think, it's a
> matter of adapting the attribute names of the function object).  It
> "freezes" all non local variables in the function.  It's a hack! It
> may be possible to adapt it.
> 
> def new_closure(vals):
>     args = ','.join('x%i' % i for i in range(len(vals)))
>     f = eval("lambda %s:lambda:(%s)" % (args, args))
>     return f(*vals).func_closure
> 
> def localize(f):
>     f_globals = dict((n, f.func_globals[n]) for n in f.func_code.co_names)
>     f_closure = ( f.func_closure and
>                   new_closure([c.cell_contents for c in f.func_closure]) )
>     return type(f)(f.func_code, f_globals, f.func_name,
>                    f.func_defaults, f_closure)

Nice :) (and, as far as I understand, it could be used to implement the
decorator I ment).

Best regards.
*j




More information about the Python-ideas mailing list