Re: [Python-ideas] Proposal: A Reduce-Map Comprehension and a "last" builtin

2018-04-08 Thread Jacco van Dorp
> With the increased emphasis on iterators and generators in Python 3.x,
> the lack of a simple expression level equivalent to "for item in
> iterable: pass" is occasionally irritating, especially when
> demonstrating behaviour at the interactive prompt.

I've sometimes thought that exhaust(iterator) or iterator.exhaust() would be
a good thing to have - I've often wrote code doing basically "call this function
for every element in this container, and idc about return values", but find
myself using a list comprehension instead of generator. I guess it's such an
edge case that exhaust(iterator) as builtin would be overkill (but perhaps
itertools could have it ?), and most people don't pass around iterators, so
(f(x) for x in y).exhaust() might not look natural to most people. It
could return
the value for the last() semantics, but I think exhaustion would often be more
important than the last value.

2018-04-09 0:58 GMT+02:00 Greg Ewing :
> Kyle Lahnakoski wrote:
>
>> Consider Serhiy Storchaka's elegant solution, which I reformatted for
>> readability
>>
>>> smooth_signal = [
>>> average
>>>for average in [0]
>>>for x in signal
>>> for average in [(1-decay)*average + decay*x]
>>> ]
>
>
> "Elegant" isn't the word I would use, more like "clever".
> Rather too clever, IMO -- it took me some head scratching
> to figure out how it does what it does.
>
> And it would have taken even more head scratching, except
> there's a clue as to *what* it's supposed to be doing:
> the fact that it's assigned to something called
> "smooth_signal" -- one of those "inaccurate names" that
> you disparage so much. :-)
>
> --
> Greg
>
> ___
> 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] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]

2018-04-08 Thread Raymond Hettinger


> On Apr 8, 2018, at 6:43 PM, Tim Peters  wrote:
> 
>> My other common case for accumulate() is building cumulative
>> probability distributions from probability mass functions (see the
>> code for random.choice() for example, or typical code for a K-S test).
> 
> So, a question:  why wasn't itertools.accumulate() written to accept
> iterables of _only_ numeric types?  Akin to `sum()`.  I gather from
> one of Nick's messages that it was so restricted in 3.2.  Then why was
> it generalized to allow any 2-argument function?

Prior to 3.2, accumulate() was in the recipes section as pure Python code.  It 
had no particular restriction to numeric types.

I received a number of requests for accumulate() to be promoted to a real 
itertool (fast, tested, documented C code with a stable API).  I agreed and 
accumulate() was added to itertools in 3.2.  It worked with anything supporting 
__add__, including str, bytes, lists, and tuples.  More specifically, 
accumulate_next() called PyNumber_Add() without any particular type restriction.

Subsequently, I got requests to generalize accumulate() to support any arity-2 
function (with operator.mul offered as the motivating example).  Given that 
there were user requests and there were ample precedents in other languages, I 
acquiesced despite having some reservations (if used with a lambda, the 
function call overhead might make accumulate() slower than a plain Python 
for-loop without the function call). So, that generalized API extension went 
into 3.3 and has remained unchanged ever since.

Afterwards, I was greeted with the sound of crickets.  Either it was nearly 
perfect or no one cared or both ;-)  

It remains one of the least used itertools.


> Given that it was, `sum()` is no longer particularly relevant:  the
> closest thing by far is now `functools.reduce()`, which does support
> an optional `initial` argument.  Which it really should, because it's
> impossible for the implementation to guess a suitable starting value
> for an arbitrary user-supplied dyadic function.
> 
> My example using accumulate() to generate list prefixes got snipped,
> but same thing there:  it's impossible for that snippet to work unless
> an empty list is supplied as the starting value.  And it's impossible
> for the accumulate() implementation to guess that.

Honestly, I couldn't immediately tell what this code was doing:

list(accumulate([8, 4, "k"], lambda x, y: x + [y], first_result=[]))

This may be a case where a person would be better-off without accumulate() at 
all.


> In short, for _general_ use `accumulate()` needs `initial` for exactly
> the same reasons `reduce()` needed it.

The reduce() function had been much derided, so I've had it mentally filed in 
the anti-pattern category.  But yes, there may be wisdom there.


> BTW, the type signatures on the scanl (requires an initial value) and
> scanl1 (does not support an initial value) implementations I pasted
> from Haskell's Standard Prelude give a deeper reason:  without an
> initial value, a list of values of type A can only produce another
> list of values of type A via scanl1.  The dyadic function passed must
> map As to As.  But with an initial value supplied of type B, scanl can
> transform a list of values of type A to a list of values of type B.
> While that may not have been obvious in the list prefix example I
> gave, that was at work:  a list of As was transformed into a list _of_
> lists of As.  That's impossible for scanl1 to do, but easy for scanl.

Thanks for pointing that out.  I hadn't considered that someone might want to 
transform one type into another using accumulate().  That is pretty far from my 
mental model of what accumulate() was intended for.  Also, I'm still not sure 
whether we would want code like that buried in an accumulate() call rather than 
as a regular for-loop where I can see the logic and trace through it with pdb.

