Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-27 Thread Robert Vanden Eynde
> 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_code/deferral.py

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  a
écrit :

>
> 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/
>
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-24 Thread 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/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread Steven D'Aprano
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
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread Peter O'Connor
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?



On Fri, Jul 20, 2018 at 5:41 PM, Steven D'Aprano 
wrote:

> 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/
>
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread Nick Coghlan
On 20 July 2018 at 22:45, Steven D'Aprano  wrote:
> 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   |   ncogh...@gmail.com   |   Brisbane, Australia
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread 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/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread Jonathan Fine
Hi Peter

You wrote:
On Fri, Jul 20, 2018 at 3:43 PM, Peter O'Connor
 wrote:
> 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_code/deferral.py

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
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread 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
,
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  wrote:

> 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
>
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread Brice Parent

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.
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread 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
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread Steven D'Aprano
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
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread Peter O'Connor
On Fri, Jul 20, 2018 at 1:30 PM, Jonathan Fine  wrote:
>
>
> 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  wrote:

> 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
>
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread 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
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread Grégory Lielens
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))
  

___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread Grégory Lielens
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...
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread Chris Angelico
On Fri, Jul 20, 2018 at 7:03 PM, 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.
> 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
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Idea: Deferred Default Arguments?

2018-07-20 Thread Jonathan Fine
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
 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.
> 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/
>
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/