[Python-ideas] 'Injecting' objects as function-local constants
Jan Kaliszewski
zuo at chopin.edu.pl
Sat Jun 11 15:30:29 CEST 2011
== Use cases ==
A quite common practice is 'injecting' objects into a function as its
locals, at def-time, using function arguments with default values...
Sometimes to keep state using a mutable container:
def do_and_remember(val, verbose=False, mem=collections.Counter()):
result = do_something(val)
mem[val] += 1
if verbose:
print('Done {} times for {!r}'.format(mem[val], val))
Sometimes, when creating functions dynamically (making use of nested
scopes), e.g. to keep some individual function features (usable within
that functions):
def make_my_callbacks(callback_params):
my_callbacks = []
for params in callback_params:
def fun1(*args, _params=params, **kwargs):
"...do something with args and params..."
def fun2(*args, _params=params, **kwargs):
"...do something with args and params..."
def fun3(*args, _fun1=fun1, _fun2=fun2, **kwargs):
"""...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
Sometimes simply to make critical parts of code optimised...
def do_it_quickly(fields, _len=len, _split=str.split,
_sth=something):
return [_len(f), _split(f), _sth(f) for f in fields]
...or even for readability -- keeping function-specific constants within
the function definition:
def check_value(val,
VAL_REGEX=re.compile('^...$'),
VAL_MAX_LEN=38):
return len(val) <= VAL_MAX_LEN and VAL_RE.search(val) is not None
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).
== Proposed solutions ==
I see three possibilities:
1.
To add a new keyword, e.g. `inject':
def do_and_remember(val, verbose=False):
inject mem = collections.Counter()
...
or maybe:
def do_and_remember(val, verbose=False):
inject collections.Counter() as mem
...
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()):
...
do_and_remember(val, False, mem='something') would raise TypeError and
`mem' shoudn not appear in help() etc. as a function argument.
3.
To provide a special decorator, e.g. functools.within:
@functools.within(mem=collections.Counter())
def do_and_remember(val, verbose=False):
...
Regards.
*j
More information about the Python-ideas
mailing list