As for scanl, I'm not sure what this code means without seeing some python 
equivalent.

scanl:: (a -> b -> a) -> a -> [b] -> [a]
scanl f q xs =  q : (case xs of
   []   -> []
   x:xs -> scanl f (f q x) xs)


scanl1   :: (a -> a -> a) -> [a] -> [a]
scanl1 f (x:xs)  =  scanl f x xs
scanl1 _ []  =  []


> Or, in short, someone coming from a typed functional language
> background sees all sorts of things that rarely (if ever) come up in
> number-crunching languages.  Their sensibilities should count too -
> although not much ;-)  They should get _some_ extra consideration in
> this context, though, because `itertools` is one of the first things
> they dig into when they give Python a try.

I concur.


>> and it would have been distracting to even had the option.
> 
> Distracting for how long?  One second or two? ;-)

Possibly forever.  In my experience, if a person initially frames a problem 
wrong (or perhaps in a hard to solve way), it can take them a long time 

Re: [Python-ideas] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]

2018-04-08 Thread Greg Ewing

Raymond Hettinger wrote:

For neither of those use case categories did I ever want an initial value and
it would have been distracting to even had the option. For example, when
doing a discounted cash flow analysis, I was taught to model the various
flows as a single sequence of up and down arrows rather than thinking of the
initial balance as a distinct concept¹


There's always an initial value, even if it's implicit.

The way accumulate() works can be thought of in two ways:

(1) The initial value is implicitly the identity of whatever
operation the function performs.

(2) The first item in the list is the initial value, and
the rest are items to be accumulated.

Both of these are somewhat dodgy, IMO. The first one works
only if the assumed identity is what you actually want, *and*
there is always at least one item to accumulate.

If those conditions don't hold, you need to insert the
initial value as the first item. But this is almost
certainly going to require extra code. The initial value
and the items are conceptually different things, and are
unlikely to start out in the same list together.

What's more, the first thing the implementation of
accumulate() does is extract the first item and treat it
differently from the rest. So your code goes out of its
way to insert the initial value, and then accumulate()
goes out of its way to pull it out again. Something
smells wrong about that.

As an example, suppose you have a list [1, 2, 3] and
you want to construct [], [1], [1, 2], [1, 2, 3].
To do that with accumulate() you need to write something
like:

   accumulate([[], 1, 2, 3], lambda x, y: x + [y])

The fact that the first element of the list doesn't even
have the same *type* as the rest should be a strong hint
that forcing them to occupy the same list is an
unnatural thing to do.

--
Greg









Because of this background, I was surprised to have the question ever come up
at all (other than the symmetry argument that sum() has "start" so
accumulate() must as well).

When writing itertools.accumulate(), I started by looking to see what other
languages had done.  Since accumulate() is primarily a numerical tool, I
expected that the experience of numeric-centric languages would have
something to teach us.  My reasoning was that if the need hadn't arisen for
APL, R, Numpy, Matlab², or Mathematica, perhaps it really was just noise.

My views may be dated though.  Looking at the wheel sieve and collatz glide
record finder, I see something new, a desire to work with lazy, potentially
infinite accumulations (something that iterators do well but almost never
arises in the world of fixed-length sequences or cumulative probability
distributions).

So I had been warming up to the idea, but got concerned that Nick could have
had such a profoundly different idea about what the code should do.  That
cooled my interest a bit, especially when thinking about two key questions,
"Will it create more problems than it solves?" and "Will anyone actually use
it?".



Raymond







¹
http://www.chegg.com/homework-help/questions-and-answers/solve-present-worth-cash-flow-shown-using-three-interest-factors-10-interest-compounded-an-q878034


² https://www.mathworks.com/help/matlab/ref/accumarray.html 
___ 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] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]

2018-04-08 Thread Tim Peters
> >>> list(accumulate([1, 2, 3]))
> [11, 13, 16]

Wow!  I would have sworn that said

[1, 3, 6]

when I sent it.  Damn Gmail ;-)

> >>> list(accumulate([1, 2, 3], initial=10))
>  [10, 11, 13, 16]
___
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] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]

2018-04-08 Thread Tim Peters
[Raymond]
> The Bayesian world view isn't much different except they would
> prefer "prior" instead of "initial" or "start" ;-)
>
> my_changing_beliefs = accumulate(stream_of_new_evidence, bayes_rule, 
> prior=what_i_used_to_think)
>
> Though the two analogies are cute, I'm not sure they tell us much.  In ...

They're not intended to.  They're just intended to shake people loose
from picturing nothing subtler than adding a list of 3 integers ;-)


> My own experience with actually using accumulations in algorithmic
> code falls neatly into two groups.  Many years ago, I used APL
> extensively in accounting work and my recollection is that a part
> of the convenience of "\+" was that the sequence length didn't change
> (so that the various data arrays could interoperate with one another).

Sure.


> My other common case for accumulate() is building cumulative
> probability distributions from probability mass functions (see the
> code for random.choice() for example, or typical code for a K-S test).

