msg198849 - (view) |
Author: Vajrasky Kok (vajrasky) * |
Date: 2013-10-02 15:23 |
>>> from itertools import repeat
>>> repeat(2, -10).__length_hint__()
0
>>> repeat(2, times=-10).__length_hint__()
18446744073709551606
>>> repeat(2, times=-10)
repeat(2, -10)
>>> repeat(2, -10)
repeat(2, 0)
Hereby, I attached two alternatives of patch to make the behaviour consistent.
The first one makes the negative number parameter/keyword ALWAYS means endless.
|
msg198850 - (view) |
Author: Vajrasky Kok (vajrasky) * |
Date: 2013-10-02 15:24 |
The second patch makes the negative number parameter/keyword ALWAYS means 0.
|
msg198865 - (view) |
Author: Raymond Hettinger (rhettinger) * |
Date: 2013-10-02 19:43 |
The preferred behavior is that a negative number always means 0.
That is what lists do:
>>> [1] * (-5)
[]
|
msg198881 - (view) |
Author: Vajrasky Kok (vajrasky) * |
Date: 2013-10-03 04:46 |
Improved the patch which makes negative number *always* means 0. Added comment and put more test.
|
msg199335 - (view) |
Author: Serhiy Storchaka (serhiy.storchaka) * |
Date: 2013-10-09 19:30 |
Raymond's patch looks even more cumbersome than previous code. And it doesn't work in some cases:
>>> it = itertools.cycle([-2, -1])
>>> class Index:
... def __index__(self): return next(it)
...
>>> itertools.repeat(42, times=Index())
repeat(42, -1)
|
msg208306 - (view) |
Author: Terry J. Reedy (terry.reedy) * |
Date: 2014-01-16 20:29 |
Current behaviors 3.4b2
>>> itertools.repeat('a', -1)
repeat('a', 0)
>>> itertools.repeat('a', times=-1)
repeat('a') # forever
>>> itertools.repeat('a', times=-2)
repeat('a', -2)
My opinions (same as what Raymond says -- negative == 0)
The first line is correct in both behavior and representation.
The second line behavior (and corresponding repr) are wrong.
The third line repr is wrong but the behavior is like the first.
Guido's response on pydev, in context about having a signature that can be captured by C and Python. The main different is comment about adding None to the C signature in addition to the Python 'equivalent, which has 'times=None'.
"If I had complete freedom in redefining the spec I would treat
positional and keyword the same, interpret absent or None to mean
"forever" and explicit negative integers to mean the same as zero, and
make repr show a positional integer >= 0 if the repeat isn't None.
But I don't know if that's too much of a change."
I think the only thing different is comment about None.
|
msg208848 - (view) |
Author: Larry Hastings (larry) * |
Date: 2014-01-22 20:36 |
This problem been independently rediscovered by people converting
code to Argument Clinic. A Python signature can't express these
semantics, where a parameter behaves differently depending on
whether it's passed in by keyword or by reference. So Argument
Clinic can't either. I think it would be best if itertools.repeat
behaved like a pure Python function--that is, that it behaved
the same whether "times" was passed in by position or by keyword.
What's I find curious: the documentation is wildly out of sync with the implementation. It says:
itertools.repeat(object[, times])
....
def repeat(object, times=None):
....
http://docs.python.org/3.4/library/itertools.html#itertools.repeat
But repeat() doesn't support passing in None for the times parameter,
if indeed it ever has.
I see two possible choices here.
1) Honor the existing behavior. Change the signature to simply
def repeat(object, times=-1):
and document it that way.
2) Honor the documentation. Change the implementation to
def repeat(object, times=None):
This change could break code. So we'd have to go through a
deprecation cycle. Breaking "times=-1" without a deprecation
cycle is simply not viable at this point.
I could live with either.
|
msg209051 - (view) |
Author: Vajrasky Kok (vajrasky) * |
Date: 2014-01-24 09:14 |
Serhiy said, "Why do you cast PyDict_Size(kwds) to int?"
Sorry, Serhiy! Today I just realized there is your review for this ticket. I couldn't really remember but I think I got conversion warning if I did not downcast it. Something about I shouldn't not have operation involving ssize_t and int in one statement. Strangely when I tested it again, it did not complain. Perhaps I used different distro back then.
Well, since Larry has a patch intersecting with this ticket, I guess we should let him does this work. Thanks, Serhiy!
I tried to reply you in the rietveld but I got an error page.
|
msg209052 - (view) |
Author: Larry Hastings (larry) * |
Date: 2014-01-24 09:17 |
I don't have a patch for this issue. If you're thinking of my "nullable ints" patch, that was just an experiment. I'd prefer we figure out what we're going to do on this issue, and we can talk abut conversion to Argument Clinic later.
|
msg209057 - (view) |
Author: Vajrasky Kok (vajrasky) * |
Date: 2014-01-24 10:16 |
Here is the updated patch. I try to be conservative here, preserving the current behaviour.
Negative times is same as zero.
Default value (which can be passed by omitting times) means unlimited.
I did not change the signature "repeat(object [,times])" because the only suitable default value, in my opinion, is "repeat(object [,times=<unlimited>])" which is impossible at the moment.
Well, let see what Raymond has anything to say about this.
|
msg209058 - (view) |
Author: Vajrasky Kok (vajrasky) * |
Date: 2014-01-24 10:21 |
def repeat(object, times=-1): => it could break the code in the wild.
Current behaviour: repeat(object) -> unlimited, repeat(object, -1) -> 0 repetitions.
|
msg209063 - (view) |
Author: Larry Hastings (larry) * |
Date: 2014-01-24 10:59 |
Your patch does not address my concern.
My concern is that itertools.repeat doesn't parse its arguments like other Python functions. It behaves differently depending on whether "times" is passed by position or by keyword. Therefore its actual calling signature cannot be represented accurately with an inspect.Signature object.
Let me state this precisely. Currently, inspect.signature doesn't produce a Signature object for itertools.repeat. But let's assume for the moment that it did, and that the default value for the "times" parameter was -1. Then, for this code:
sig = inspect.signature(itertools.repeat)
a = itertools.repeat('a')
b = itertools.repeat('a', sig.parameters['times'].default)
c = itertools.repeat('a', times=sig.parameters['times'].default)
I'd expect the a, b, and c objects to behave identically and be interchangeable. Passing in the default value for an optional parameter should always produce the same result as not passing in a value for that parameter, and for positional-or-keyword parameters it shouldn't matter whether that's done by position or by keyword.
However, b is different from a and c: a and c yields infinitely-many 'a's, whereas b never yields anything.
I want to see a patch where, after applying the patch, a b and c would be interchangeable. Such a patch should be *simpler* than the existing code, as it would remove all the special-case code that examines the length of the args tuple.
"Special cases aren't special enough to break the rules." I think itertools.repeat's argument parsing should stop breaking the rules.
|
msg209152 - (view) |
Author: Vajrasky Kok (vajrasky) * |
Date: 2014-01-25 05:11 |
Larry said, "It behaves differently depending on whether "times" is passed by position or by keyword."
And that is the bug. It should be the same no matter whether we send times through positional or keyword. As Raymond has said earlier in this thread, "The preferred behavior is that a negative number always means 0."
So negative times through keyword must mean zero repetitions.
So it means we cannot set -1 as a default value for times because it will upset many people.
I am not sure at the moment in itertools.repeat,
sig = inspect.signature(itertools.repeat)
a = itertools.repeat('a')
b = itertools.repeat('a', sig.parameters['times'].default)
c = itertools.repeat('a', times=sig.parameters['times'].default)
a & b & c can and should be interchangebly. b & c, yeah, should be interchangeably. But not a. a is a special case. Of course, if we were to design this function from scratch again, we should make a & b & c interchangeably. That is the ideal case. We should be able to send default value explicitly and manually. But we are in beta phase. Can we alter the behaviour in this phase?
But if we have to make a & b & c interchangeably in Python 3.4 (for philosophical or pragmatic reason), I think None is sensible default value, and your nullable-int patch works in this case. And I think we need to get approval from Raymond Hettinger.
|
msg209157 - (view) |
Author: Serhiy Storchaka (serhiy.storchaka) * |
Date: 2014-01-25 06:43 |
Vajrasky's last patch (v3) in general LGTM. I'm not sure about documentation changes, sample Python implementation is only a demonstration, it shouldn't be exact equivalent.
|
msg209185 - (view) |
Author: Larry Hastings (larry) * |
Date: 2014-01-25 10:37 |
The logic of the patch is hard to follow. However, it's still examining the size of args, and now it's examining the size of kwargs, and behaving differently based on these sizes is exactly what I don't want.
I've attached an example patch of how I would change itertools.repeat if I were doing it today, without the benefit of Argument Clinic. If that approach seems reasonable, maybe we can redo it with Argument Clinic and get a signature too.
|
msg209195 - (view) |
Author: Mark Lawrence (BreamoreBoy) * |
Date: 2014-01-25 12:58 |
msg209063 from Larry states "My concern is that itertools.repeat doesn't parse its arguments like other Python functions." From my viewpoint the only long term option is to bring repeat (and other functions that you may have found exhibiting this behaviour during the AC process) into line with other Python functions. If that means a deprecation period and not being able to use AC in the short term so be it, or have I again misunderstood the technical aspects that you've already discussed, in which case you can all give me a good kicking? :)
|
msg209196 - (view) |
Author: Larry Hastings (larry) * |
Date: 2014-01-25 13:20 |
I think you've understood it.
The problem is, in order to deprecate the behavior, we first must provide the new behavior. (That's official policy, in PEP 5.) It's untenable to say "you'll have to stop using 'times=-1' in the future, but you can't use 'times=None' yet". And right now I'm pretty sure we shouldn't add "times=None" for 3.4. Therefore, we can't even deprecate the old behavior yet.
|
msg209209 - (view) |
Author: Mark Lawrence (BreamoreBoy) * |
Date: 2014-01-25 15:39 |
From a user perspective the docs say this "itertools.repeat(object[, times]) - Make an iterator that returns object over and over again. Runs indefinitely unless the times argument is specified." So to me the use of "Times=None" in the "equivalent to" section is simply a red herring, as the user often won't bother reading this and certainly won't see it from interactive help. As for negative times values I'd say leave it as is, although it someone was to suggest deprecating this behaviour and raising a ValueError instead I'd have no objections.
|
msg209277 - (view) |
Author: Vajrasky Kok (vajrasky) * |
Date: 2014-01-26 05:00 |
Larry said, "A proper fix for the "bug" will require changing the semantics of the function.
It's inappropriate to do that in 2.7, 3.3, and (now that we're in beta) 3.4."
I think we can not have it all and need to be pragmatic in this ticket. While we can not fix the default value quirk in 2.7, 3.3, and 3.4, at least we can make the negative times consistent whether it is sent through positional or keyword.
Otherwise, we have to fix the doc:
repeat(object [,times])
create an iterator which returns the object for the specified number of times. If not specified, returns the object endlessly.If times is negative and is sent via positional, it means zero repetitions. But if times is negative and is sent via keyword, it means endless repetition.
And it doesn't feel right, does it?
|
msg209278 - (view) |
Author: Larry Hastings (larry) * |
Date: 2014-01-26 05:02 |
Raymond said "The preferred behavior is that a negative number always means 0." My proposal honors that.
We certainly aren't changing this in 3.3 and 2.7, and as release manager for 3.4 I'm pretty sure we aren't changing it there.
|
msg209390 - (view) |
Author: Alexander Belopolsky (belopolsky) * |
Date: 2014-01-27 04:00 |
If "equivalent to" code is not considered to be part of documentation, then the meaning of negative times should be documented.
|
msg209410 - (view) |
Author: Vajrasky Kok (vajrasky) * |
Date: 2014-01-27 07:37 |
How to handle this "problem" in Python maintenance releases (2.7, 3.3 and soon-to-be 3.4) is being discussed here: https://mail.python.org/pipermail/python-dev/2014-January/132101.html
In case, we are taking "backporting the full fix" road, here is the patch. I modified Larry's patch based on Serhiy's suggestion and bettered the error message, added test, and fixed the doc.
"Better error message" -> without my fix, repeat('a', times='cutecat') will throw "an integer is required". I make it as such it will throw "times must be integer or None."
|
msg209440 - (view) |
Author: Larry Hastings (larry) * |
Date: 2014-01-27 12:49 |
For what it's worth: I figured out how this happened. Maybe it's obvious to you, but this behavior baffled me until I went back and looked at the revision history.
In revision e260d6daf784, the argument parsing for itertools.repeat looks like this:
Py_ssize_t cnt = -1;
if (type == &repeat_type && !_PyArg_NoKeywords("repeat()", kwds))
return NULL;
if (!PyArg_ParseTuple(args, "O|n:repeat", &element, &cnt))
return NULL;
if (PyTuple_Size(args) == 2 && cnt < 0)
cnt = 0;
In the subsequent revision, 3dbdbc5e6d85, it was changed to this:
Py_ssize_t cnt = -1;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|n:repeat", kwargs,
&element, &cnt))
return NULL;
if (PyTuple_Size(args) == 2 && cnt < 0)
cnt = 0;
The original intent is now clear: only allow "cnt" to be -1 if it wasn't specified as an argument. The author simply forgot that "times" could now be passed in as a keyword argument too.
What the author *probably* wanted was something like this:
PyObject *times_keyword;
...
times_keyword = PyDict_GetItemString(kwargs, "times");
Py_XDECREF(times_keyword);
if ((PyTuple_Size(args) == 2 || times_keyword) && cnt < 0)
cnt = 0;
But I suggest it's far too late to change it to that now.
|
msg210606 - (view) |
Author: Vajrasky Kok (vajrasky) * |
Date: 2014-02-08 10:13 |
Here is the ultimate patch for this bug. The doc fix is based on Larry's writing: https://mail.python.org/pipermail/python-dev/2014-January/132156.html
I made sure the patch could be compiled by Sphinx and displayed nicely. I added test and comment in the code.
|
msg221224 - (view) |
Author: Raymond Hettinger (rhettinger) * |
Date: 2014-06-22 07:15 |
I'm inclined to apply Vajrasky Kok's third version (with minor cleanups).
The rule will be pretty much what Guido stated but without adding a special case for times=None (that could be an added enhancement at a later time if the need arose): "If I had complete freedom in redefining the spec I would treat positional and keyword the same, interpret absent or None to mean "forever" and explicit negative integers to mean the same as zero, and make repr show a positional integer >= 0 if the repeat isn't None."
The if-absent-run-forever rule matches what the decade old positional-only API does and what the newer keyword form does as well. It also matches what the documented rough equivalent code does.
The negative-times-means-zero rule matches the current positional-only api, it matches list.__mul__ and str.__mul__, and it matches the documented equivalent code. However, it does change the meaning of the keyword argument when the value is negative (the part that conflicts with the positional API, was never intended, nor was promised in the docs).
Larry's false dilemmas aside, I think that takes care of the core issue that a negative value for a keyword times-argument does not have the same behavior as it would for a positional times-argument.
The use of "None" for an optional argument in the "equivalent" code is red herring. As Serhiy says, the "sample Python implementation is only a demonstration, it shouldn't be exact equivalent." If Larry still perceives this to be "wildly out of sync", it isn't hard to put in the usual "times=sentinel" setup in the same code, but that only adds a little precision at the expense of clarity (i.e. readers are more likely to be confused by the sentinel trick than by the conventional way of noting optional arguments with None).
|
msg221225 - (view) |
Author: Larry Hastings (larry) * |
Date: 2014-06-22 07:24 |
Please clarify, what is my "false dilemma"?
|
msg221237 - (view) |
Author: Raymond Hettinger (rhettinger) * |
Date: 2014-06-22 08:58 |
> Please clarify
I was referring to your first post, "I see two possible choices here ... [changing the signature to times=-1 or to times=None]".
There was another choice, make the code work as originally intended where omitting the times argument repeats indefinitely and where negative values are treated the same as zero.
|
msg221522 - (view) |
Author: Roundup Robot (python-dev) |
Date: 2014-06-25 04:39 |
New changeset dce9dbc8e892 by Raymond Hettinger in branch '3.4':
Issue #19145: Fix handling of negative values for a "times" keyword argument to itertools.repeat()>
http://hg.python.org/cpython/rev/dce9dbc8e892
|
msg221523 - (view) |
Author: Roundup Robot (python-dev) |
Date: 2014-06-25 04:53 |
New changeset 85dc4684c83e by Raymond Hettinger in branch '2.7':
Issue #19145: Fix handling of negative values for a "times" keyword argument to itertools.repeat()>
http://hg.python.org/cpython/rev/85dc4684c83e
|
msg221526 - (view) |
Author: Larry Hastings (larry) * |
Date: 2014-06-25 06:24 |
The main thing for me isn't that the function and its documentation-pseudocode are in sync (though in the long run this is desirable). What's important to me is that the function have a sensible, relevant signature in Python. There was simply no way to express the "times argument behaves differently when passed in by position vs. by argument" semantics in a signature.
I agree the new implementation is an improvement. But there's still no way to represent repeat's signature in Python. This is because "times" is an optional argument without a valid default value. It should always hold true that calling a function and explicitly passing in an optional argument's default value should behave identically to not specifying that argument. But there's no value I can pass in to "times" that results in the same behavior as not passing in "times". That's why I prefer the "times=None" approach.
At some point I expect to get "nullable ints" into Argument Clinic (see #20341 ). Once that's in, I propose we convert itertools.repeat to work with Argument Clinic, as follows:
* We use a nullable int for the "times" parameter.
* The "times" parameter would have a default of None.
* If times=None, repeat would repeat forever.
repeat would then have an accurate signature.
Raymond: does that sound reasonable?
|
msg221528 - (view) |
Author: Raymond Hettinger (rhettinger) * |
Date: 2014-06-25 06:48 |
> there's still no way to represent repeat's signature in Python
There is a way using *args and **kwds but that isn't any fun (just like range() or min() in that regard).
Right now, repeat() does what it is supposed to do. It may currently be inconvenient for the argument clinic, but changing repeat() in way that no one currently needs smacks of having the tail wag the dog ;-)
What I would like to see in the future is better support for optional arguments in
PyArg_ParseTupleAndKeywords. Right now, "O|n:repeat" precludes us from using None or some sentinel object to mean the same as the-argument-was-omitted. To put a None default value in now, we would have to do tricks with "O|O" and then manually write the "|n" validation, type conversion, and associated error messages.
|
msg221531 - (view) |
Author: Larry Hastings (larry) * |
Date: 2014-06-25 07:17 |
> There is a way using *args and **kwds but that isn't any fun
That's why, earlier, I said a "sensible" signature. Every function *could* get the signature "(*args, **kwargs)" but this imparts no useful semantic information.
> What I would like to see in the future is better support
> for optional arguments in PyArg_ParseTupleAndKeyword
It sounds to me like you're proposing adding "nullable int" support to PyArg_ParseTuple*. I'm not going to; I see Argument Clinic as the way forward, and I'm adding it there instead.
In general I'd rather see work go into AC than into PyArg_ParseTuple*. I think PyArg_ParseTuple* is already too complicated, and using AC gives the function a signature for free. My hope is to increase the value proposition of AC so much that everyone agrees with me and we deprecate (but don't remove!) PyArg_ParseTuple*. :D
> changing repeat() in way that no one currently needs smacks of having
> the tail wag the dog
I concede that nobody (probably) needs a workable default value for the times argument. But I suggest that giving functions sensible signatures is a worthy goal in its own right, and that the "times=None" semantics will get us there in a reasonable, backwards-compatible way.
|
msg221611 - (view) |
Author: Vajrasky Kok (vajrasky) * |
Date: 2014-06-26 16:00 |
Raymond, thanks for committing my patch but my name was already put into ACKS before this commit.
$ grep -R Vajrasky Misc/ACKS
Vajrasky Kok
Vajrasky Kok
|
msg221614 - (view) |
Author: Roundup Robot (python-dev) |
Date: 2014-06-26 16:25 |
New changeset 463f499ef591 by Raymond Hettinger in branch '3.4':
Issue #19145: Remove duplicate ACKS entry
http://hg.python.org/cpython/rev/463f499ef591
|
msg221615 - (view) |
Author: Roundup Robot (python-dev) |
Date: 2014-06-26 16:27 |
New changeset 07eb04003839 by Raymond Hettinger in branch '2.7':
Issue #19145: Remove duplicate ACKS entry
http://hg.python.org/cpython/rev/07eb04003839
|
|
Date |
User |
Action |
Args |
2022-04-11 14:57:51 | admin | set | github: 63344 |
2014-06-26 16:27:46 | python-dev | set | messages:
+ msg221615 |
2014-06-26 16:25:51 | python-dev | set | messages:
+ msg221614 |
2014-06-26 16:00:41 | vajrasky | set | messages:
+ msg221611 |
2014-06-25 07:17:15 | larry | set | messages:
+ msg221531 |
2014-06-25 06:48:47 | rhettinger | set | messages:
+ msg221528 |
2014-06-25 06:24:11 | larry | set | messages:
+ msg221526 |
2014-06-25 04:54:38 | rhettinger | set | status: open -> closed resolution: fixed versions:
+ Python 2.7, Python 3.4 |
2014-06-25 04:53:53 | python-dev | set | messages:
+ msg221523 |
2014-06-25 04:39:34 | python-dev | set | nosy:
+ python-dev messages:
+ msg221522
|
2014-06-22 08:58:42 | rhettinger | set | messages:
+ msg221237 |
2014-06-22 07:24:51 | larry | set | messages:
+ msg221225 |
2014-06-22 07:15:34 | rhettinger | set | messages:
+ msg221224 |
2014-05-31 04:05:54 | vajrasky | set | versions:
+ Python 3.5, - Python 3.4 |
2014-02-08 10:13:56 | vajrasky | set | files:
+ ultimate_solution_for_negative_times_via_keyword_behaviours.patch
messages:
+ msg210606 |
2014-02-03 15:36:15 | BreamoreBoy | set | nosy:
- BreamoreBoy
|
2014-01-27 12:49:38 | larry | set | messages:
+ msg209440 |
2014-01-27 07:37:08 | vajrasky | set | files:
+ fix_itertools_repeat_negative_number_means_0_v4.patch
messages:
+ msg209410 |
2014-01-27 04:00:59 | belopolsky | set | nosy:
+ belopolsky messages:
+ msg209390
|
2014-01-26 05:02:40 | larry | set | messages:
+ msg209278 |
2014-01-26 05:00:44 | vajrasky | set | messages:
+ msg209277 |
2014-01-25 15:39:17 | BreamoreBoy | set | messages:
+ msg209209 |
2014-01-25 13:20:23 | larry | set | messages:
+ msg209196 |
2014-01-25 12:58:03 | BreamoreBoy | set | nosy:
+ BreamoreBoy messages:
+ msg209195
|
2014-01-25 10:37:24 | larry | set | files:
+ larry.sample.itertools.repeat.patch.1.txt
messages:
+ msg209185 |
2014-01-25 06:43:50 | serhiy.storchaka | set | messages:
+ msg209157 |
2014-01-25 05:11:06 | vajrasky | set | messages:
+ msg209152 |
2014-01-24 10:59:48 | larry | set | messages:
+ msg209063 |
2014-01-24 10:21:51 | vajrasky | set | messages:
+ msg209058 |
2014-01-24 10:16:39 | vajrasky | set | files:
+ fix_itertools_repeat_negative_number_means_0_v3.patch
messages:
+ msg209057 |
2014-01-24 09:17:41 | larry | set | messages:
+ msg209052 |
2014-01-24 09:14:27 | vajrasky | set | messages:
+ msg209051 |
2014-01-22 20:36:02 | larry | set | nosy:
+ larry messages:
+ msg208848
|
2014-01-16 20:29:08 | terry.reedy | set | nosy:
+ terry.reedy messages:
+ msg208306
|
2013-10-09 19:30:33 | serhiy.storchaka | set | messages:
+ msg199335 |
2013-10-06 08:59:21 | rhettinger | set | files:
+ itertools_repeat.diff |
2013-10-05 03:02:13 | ezio.melotti | set | nosy:
+ ezio.melotti, serhiy.storchaka
stage: patch review |
2013-10-03 04:46:21 | vajrasky | set | files:
+ fix_itertools_repeat_negative_number_means_0_v2.patch
messages:
+ msg198881 |
2013-10-02 19:43:33 | rhettinger | set | messages:
+ msg198865 |
2013-10-02 15:25:37 | benjamin.peterson | set | assignee: rhettinger
components:
+ Extension Modules, - Library (Lib) nosy:
+ rhettinger |
2013-10-02 15:24:38 | vajrasky | set | files:
+ fix_itertools_repeat_negative_number_means_0.patch
messages:
+ msg198850 |
2013-10-02 15:23:16 | vajrasky | create | |