Idea: Deferred Default Arguments?
Often when programming I run into a situation where it would be nice to have "deferred defaults". Here is an example of what I mean: def subfunction_1(a=2, b=3, c=4): return a+b*c def subfunction_2(d=5, e=6, f=7): return d*e+f def main_function(a=2, b=3, c=4, d=5, e=6, f=7): return subfunction_1(a=a, b=b, c=c) + subfunction_2(d=d, e=e, f=f) Here you can see that I had to redefine the defaults in the main_function. In larger codebases, I find bugs often arise because defaults are defined in multiple places, and somebody changes them in a lower-level function but fails to realize that they are still defined differently in a higher function. The only way I currently see to achieve this is not very nice at all, and completely obfuscates the signature of the function: def main_function(**kwargs): return subfunction_1(**{k: v for k, v in kwargs.items() if k in ['a', 'b', 'c']}) + subfunction_2(**{k: v for k, v in kwargs.items() if k in ['d', 'e', 'f']}) What I was thinking was a "deferred" builtin that would just allow a lower function to define the value (and raise an exception if anyone tried to use it before it was defined) def main_function(a=deferred, b=deferred, c=deferred, d=deferred, e=deferred, f=deferred): return subfunction_1(a=a, b=b, c=c) + subfunction_2(d=d, e=e, f=f) I assume this has been discussed before somewhere, but couldn't find anything on it, so please feel free to point me towards any previous discussion on the topic.
Hi Peter
Interesting problem. We can already get something like your proposed
solution by using None instead of deferred.
You have to start with
def subfunction_1(a=None, b=None, c=None):
if a is None: a = 2
# similarly for b and c.
return a+b*c
You will loose the default values being shown when you do help(subfunction).
There's another thread on PEP 505, active right now, that would allow
you to write something like
a = a OR 2
to provide the default values.
https://mail.python.org/pipermail/python-ideas/2018-July/052071.html
I hope this helps. Please let us know if that might work for you.
--
Jonathan
On Fri, Jul 20, 2018 at 10:03 AM, Peter O'Connor
Often when programming I run into a situation where it would be nice to have "deferred defaults". Here is an example of what I mean:
def subfunction_1(a=2, b=3, c=4): return a+b*c
def subfunction_2(d=5, e=6, f=7): return d*e+f
def main_function(a=2, b=3, c=4, d=5, e=6, f=7): return subfunction_1(a=a, b=b, c=c) + subfunction_2(d=d, e=e, f=f)
Here you can see that I had to redefine the defaults in the main_function. In larger codebases, I find bugs often arise because defaults are defined in multiple places, and somebody changes them in a lower-level function but fails to realize that they are still defined differently in a higher function.
The only way I currently see to achieve this is not very nice at all, and completely obfuscates the signature of the function:
def main_function(**kwargs): return subfunction_1(**{k: v for k, v in kwargs.items() if k in ['a', 'b', 'c']}) + subfunction_2(**{k: v for k, v in kwargs.items() if k in ['d', 'e', 'f']})
What I was thinking was a "deferred" builtin that would just allow a lower function to define the value (and raise an exception if anyone tried to use it before it was defined)
def main_function(a=deferred, b=deferred, c=deferred, d=deferred, e=deferred, f=deferred): return subfunction_1(a=a, b=b, c=c) + subfunction_2(d=d, e=e, f=f)
I assume this has been discussed before somewhere, but couldn't find anything on it, so please feel free to point me towards any previous discussion on the topic.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Fri, Jul 20, 2018 at 7:03 PM, Peter O'Connor
Often when programming I run into a situation where it would be nice to have "deferred defaults". Here is an example of what I mean:
def subfunction_1(a=2, b=3, c=4): return a+b*c
def subfunction_2(d=5, e=6, f=7): return d*e+f
def main_function(a=2, b=3, c=4, d=5, e=6, f=7): return subfunction_1(a=a, b=b, c=c) + subfunction_2(d=d, e=e, f=f)
Here you can see that I had to redefine the defaults in the main_function. In larger codebases, I find bugs often arise because defaults are defined in multiple places, and somebody changes them in a lower-level function but fails to realize that they are still defined differently in a higher function.
The only way I currently see to achieve this is not very nice at all, and completely obfuscates the signature of the function:
def main_function(**kwargs): return subfunction_1(**{k: v for k, v in kwargs.items() if k in ['a', 'b', 'c']}) + subfunction_2(**{k: v for k, v in kwargs.items() if k in ['d', 'e', 'f']})
Hmm. This might be something where a bit of a helper could, well, help. Is this a pattern that you make use of a lot? Suppose you write your main function like this: @combines(subfunction_1, subfunction_2) def main_function(sf1, sf2): return subfunction_1(**sf1) + subfunction_2(**sf2) or even like this: @precombine def main_function(subfunction_1, subfunction_2): return subfunction_1 + subfunction_2 The decorator could then generate a new function, with a proper signature (available to help()), that hides the messy details of filtering kwargs - and in the case of precombine, actually calls the function as well. (Obviously you can't use this if you might want to call a subfunction conditionally.) Another option would be to have all your subfunctions absorb and ignore all unexpected arguments. def subfunction_1(a=2, b=3, c=4, **_): return a+b*c def subfunction_2(d=5, e=6, f=7, **_): return d*e+f def main_function(**kw): return subfunction_1(**kw) + subfunction_2(**kw) This doesn't help with the signature, but it's an easy thing to do. You'd have to document your parameters separately from your functions; appropriate for something like the subprocess module (where a bunch of functions basically pipe their args straight into Popen), not appropriate for a lot of other places. On the plus side, both of these ideas work with existing Python versions, so you wouldn't have to wait for 3.8 or 3.9 :) ChrisA
Excellent point! I am not fully convinced by the syntax yet, but having proposed something is already very valuable and I do not have a better proposal. As I had to defer defaults countless times, and each times grumped about it, I hope something will come out of this...
That's a problem I've also encountered countless times. But the solution you propose only covers the use cases where we always pass the arguments. If your main function looks like the following, you wouldn't be able to set them as deferred: def main_function(action, a=2, b=3): if action== "product": return a * b if action== "complicated_action": return complicated_action(a=a, b=b) ... Maybe we could make deferred a keywork (only in this context), like this: def main_function(action, deferred a=2, deferred b=3): if action== "product": return a * b # will return 6 if action== "complicated_action": return complicated_action(a=a, b=b)# will return the same as complicated_action() ... deferred variables would just be classic variables some 'deferred' flag, which when they are passed to a function with a default value, are redefined to that value. And probably, when we use them (read them or assign a new value to them), they would lose this deferred flag. Le 20/07/2018 à 11:03, Peter O'Connor a écrit :
Often when programming I run into a situation where it would be nice to have "deferred defaults". Here is an example of what I mean: def subfunction_1(a=2, b=3, c=4): return a+b*c
def subfunction_2(d=5, e=6, f=7): return d*e+f
def main_function(a=2, b=3, c=4, d=5, e=6, f=7): return subfunction_1(a=a, b=b, c=c) + subfunction_2(d=d, e=e, f=f)
Here you can see that I had to redefine the defaults in the main_function. In larger codebases, I find bugs often arise because defaults are defined in multiple places, and somebody changes them in a lower-level function but fails to realize that they are still defined differently in a higher function.
The only way I currently see to achieve this is not very nice at all, and completely obfuscates the signature of the function:
def main_function(**kwargs): return subfunction_1(**{k: v for k, v in kwargs.items() if k in ['a', 'b', 'c']}) + subfunction_2(**{k: v for k, v in kwargs.items() if k in ['d', 'e', 'f']})
What I was thinking was a "deferred" builtin that would just allow a lower function to define the value (and raise an exception if anyone tried to use it before it was defined)
def main_function(a=deferred, b=deferred, c=deferred, d=deferred, e=deferred, f=deferred): return subfunction_1(a=a, b=b, c=c) + subfunction_2(d=d, e=e, f=f)
I assume this has been discussed before somewhere, but couldn't find anything on it, so please feel free to point me towards any previous discussion on the topic.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
A crazy idea, didn't think much about it yet: def subfunc(c,a=0,b=1): Blabla def function(c,d=3, from args(subfunc) import a,b): Blabla return anotherfunc(a+b+c+d,subfunc(c,a,b))
Excellent contributions. I'm going to try to (partially) consolidate what we've got. REVIEW ======= I'll start by reviewing the situation regarding default arguments. There are two basic patterns for default arguments. The first is --- def fn(a=EXP): # body of function --- The second is --- def fn(a=None): if a is None: a = EXP # body of function --- Here, EXP is any Python expression. A fairly gotcha is to use a list, or some other mutable object, as EXP. This happens when you write --- def fn(a=[]): # body of function --- because then EXP = '[]' which will be evaluated just once, and every call fn() will be using the same list! To avoid this you should use the second pattern. I think there may be an example of this in the standard Python tutorial. (An aside. You probably need the second pattern if you EXP is, say, ([], []). Although technically immutable, this value has mutable members. And can't be hashed, or use as a dictionary key or element of a set.) WHEN TO USE None ================= If you want something mutable as the 'default argument' you have to use the second pattern. If your default argument is immutable then you can if you wish use the second pattern. But you don't have to use the second pattern. When you use the second pattern, the expression f(None) means 'create for me the default argument' (and raise an exception if there isn't one). Think about it. For immutable EXP, fn() is the same, whether fn is coded using the first pattern or the second. But the value of fn(None) depends very much on which pattern is used to code fn(). So here's the big conclusion (drum roll): === fn should be coded using the second pattern if we wish to pass None as a sentinel argument to fn. === SUMMARY ========= My suggestion was to use the second pattern to solve Peter O'Connor's original problem. It can be done now, but is a bit verbose, and looses useful information in help(fn). Brice Parent's suggestion was to introduce a keyword deferred, like so --- def fn(deferred a=EXP): # body of function --- which I like to think of as a syntactic shorthand for the second pattern. I sort of think we've now got a reasonable answer for Peter's problem. What do you think, Peter? And Brice, are you happy with my interpretation of your deferred keyword? --- Jonathan
On Fri, Jul 20, 2018 at 1:30 PM, Jonathan Fine
I sort of think we've now got a reasonable answer for Peter's problem. What do you think, Peter? And Brice, are you happy with my interpretation of your deferred keyword?
I think the problem with the "None" approach (second pattern) is that it
forces the writer of the subfunction to write their defaults in a more
awkward way in anticipation of other functions which defer their defaults
to it. Translated to the original example, it would become:
def subfunction_1(a=None, b=None, c=None):
if a is None: a=1
if b is None: b=2
if c is None: c=3
return a+b*c
def subfunction_2(d=None, e=None, f=None):
if d is None: d=5
if e is None: e=6
if f is None: f=7
return d*e+f
def main_function(a=None, b=None, c=None, d=None, e=None, f=None):
return subfunction_1(a=a, b=b, c=c) + subfunction_2(d=d, e=e, f=f)
subfunction_1 may be written by someone totally different from the author
of main_function, and may even be in a different codebase. For the author
of subfunction_1, it makes no sense to use the "None" approach instead of
python's normal default mechanism (since all arguments here are
immutables).
On Fri, Jul 20, 2018 at 1:30 PM, Jonathan Fine
Excellent contributions. I'm going to try to (partially) consolidate what we've got.
REVIEW ======= I'll start by reviewing the situation regarding default arguments. There are two basic patterns for default arguments.
The first is --- def fn(a=EXP): # body of function ---
The second is --- def fn(a=None): if a is None: a = EXP # body of function ---
Here, EXP is any Python expression. A fairly gotcha is to use a list, or some other mutable object, as EXP. This happens when you write --- def fn(a=[]): # body of function --- because then EXP = '[]' which will be evaluated just once, and every call fn() will be using the same list! To avoid this you should use the second pattern. I think there may be an example of this in the standard Python tutorial.
(An aside. You probably need the second pattern if you EXP is, say, ([], []). Although technically immutable, this value has mutable members. And can't be hashed, or use as a dictionary key or element of a set.)
WHEN TO USE None =================
If you want something mutable as the 'default argument' you have to use the second pattern. If your default argument is immutable then you can if you wish use the second pattern.
But you don't have to use the second pattern. When you use the second pattern, the expression f(None) means 'create for me the default argument' (and raise an exception if there isn't one).
Think about it. For immutable EXP, fn() is the same, whether fn is coded using the first pattern or the second. But the value of fn(None) depends very much on which pattern is used to code fn().
So here's the big conclusion (drum roll): === fn should be coded using the second pattern if we wish to pass None as a sentinel argument to fn. ===
SUMMARY ========= My suggestion was to use the second pattern to solve Peter O'Connor's original problem. It can be done now, but is a bit verbose, and looses useful information in help(fn).
Brice Parent's suggestion was to introduce a keyword deferred, like so --- def fn(deferred a=EXP): # body of function --- which I like to think of as a syntactic shorthand for the second pattern.
I sort of think we've now got a reasonable answer for Peter's problem. What do you think, Peter? And Brice, are you happy with my interpretation of your deferred keyword?
--- Jonathan
Hi Peter You make the very good point, that
subfunction_1 may be written by someone totally different from the author of main_function, and may even be in a different codebase. For the author of subfunction_1, it makes no sense to use the "None" approach instead of python's normal default mechanism (since all arguments here are immutables).
Good point. To rephrase, what should we do if we want to use a third party or legacy function, which begins === def fn(a=1, b=2, c=3): # function body === We can solve this by defining a function decorator. Suppose we have a function fix_it, whose argument and return value are both functions. The basic specification of fix_it is that --- fixed_fn = fix_it(fn) --- is in practice equivalent to --- def fixed_fn(a=None, b=None, c=None): if a is None: a = 1 if b is None: b = 2 if c is None: c = 3 # function body for fn # or if you prefer return fn(a, b, c) --- An aside. We can code fix_it by using https://docs.python.org/3/library/inspect.html ===
import inspect def fn(a=1, b=2, c=3): pass ... str(inspect.signature(fn)) '(a=1, b=2, c=3)' ===
You could also use with new code, like so: --- @fix_it def fn(a=1, b=2, c=3): # function body --- I think this helps solve your problem. Is there, Peter, anything else that would be left to do (except, of course, write the fix_it function). Thank you again for your problem and comments. -- Jonathan
Ah, right, the fix_it(fcn) is a nice idea. It might also be a good idea,
if we're making an external library anyway, to have a "deferred" object to
avoid overloading "None" (which may mean something else than "differ
argument"). I implemented the decorator here
https://github.com/petered/peters_example_code/blob/master/peters_example_co...,
and it can be used as:
from deferral import deferrable_args, deferred
@deferrable_args
def subfunction_1(a=2, b=3, c=4):
return a+b*c
@deferrable_args
def subfunction_2(d=5, e=6, f=7):
return d*e+f
def main_function(a=deferred, b=deferred, c=deferred, d=deferred,
e=deferred, f=deferred):
return subfunction_1(a=a, b=b, c=c) + subfunction_2(d=d, e=e, f=f)
assert main_function() == (2+3*4)+(5*6+7)
assert main_function(a=8) == (8+3*4)+(5*6+7)
I still think it would be nice to have this as a built-in python feature,
for a few reasons:
- When using non-differable functions (say in other codebases), we have to
do a bunch of "func = deferrable_args(func)" at the top of the module (or
we can just do them at runtime, but then we're doing inspection every time,
which is probably slow).
- It adds a layer to the call stack for every deferrable function you're in.
- To avoid annoying errors where you've defined an arg as deferred but
forgot to wrap the function in question.
On Fri, Jul 20, 2018 at 3:39 PM, Jonathan Fine
Hi Peter
You make the very good point, that
subfunction_1 may be written by someone totally different from the author of main_function, and may even be in a different codebase. For the author of subfunction_1, it makes no sense to use the "None" approach instead of python's normal default mechanism (since all arguments here are immutables).
Good point. To rephrase, what should we do if we want to use a third party or legacy function, which begins === def fn(a=1, b=2, c=3): # function body ===
We can solve this by defining a function decorator. Suppose we have a function fix_it, whose argument and return value are both functions. The basic specification of fix_it is that --- fixed_fn = fix_it(fn) --- is in practice equivalent to --- def fixed_fn(a=None, b=None, c=None): if a is None: a = 1 if b is None: b = 2 if c is None: c = 3 # function body for fn # or if you prefer return fn(a, b, c) ---
An aside. We can code fix_it by using https://docs.python.org/3/library/inspect.html ===
import inspect def fn(a=1, b=2, c=3): pass ... str(inspect.signature(fn)) '(a=1, b=2, c=3)' ===
You could also use with new code, like so: --- @fix_it def fn(a=1, b=2, c=3): # function body ---
I think this helps solve your problem. Is there, Peter, anything else that would be left to do (except, of course, write the fix_it function).
Thank you again for your problem and comments.
-- Jonathan
Hi Peter
You wrote:
On Fri, Jul 20, 2018 at 3:43 PM, Peter O'Connor
Ah, right, the fix_it(fcn) is a nice idea. It might also be a good idea, if we're making an external library anyway, to have a "deferred" object to avoid overloading "None" (which may mean something else than "differ argument"). I implemented the decorator here https://github.com/petered/peters_example_code/blob/master/peters_example_co...
Oh, well done Peter. Thank you. About 15 lines of code, and the same again for comments. And all for the good of the community (and scratching your own itch). I think we've now got pretty much the right basic ideas for solving your original problem. And your original problem is fairly widespread. So what, do you think, are the next steps? -- Jonathan
On Fri, Jul 20, 2018 at 04:43:56PM +0200, Peter O'Connor wrote:
I still think it would be nice to have this as a built-in python feature, for a few reasons: - When using non-differable functions (say in other codebases), we have to do a bunch of "func = deferrable_args(func)" at the top of the module (or we can just do them at runtime, but then we're doing inspection every time, which is probably slow). - It adds a layer to the call stack for every deferrable function you're in. - To avoid annoying errors where you've defined an arg as deferred but forgot to wrap the function in question.
What makes you think that a built-in deferred feature won't have exactly the same issues? Do you have an implementation that doesn't need to do intraspection? -- Steve
On Fri, Jul 20, 2018 at 5:41 PM, Steven D'Aprano
What makes you think that a built-in deferred feature won't have exactly the same issues? Do you have an implementation that doesn't need to do intraspection?
I don't know about these low level things, but I assume it'd be implemented
in C and wouldn't have the same cost as entering a new function in Python.
I imagine it just being a small modification of the mechanism that Python
already uses to assign values to arguments when a function is called. Is
that not the case?
On Fri, Jul 20, 2018 at 5:41 PM, Steven D'Aprano
On Fri, Jul 20, 2018 at 04:43:56PM +0200, Peter O'Connor wrote:
I still think it would be nice to have this as a built-in python feature, for a few reasons: - When using non-differable functions (say in other codebases), we have to do a bunch of "func = deferrable_args(func)" at the top of the module (or we can just do them at runtime, but then we're doing inspection every time, which is probably slow). - It adds a layer to the call stack for every deferrable function you're in. - To avoid annoying errors where you've defined an arg as deferred but forgot to wrap the function in question.
What makes you think that a built-in deferred feature won't have exactly the same issues? Do you have an implementation that doesn't need to do intraspection?
-- Steve _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
On Fri, Jul 20, 2018 at 05:54:44PM +0200, Peter O'Connor wrote:
On Fri, Jul 20, 2018 at 5:41 PM, Steven D'Aprano
wrote: What makes you think that a built-in deferred feature won't have exactly the same issues? Do you have an implementation that doesn't need to do intraspection?
I don't know about these low level things, but I assume it'd be implemented in C and wouldn't have the same cost as entering a new function in Python. I imagine it just being a small modification of the mechanism that Python already uses to assign values to arguments when a function is called. Is that not the case?
I don't know. That's why I'm asking why you think it wouldn't. -- Steve
On Fri, Jul 20, 2018 at 11:03:12AM +0200, Peter O'Connor wrote:
Often when programming I run into a situation where it would be nice to have "deferred defaults". Here is an example of what I mean:
def subfunction_1(a=2, b=3, c=4): return a+b*c
def subfunction_2(d=5, e=6, f=7): return d*e+f
def main_function(a=2, b=3, c=4, d=5, e=6, f=7): return subfunction_1(a=a, b=b, c=c) + subfunction_2(d=d, e=e, f=f)
Here you can see that I had to redefine the defaults in the main_function.
Perhaps you mean duplicate, or repeat, or copy. But surely they're not redefined -- then they would have different values. Being able to redefine the defaults in a wrapper function is a feature. Putting aside the terminology, I think this is a minor annoyance: DRY violations when setting default values.
In larger codebases, I find bugs often arise because defaults are defined in multiple places, and somebody changes them in a lower-level function but fails to realize that they are still defined differently in a higher function.
Changing function defaults in production code without a period of deprecation and warnings is a no-no. Default values are a part of the function API, and changing the public API of a function without warning is asking for trouble. But during development, there are no such constraints (your only users are your development team) and it is a real nuisance keeping defaults in sync across multiple functions and/or classes. I've raised this issue in the past: https://mail.python.org/pipermail/python-list/2016-September/714546.html and I still don't have a great solution for it.
The only way I currently see to achieve this is not very nice at all, and completely obfuscates the signature of the function: [...]
There are lots of other ways to solve this. None of which are great.
What I was thinking was a "deferred" builtin that would just allow a lower function to define the value (and raise an exception if anyone tried to use it before it was defined) [...] I assume this has been discussed before somewhere, but couldn't find anything on it, so please feel free to point me towards any previous discussion on the topic.
https://mail.python.org/pipermail/python-ideas/2011-July/010678.html -- Steve
On 20 July 2018 at 22:45, Steven D'Aprano
Perhaps you mean duplicate, or repeat, or copy. But surely they're not redefined -- then they would have different values. Being able to redefine the defaults in a wrapper function is a feature.
Putting aside the terminology, I think this is a minor annoyance: DRY violations when setting default values.
FWIW, I tend to handle this problem the same way I handle other DRY problems with magic constants: give the default value a name and either export it directly, or export an API for retrieving it. If that results in name sprawl ("But now I have 15 defaults to export!"), then I take it as a hint that I may not be modeling my data correctly, and am missing a class definition or two somewhere. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
It might be stupid, but how about solving this problem using the following: from . import other_func, SomeClass def my_func(a=other_func.defaults.a, b=other_func.defaults.b, c=SomeClass.some_method.defaults.c): ... or def my_func(a=None, b=None, c=None): # or use some sentinel value instead of None if a is None: a = other_func.defaults.a if b is None: b = other_func.defaults.b if c is None: c = SomeClass.some_method.defaults.c ... or even def my_func(a=None, b=None, c=None): if a is None: a = default(other_func, "a") if b is None: b = default(other_func, "b") if c is None: c = default(SomeClass.some_method, "c") ... I used *.defaults.* but it might be something else, as well as the function I named 'default' which might be anything else. I prefer the first, as it's both short and easy to read, but I'm not sure about the implications about such a thing. And it probably has already been proposed for other use cases. The second and third versions are more verbose, but probably easier to implement, specially the third which should already be doable using something like import inspect def default(function, argument): return inspect.signature(function).parameters[argument].default.
I agree this is a problem, which I have seen solved by removing the method signature, which is unfortunate:
def flexible_method(**kwargs): # Read the code to find out the expected parameters
I have an @override decorator to handle this type of pattern. It will perform the null-coalescing with properties found in a special "kwargs" parameter. "kwargs" is assigned a dict that has a copy of the method arguments. The value of a callee's argument is, in order, * a not None value provided by the caller or * a not None value found in the kwargs dict or * the default value provided by the method declaration or * None I was not clear on where you wanted to define your defaults. Either like this:
@override def subfunction_1(a=None, b=None, c=None, kwargs=None): return a+b*c
@override def subfunction_2(d=None, e=None, f=None, kwargs=None): return d*e+f
@orverride def main_function(a=2, b=3, c=4, d=5, e=6, f=7, kwargs=None): return subfunction_1(a, b, c) + subfunction_2(d, e, f) return subfunction_1(kwargs) + subfunction_2(kwargs) # IF YOU WANT TO BE LAZY
or like this:
@override def subfunction_1(a=2, b=3, c=4, kwargs=None): return a+b*c
@override def subfunction_2(d=5, e=6, f=7, kwargs=None): return d*e+f
@orverride def main_function(a=None, b=None, c=None, d=None, e=None, f=None, kwargs=None): return subfunction_1(a, b, c) + subfunction_2(d, e, f) return subfunction_1(kwargs) + subfunction_2(kwargs) # IF YOU WANT TO BE LAZY
both are identical except for where you declare the default values. https://github.com/klahnakoski/mo-kwargs
Someone wrote : Thank you for your deferred default values idea, which we're now working on together.
https://github.com/petered/peters_example_code/blob/master/peters_example_co...
Allowing to write:
from deferral import deferrable_args, deferred
@deferrable_args
def f(x, y=2, z=3):
return (x,y,z)
f(5, deferred, 7) == (5,2,7)
(I'd rename "deferrable_args" to simply "deferrable")
The api chosen in deferall.py is a deferall.deferred, one could also use
None or Ellipsis ? That looks nice :
from deferral import elideferrable
@elideferrable
def f(x, y=2, z=3):
return (x,y,z)
f(5, ..., 7) == (5, 2, 7)
from deferral import nonedeferrable
@nonedeferrable
def f(x, y=2, z=3):
return (x,y,z)
f(5, None, 7) == (5, 2, 7)
Le mar. 24 juil. 2018 à 14:26, Kyle Lahnakoski
I agree this is a problem, which I have seen solved by removing the method signature, which is unfortunate:
def flexible_method(**kwargs): # Read the code to find out the expected parameters
I have an @override decorator to handle this type of pattern. It will perform the null-coalescing with properties found in a special "kwargs" parameter. "kwargs" is assigned a dict that has a copy of the method arguments. The value of a callee's argument is, in order,
* a not None value provided by the caller or * a not None value found in the kwargs dict or * the default value provided by the method declaration or * None
I was not clear on where you wanted to define your defaults. Either like this:
@override def subfunction_1(a=None, b=None, c=None, kwargs=None): return a+b*c
@override def subfunction_2(d=None, e=None, f=None, kwargs=None): return d*e+f
@orverride def main_function(a=2, b=3, c=4, d=5, e=6, f=7, kwargs=None): return subfunction_1(a, b, c) + subfunction_2(d, e, f) return subfunction_1(kwargs) + subfunction_2(kwargs) # IF YOU
WANT TO BE LAZY
or like this:
@override def subfunction_1(a=2, b=3, c=4, kwargs=None): return a+b*c
@override def subfunction_2(d=5, e=6, f=7, kwargs=None): return d*e+f
@orverride def main_function(a=None, b=None, c=None, d=None, e=None, f=None,
kwargs=None):
return subfunction_1(a, b, c) + subfunction_2(d, e, f) return subfunction_1(kwargs) + subfunction_2(kwargs) # IF YOU
WANT TO BE LAZY
both are identical except for where you declare the default values.
https://github.com/klahnakoski/mo-kwargs
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
participants (9)
-
Brice Parent
-
Chris Angelico
-
Grégory Lielens
-
Jonathan Fine
-
Kyle Lahnakoski
-
Nick Coghlan
-
Peter O'Connor
-
Robert Vanden Eynde
-
Steven D'Aprano