So, a question:  why wasn't itertools.accumulate() written to accept
iterables of _only_ numeric types?  Akin to `sum()`.  I gather from
one of Nick's messages that it was so restricted in 3.2.  Then why was
it generalized to allow any 2-argument function?

Given that it was, `sum()` is no longer particularly relevant:  the
closest thing by far is now `functools.reduce()`, which does support
an optional `initial` argument.  Which it really should, because it's
impossible for the implementation to guess a suitable starting value
for an arbitrary user-supplied dyadic function.

My example using accumulate() to generate list prefixes got snipped,
but same thing there:  it's impossible for that snippet to work unless
an empty list is supplied as the starting value.  And it's impossible
for the accumulate() implementation to guess that.

In short, for _general_ use `accumulate()` needs `initial` for exactly
the same reasons `reduce()` needed it.

BTW, the type signatures on the scanl (requires an initial value) and
scanl1 (does not support an initial value) implementations I pasted
from Haskell's Standard Prelude give a deeper reason:  without an
initial value, a list of values of type A can only produce another
list of values of type A via scanl1.  The dyadic function passed must
map As to As.  But with an initial value supplied of type B, scanl can
transform a list of values of type A to a list of values of type B.
While that may not have been obvious in the list prefix example I
gave, that was at work:  a list of As was transformed into a list _of_
lists of As.  That's impossible for scanl1 to do, but easy for scanl.

Or, in short, someone coming from a typed functional language
background sees all sorts of things that rarely (if ever) come up in
number-crunching languages.  Their sensibilities should count too -
although not much ;-)  They should get _some_ extra consideration in
this context, though, because `itertools` is one of the first things
they dig into when they give Python a try.


> For neither of those use case categories did I ever want an initial value

As above, in all your related experiences "0" was a suitable base
value, so you had no reason to care.

> and it would have been distracting to even had the option.

Distracting for how long?  One second or two? ;-)


> ...
> Because of this background, I was surprised to have the question ever
> come up at all (other than the symmetry argument that sum() has "start"
> so accumulate() must as well).

As above, the real parallel here is to reduce().  `sum()` became an
historical red herring when `accumulate()` was generalized.

With a different background, you may just as well have been surprised
if the question _hadn't_ come up.  For example, this is a standard
example in the Haskell world for how to define an infinite Fibonacci
sequence with the initial two values f0 and f1:

fibs = f0 : scanl (+) f1 fibs

The part from `scanl` onward would be spelled in Python as

accumulate(fibs, initial=f1)

although it requires some trickery to get the recursive reference to
work (details on request, but I'm sure you already know how to do
that).


> When writing itertools.accumulate(), I started by looking to see what
> other languages had done.  Since accumulate() is primarily a
> numerical tool, I expected that the experience of numeric-centric
> languages would have something to teach us.  My reasoning was
> that if the need hadn't arisen for APL, R, Numpy, Matlab², or Mathematica,
> perhaps it really was just noise.

In the itertools context, I also would have looked hard at Haskell experience.

BTW, whoever wrote the current `accumulate()` docs also found a use
for `initial`, but hacked around it:

"""
First-order recurrence relations can be modeled by supplying the
initial value in the iterable and using only the accumulated total in
func argument:
"""

followed by:

>>> logistic_map = lambda x, _:  r * x * (1 - x)
>>> r = 3.8
>>> x0 = 0.4
>>> inp

Re: [Python-ideas] Proposal: A Reduce-Map Comprehension and a "last" builtin

2018-04-08 Thread Greg Ewing

Kyle Lahnakoski wrote:


Consider Serhiy Storchaka's elegant solution, which I reformatted for
readability


smooth_signal = [
average
   for average in [0]
   for x in signal
for average in [(1-decay)*average + decay*x]
]


"Elegant" isn't the word I would use, more like "clever".
Rather too clever, IMO -- it took me some head scratching
to figure out how it does what it does.

And it would have taken even more head scratching, except
there's a clue as to *what* it's supposed to be doing:
the fact that it's assigned to something called
"smooth_signal" -- one of those "inaccurate names" that
you disparage so much. :-)

--
Greg
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-08 Thread Steve Dower
# Dict display
data = {
key_a: local_a := 1,
key_b: local_b := 2,
key_c: local_c := 3,
}

Isn’t this a set display with local assignments and type annotations? :o)

(I’m -1 on all of these ideas, btw. None help readability for me, and I read 
much more code than I write.)

Top-posted from my Windows phone

From: Nick Coghlan
Sent: Sunday, April 8, 2018 6:27
To: Chris Angelico
Cc: python-ideas
Subject: Re: [Python-ideas] PEP 572: Statement-Local Name Bindings,take three!

On 23 March 2018 at 20:01, Chris Angelico  wrote:
> Apologies for letting this languish; life has an annoying habit of
> getting in the way now and then.
>
> Feedback from the previous rounds has been incorporated. From here,
> the most important concern and question is: Is there any other syntax
> or related proposal that ought to be mentioned here? If this proposal
> is rejected, it should be rejected with a full set of alternatives.

I was writing a new stdlib test case today, and thinking about how I
might structure it differently in a PEP 572 world, and realised that a
situation the next version of the PEP should discuss is this one:

# Dict display
data = {
key_a: 1,
key_b: 2,
key_c: 3,
}

# Set display with local name bindings
data = {
local_a := 1,
local_b := 2,
local_c := 3,
   }

# List display with local name bindings
data = {
local_a := 1,
local_b := 2,
local_c := 3,
   }

# Dict display
data = {
key_a: local_a := 1,
key_b: local_b := 2,
key_c: local_c := 3,
}

# Dict display with local key name bindings
data = {
local_a := key_a: 1,
local_b := key_b: 2,
local_c := key_c: 3,
}

I don't think this is bad (although the interaction with dicts is a
bit odd), and I don't think it counts as a rationale either, but I do
think the fact that it becomes possible should be noted as an outcome
arising from the "No sublocal scoping" semantics.

Cheers,
Nick.

P.S. The specific test case is one where I want to test the three
different ways of spelling "the current directory" in some sys.path
manipulation code (the empty string, os.curdir, and os.getcwd()), and
it occurred to me that a version of PEP 572 that omits the sublocal
scoping concept will allow inline naming of parts of data structures
as you define them.


-- 
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/

___
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] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]

2018-04-08 Thread Raymond Hettinger

> On Apr 8, 2018, at 12:22 PM, Tim Peters  wrote:
> 
> [Guido]
>> Well if you can get Raymond to agree on that too I suppose you can go ahead.
>> Personally I'm -0 but I don't really write this kind of algorithmic code
>> enough to know what's useful.
> 
> Actually, you do - but you don't _think_ of problems in these terms.
> Neither do I.  For those who do:  consider any program that has state
> and responds to inputs.  When you get a new input, the new state is a
> function of the existing state and the input.

The Bayesian world view isn't much different except they would prefer "prior" 
instead of "initial" or "start" ;-)

my_changing_beliefs = accumulate(stream_of_new_evidence, bayes_rule, 
prior=what_i_used_to_think)

Though the two analogies are cute, I'm not sure they tell us much.  In running 
programs or bayesian analysis, we care more about the result rather than the 
accumulation of intermediate results.

My own experience with actually using accumulations in algorithmic code falls 
neatly into two groups.  Many years ago, I used APL extensively in accounting 
work and my recollection is that a part of the convenience of "\+" was that the 
sequence length didn't change (so that the various data arrays could 
interoperate with one another).  

My other common case for accumulate() is building cumulative probability 
distributions from probability mass functions (see the code for random.choice() 
for example, or typical code for a K-S test).

For neither of those use case categories did I ever want an initial value and 
it would have been distracting to even had the option. For example, when doing 
a discounted cash flow analysis, I was taught to model the various flows as a 
single sequence of up and down arrows rather than thinking of the initial 
balance as a distinct concept¹

Because of this background, I was surprised to have the question ever come up 
at all (other than the symmetry argument that sum() has "start" so accumulate() 
must as well).

When writing itertools.accumulate(), I started by looking to see what other 
languages had done.  Since accumulate() is primarily a numerical tool, I 
expected that the experience of numeric-centric languages would have something 
to teach us.  My reasoning was that if the need hadn't arisen for APL, R, 
Numpy, Matlab², or Mathematica, perhaps it really was just noise.

My views may be dated though.  Looking at the wheel sieve and collatz glide 
record finder, I see something new, a desire to work with lazy, potentially 
infinite accumulations (something that iterators do well but almost never 
arises in the world of fixed-length sequences or cumulative probability 
distributions).

So I had been warming up to the idea, but got concerned that Nick could have 
had such a profoundly different idea about what the code should do.  That 
cooled my interest a bit, especially when thinking about two key questions, 
"Will it create more problems than it solves?" and "Will anyone actually use 
it?".



Raymond







¹ 
http://www.chegg.com/homework-help/questions-and-answers/solve-present-worth-cash-flow-shown-using-three-interest-factors-10-interest-compounded-an-q878034

² https://www.mathworks.com/help/matlab/ref/accumarray.html
___
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] Accepting multiple mappings as positional arguments to create dicts

2018-04-08 Thread Andrés Delfino
 Hi!

I thought that maybe dict could accept several mappings as positional
arguments, like this:

class Dict4(dict):
> def __init__(self, *args, **kwargs):
> if len(args) > 1:
> if not all([isinstance(arg, dict) for arg in args]):
> raise TypeError('Dict4 expected instances of dict since
> multiple positional arguments were passed')
>
> temp = args[0].copy()
>
> for arg in args[1:]:
> temp.update(arg)
>
> super().__init__(temp, **kwargs)
> else:
> super().__init__(*args, **kwargs)
>

AFAIK, this wouldn't create compatibility problems, since you can't pass
two positional arguments now anyways.

It would be useful to solve the "sum/union dicts" discussion, for example:
requests.get(url, params=dict(params, {'foo': bar})

Whar are your thoughts?
___
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] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]

2018-04-08 Thread Tim Peters
[Guido]
> Well if you can get Raymond to agree on that too I suppose you can go ahead.
> Personally I'm -0 but I don't really write this kind of algorithmic code
> enough to know what's useful.

Actually, you do - but you don't _think_ of problems in these terms.
Neither do I.  For those who do:  consider any program that has state
and responds to inputs.  When you get a new input, the new state is a
function of the existing state and the input.  That's `accumulate`!
It generates a sequence of new states as the sequence of incrementally
updated states derived from a sequence of inputs according to the
function passed to accumulate.

In that view of the world, specifying a starting value is merely
specifying the program's initial state - and from that view of the
world, not allowing to specify a starting value is bizarre.

While Will Ness's wheel sieve program, and my Collatz
glide-record-finder, don't _derive_ from that view of the world, it
turns out that specifying (and returning) an initial value is also
useful for them, and despite that they're just doing integer running
sums.

A simple example from the broader view of the world is generating all
the prefixes of a list:

>>> from itertools import *
>>> list(accumulate(chain([[]], [8, 4, "k"]), lambda x, y: x + [y]))
[[], [8], [8, 4], [8, 4, 'k']]

That's obviously easier to follow if written, e.g.,

list(accumulate([8, 4, "k"], lambda x, y: x + [y], first_result=[]))


> I do think that the new parameter name ["first_result"] is ugly. But maybe 
> that's
> the point.

I noted later that the `accumulate` in the Python itertoolz package
names its optional argument "initial".  That's not ugly, and works
just as well for me.
___
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] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]

2018-04-08 Thread Kirill Balunov
2018-04-08 8:19 GMT+03:00 Nick Coghlan :

> A name like "first_result" would also make it clearer to readers that
> passing that parameter has an impact on the length of the output
> series (since you're injecting an extra result), and also that the
> production of the first result skips calling func completely (as can
> be seen in Tim's str coercion example).
>
> So where I'd be -1 on:
>
> >>> list(accumulate(1, 2, 3))
> [1, 3, 6]
> >>> list(accumulate(1, 2, 3, start=0))
> [0, 1, 3, 6]
> >>> list(accumulate(1, 2, 3, start=1))
> [1, 2, 4, 7]
>
> I'd be +1 on:
>
> >>> list(accumulate(1, 2, 3))
> [1, 3, 6]
> >>> list(accumulate(1, 2, 3, first_result=0))
> [0, 1, 3, 6]
> >>> list(accumulate(1, 2, 3, first_result=1))
> [1, 2, 4, 7]
>
>
It is a fair point! But the usual way to understand how to use an
additional argument, is to try it or to look examples in the documentation.

Concerning the topic of relationship between `sum` and `accumulate` I have
another point of view. If `start` means something to the `sum`, there are
no grounds for believing that it should mean the same for
`accumulate`, these functions are not really comparable and fundamentally
different. The closest `sum`s friend is `functools.reduce` which uses
`initial` instead of `start`. ( the documentation uses `initializer` and
the docstring uses `initial`, as for me I prefer the `initial`) and so
there is already a discrepancy.

Having said this I think that it is no matter how it will be named `start`
or `initial` or `first_result` or `first`, which is more suitable. I would
prefer `initial` to be the same as in `itertoolz` package. Regarding the
second point, should it yield one more element if provided - I think
everyone here agrees that yes.

With kind regards,
-gdg
___
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] Proposal: A Reduce-Map Comprehension and a "last" builtin

2018-04-08 Thread Kyle Lahnakoski


On 2018-04-05 21:18, Steven D'Aprano wrote:
> (I don't understand why so many people have such an aversion to writing 
> functions and seek to eliminate them from their code.)
>

I think I am one of those people that have an aversion to writing functions!

I hope you do not mind that I attempt to explain my aversion here. I
want to clarify my thoughts on this, and maybe others will find
something useful in this explanation, maybe someone has wise words for
me. I think this is relevant to python-ideas because someone with this
aversion will make different language suggestions than those that don't.

Here is why I have an aversion to writing functions: Every unread
function represents multiple unknowns in the code. Every function adds
to code complexity by mapping an inaccurate name to specific
functionality. 

When I read code, this is what I see:

>    x = you_will_never_guess_how_corner_cases_are_handled(a, b, c)
>    y =
you_dont_know_I_throw_a_BaseException_when_I_do_not_like_your_arguments(j,
k, l)

Not everyone sees code this way: I see people read method calls, make a
number of wild assumptions about how those methods work, AND THEY ARE
CORRECT!  How do they do it!?  It is as if there are some unspoken
convention about how code should work that's opaque to me.

For example before I read the docs on
itertools.accumulate(list_of_length_N, func), here are the unknowns I see:

* Does it return N, or N-1 values?
* How are initial conditions handled?
* Must `func` perform the initialization by accepting just one
parameter, and accumulate with more-than-one parameter?
* If `func` is a binary function, and `accumulate` returns N values,
what's the Nth value?
* if `func` is a non-cummutative binary function, what order are the
arguments passed? 
* Maybe accumulate expects func(*args)?
* Is there a window size? Is it equal to the number of arguments of `func`?

These are not all answered by reading the docs, they are answered by
reading the code. The code tells me the first value is a special case;
the first parameter of `func` is the accumulated `total`; `func` is
applied in order; and an iterator is returned.  Despite all my
questions, notice I missed asking what `accumulate` returns? It is the
unknown unknowns that get me most.

So, `itertools.accumulate` is a kinda-inaccurate name given to a
specific functionality: Not a problem on its own, and even delightfully
useful if I need it often. 

What if I am in a domain where I see `accumulate` only a few times a
year? Or how about a program that uses `accumulate` in only one place?
For me, I must (re)read the `accumulate` source (or run the caller
through the debugger) before I know what the code is doing. In these
cases I advocate for in-lining the function code to remove these
unknowns. Instead of an inaccurate name, there is explicit code. If we
are lucky, that explicit code follows idioms that make the increased
verbosity easier to read.

Consider Serhiy Storchaka's elegant solution, which I reformatted for
readability

> smooth_signal = [
> average
>     for average in [0]
>     for x in signal
> for average in [(1-decay)*average + decay*x]
> ]

We see the initial conditions, we see the primary function, we see how
the accumulation happens, we see the number of returned values, and we
see it's a list. It is a compact, easy read, from top to bottom. Yes, we
must know `for x in [y]` is an idiom for assignment, but we can reuse
that knowledge in all our other list comprehensions.  So, in the
specific case of this Reduce-Map thread, I would advocate using the list
comprehension. 

In general, all functions introduce non-trivial code debt: This debt is
worth it if the function is used enough; but, in single-use or rare-use
cases, functions can obfuscate.



Thank you for your time.









___
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] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]

2018-04-08 Thread Tim Peters
Another bit of prior art:  the Python itertoolz package also supplies
`accumulate()`, with an optional `initial` argument.  I stumbled into
that when reading a Stackoverflow "how can I do Haskell's scanl in
Python?" question.

https://toolz.readthedocs.io/en/latest/api.html#toolz.itertoolz.accumulate


On Fri, Apr 6, 2018 at 8:02 PM, Raymond Hettinger
 wrote:
>> On Friday, April 6, 2018 at 8:14:30 AM UTC-7, Guido van Rossum wrote:
>> On Fri, Apr 6, 2018 at 7:47 AM, Peter O'Connor  wrote:
>>>   So some more humble proposals would be:
>>>
>>> 1) An initializer to itertools.accumulate
>>> functools.reduce already has an initializer, I can't see any controversy to 
>>> adding an initializer to itertools.accumulate
>>
>> See if that's accepted in the bug tracker.
>
> It did come-up once but was closed for a number reasons including lack of use 
> cases.  However, Peter's signal processing example does sound interesting, so 
> we could re-open the discussion.
>
> For those who want to think through the pluses and minuses, I've put together 
> a Q&A as food for thought (see below).  Everybody's design instincts are 
> different -- I'm curious what you all think think about the proposal.
>
>
> Raymond
>
> -
>
> Q. Can it be done?
> A. Yes, it wouldn't be hard.
>
> _sentinel = object()
>
> def accumulate(iterable, func=operator.add, start=_sentinel):
> it = iter(iterable)
> if start is _sentinel:
> try:
> total = next(it)
> except StopIteration:
> return
> else:
> total = start
> yield total
> for element in it:
> total = func(total, element)
> yield total
>
> Q. Do other languages do it?
> A. Numpy, no. R, no. APL, no. Mathematica, no. Haskell, yes.
>
> * 
> http://docs.scipy.org/doc/numpy/reference/generated/numpy.ufunc.accumulate.html
> * https://stat.ethz.ch/R-manual/R-devel/library/base/html/cumsum.html
> * http://microapl.com/apl/apl_concepts_chapter5.html
>   \+ 1 2 3 4 5
>   1 3 6 10 15
> * https://reference.wolfram.com/language/ref/Accumulate.html
> * https://www.haskell.org/hoogle/?hoogle=mapAccumL
>
>
> Q. How much work for a person to do it currently?
> A. Almost zero effort to write a simple helper function:
>
>myaccum = lambda it, func, start: accumulate(chain([start], it), func)
>
>
> Q. How common is the need?
> A. Rare.
>
>
> Q. Which would be better, a simple for-loop or a customized itertool?
> A. The itertool is shorter but more opaque (especially with respect
>to the argument order for the function call):
>
> result = [start]
> for x in iterable:
>  y = func(result[-1], x)
>  result.append(y)
>
> versus:
>
> result = list(accumulate(iterable, func, start=start))
>
>
> Q. How readable is the proposed code?
> A. Look at the following code and ask yourself what it does:
>
> accumulate(range(4, 6), operator.mul, start=6)
>
>Now test your understanding:
>
> How many values are emitted?
> What is the first value emitted?
> Are the two sixes related?
> What is this code trying to accomplish?
>
>
> Q. Are there potential surprises or oddities?
> A. Is it readily apparent which of assertions will succeed?
>
> a1 = sum(range(10))
> a2 = sum(range(10), 0)
> assert a1 == a2
>
> a3 = functools.reduce(operator.add, range(10))
> a4 = functools.reduce(operator.add, range(10), 0)
> assert a3 == a4
>
> a4 = list(accumulate(range(10), operator.add))
> a5 = list(accumulate(range(10), operator.add, start=0))
> assert a5 == a6
>
>
> Q. What did the Python 3.0 Whatsnew document have to say about reduce()?
> A. "Removed reduce(). Use functools.reduce() if you really need it; however, 
> 99 percent of the time an explicit for loop is more readable."
>
>
> Q. What would this look like in real code?
> A. We have almost no real-world examples, but here is one from a 
> StackExchange post:
>
> def wsieve():   # wheel-sieve, by Will Ness.
> ideone.com/mqO25A->0hIE89
> wh11 = [ 2,4,2,4,6,2,6,4,2,4,6,6, 2,6,4,2,6,4,6,8,4,2,4,2,
>  4,8,6,4,6,2,4,6,2,6,6,4, 2,4,6,2,6,4,2,4,2,10,2,10]
> cs = accumulate(cycle(wh11), start=11)
> yield( next( cs))   #   cf. ideone.com/WFv4f
> ps = wsieve()   # 
> codereview.stackexchange.com/q/92365/9064
> p = next(ps)# 11
> psq = p*p   # 121
> D = dict( zip( accumulate(wh11, start=0), count(0)))   # start 
> from
> sieve = {}
> for c in cs:
> if c in sieve:
> wheel = sieve.pop(c)
> for m in wheel:
> if 

Re: [Python-ideas] PEP 572: Statement-Local Name Bindings, take three!

2018-04-08 Thread Guido van Rossum
On Sun, Apr 8, 2018 at 8:01 AM, Steven D'Aprano  wrote:

> If we really wanted to keep the sublocal scoping, we could make
> list/set/dict displays their own scope too.
>
> Personally, that's the only argument for sublocal scoping that I like
> yet: what happens inside a display should remain inside the display, and
> not leak out into the function.
>

That sounds like a reasonable proposal that we could at least consider. But
I think it will not fly. Presumably it doesn't apply to tuple displays,
because of reasonable examples like ((a := f(), a+1), a+2), and because it
would create an ugly discontinuity between (a := f()) and (a := f(),). But
then switching between [a := f(), a] and (a := f(), a) would create a
discontinuity.

For comprehensions and generator expressions there is no such discontinuity
in the new proposal, since these *already* introduce their own scope.

-- 
--Guido van Rossum (python.org/~guido)
___
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] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]

2018-04-08 Thread Guido van Rossum
Well if you can get Raymond to agree on that too I suppose you can go
ahead. Personally I'm -0 but I don't really write this kind of algorithmic
code enough to know what's useful. I do think that the new parameter name
is ugly. But maybe that's the point.

On Sat, Apr 7, 2018 at 10:26 PM, Tim Peters  wrote:

> Top-posting just to say I agree with Nick's bottom line (changing the
> name to `first_result=`).  I remain just +0.5, although that is up a
> notch from yesterday's +0.4 ;-)
>
> --- nothing new below ---
>
> On Sun, Apr 8, 2018 at 12:19 AM, Nick Coghlan  wrote:
> > On 8 April 2018 at 14:31, Guido van Rossum  wrote:
> >> Given that two respected members of the community so strongly disagree
> >> whether accumulate([], start=0) should behave like accumulate([]) or
> like
> >> accumulate([0]), maybe in the end it's better not to add a start
> argument.
> >> (The disagreement suggests that we can't trust users' intuition here.)
> >
> > The potential ambiguity I see is created mainly by calling the
> > proposed parameter "start", while having it do more than just adjust
> > the individual partial sum calculations by adding an extra partial
> > result to the output series.
> >
> > If it's called something else (e.g. "first_result"), then the
> > potential "sum(iterable, start=start)" misinterpretation goes away,
> > and it can have Tim's desired effect of defining the first output
> > value (effectively prepending it to the input iterable, without the
> > boilerplate and overhead of actually doing so).
> >
> > A name like "first_result" would also make it clearer to readers that
> > passing that parameter has an impact on the length of the output
> > series (since you're injecting an extra result), and also that the
> > production of the first result skips calling func completely (as can
> > be seen in Tim's str coercion example).
> >
> > So where I'd be -1 on:
> >
> > >>> list(accumulate(1, 2, 3))
> > [1, 3, 6]
> > >>> list(accumulate(1, 2, 3, start=0))
> > [0, 1, 3, 6]
> > >>> list(accumulate(1, 2, 3, start=1))
> > [1, 2, 4, 7]
> >
> > I'd be +1 on:
> >
> > >>> list(accumulate(1, 2, 3))
> > [1, 3, 6]
> > >>> list(accumulate(1, 2, 3, first_result=0))
> > [0, 1, 3, 6]
> > >>> list(accumulate(1, 2, 3, first_result=1))
> > [1, 2, 4, 7]
> >
> > Cheers,
> > Nick.
> >
> > --
> > Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
>



-- 
--Guido van Rossum (python.org/~guido)
___
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-08 Thread Steven D'Aprano
On Sun, Apr 08, 2018 at 09:25:33PM +1000, Nick Coghlan wrote:

> I was writing a new stdlib test case today, and thinking about how I
> might structure it differently in a PEP 572 world, and realised that a
> situation the next version of the PEP should discuss is this one:
> 
> # Dict display
> data = {
> key_a: 1,
> key_b: 2,
> key_c: 3,
> }
> 
> # Set display with local name bindings
> data = {
> local_a := 1,
> local_b := 2,
> local_c := 3,
>}

I don't understand the point of these examples. Sure, I guess they would 
be legal, but unless you're actually going to use the name bindings, 
what's the point in defining them?


data = {
1,
(spam := complex_expression),
spam+1,
spam*2,
}

which I think is cleaner than the existing alternative of defining spam 
outside of the set.

And for dicts:

d = {
   'key': 'value',
   (spam := calculated_key): (eggs := calculated_value),
   spam.lower(): eggs.upper(),
   }


> I don't think this is bad (although the interaction with dicts is a
> bit odd), and I don't think it counts as a rationale either, but I do
> think the fact that it becomes possible should be noted as an outcome
> arising from the "No sublocal scoping" semantics.

If we really wanted to keep the sublocal scoping, we could make 
list/set/dict displays their own scope too.

Personally, that's the only argument for sublocal scoping that I like 
yet: what happens inside a display should remain inside the display, and 
not leak out into the function.

So that has taken me from -1 on sublocal scoping to -0.5 if it applies 
to displays.



-- 
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] PEP 572: Statement-Local Name Bindings, take three!

2018-04-08 Thread Nick Coghlan
On 23 March 2018 at 20:01, Chris Angelico  wrote:
> Apologies for letting this languish; life has an annoying habit of
> getting in the way now and then.
>
> Feedback from the previous rounds has been incorporated. From here,
> the most important concern and question is: Is there any other syntax
> or related proposal that ought to be mentioned here? If this proposal
> is rejected, it should be rejected with a full set of alternatives.

I was writing a new stdlib test case today, and thinking about how I
might structure it differently in a PEP 572 world, and realised that a
situation the next version of the PEP should discuss is this one:

# Dict display
data = {
key_a: 1,
key_b: 2,
key_c: 3,
}

# Set display with local name bindings
data = {
local_a := 1,
local_b := 2,
local_c := 3,
   }

# List display with local name bindings
data = {
local_a := 1,
local_b := 2,
local_c := 3,
   }

# Dict display
data = {
key_a: local_a := 1,
key_b: local_b := 2,
key_c: local_c := 3,
}

# Dict display with local key name bindings
data = {
local_a := key_a: 1,
local_b := key_b: 2,
local_c := key_c: 3,
}

I don't think this is bad (although the interaction with dicts is a
bit odd), and I don't think it counts as a rationale either, but I do
think the fact that it becomes possible should be noted as an outcome
arising from the "No sublocal scoping" semantics.

Cheers,
Nick.

P.S. The specific test case is one where I want to test the three
different ways of spelling "the current directory" in some sys.path
manipulation code (the empty string, os.curdir, and os.getcwd()), and
it occurred to me that a version of PEP 572 that omits the sublocal
scoping concept will allow inline naming of parts of data structures
as you define them.


-- 
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] Start argument for itertools.accumulate() [Was: Proposal: A Reduce-Map Comprehension and a "last" builtin]

2018-04-08 Thread Tim Peters
FYI:

[Raymond]
> ...
> Q. Do other languages do it?
> A. Numpy, no. R, no. APL, no. Mathematica, no. Haskell, yes.
>
>...
> * https://www.haskell.org/hoogle/?hoogle=mapAccumL

Haskell has millions of functions ;-)  `mapAccumL` is a God-awful
mixture of Python's map(), reduce(), and accumulate() :-(  The
examples here should convince you it's nearly incomprehensible:

http://zvon.org/other/haskell/Outputlist/mapAccumL_f.html

A more-than-less direct equivalent to Python's `accumulate` is
Haskell's `scanl1`:

http://zvon.org/comp/r/ref-Haskell.html#Functions~Prelude.scanl1

That doesn't allow specifying an initial value.  But, Haskell being
Haskell, the closely related `scanl` function requires specifying an
initial value, which is also the first element of the list it returns:

http://zvon.org/comp/r/ref-Haskell.html#Functions~Prelude.scanl

Of the two, `scanl` is more basic - indeed, the Standard Prelude
defines scanl1 by peeling off the first element of the list and
passing it as the initial value for scanl to use

scanl:: (a -> b -> a) -> a -> [b] -> [a]
scanl f q xs =  q : (case xs of
[]   -> []
x:xs -> scanl f (f q x) xs)


scanl1   :: (a -> a -> a) -> [a] -> [a]
scanl1 f (x:xs)  =  scanl f x xs
scanl1 _ []  =  []

There are also analogous `scanr` and `scanr1` functions for doing
"right-to-left" accumulation.

So, bottom line:  as usual, when looking to Haskell for prior art,,
"all of the above - for a start" applies ;-)
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/