Skip to content

BUG: np.arange: Allow stop not start as sole kwargs. #17878

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 4, 2020

Conversation

MaiaKaplan
Copy link
Contributor

@MaiaKaplan MaiaKaplan commented Nov 29, 2020

Fixes gh-17764

  • Of note: Removes requirement for start argument in argparse format string
  • Behaviour change: Avoids arange(start=4) returning a range from 0 to 4
  • Behaviour change: Allows arange(stop=4) to run without error, to return range from 0 to 4

Copy link
Member

@seberg seberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Maia! I think the logic can be simplified if you check for positional arguments instead of kwarg. I.e. if args are passed in and there is at least one element, you know that start was passed by position and not by name (that also avoids the slightly annoying dictionary keyword creation, etc. which just makes me want to micro-optimize it ;)):

If o_stop == NULL:

  • If args != NULL && PyTuple_GET_SIZE(args) > 0 (not sure args can be NULL...):
    • start must have been passed by position.
    • o_stop = o_start; o_start = NULL
  • Else: raise TypeError(...)

Py_DECREF(start_str);
Py_DECREF(stop_str);
return NULL;
} else if (PyDict_Contains(kws, start_str) == 0 &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: We usually put the else on a new lin from the closing bracket }.


class TestArange:
def test_require_range(self):
import pytest
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pytest import should be top-level, if it is not already.

(I am a bit surprised by the codecov comments, since those lines seem covered by these tests...)


assert len(keyword_stop) == 3
assert len(keyword_zerotostop) == 3
assert (keyword_stop == keyword_zerotostop).all()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just FYI, we also have a assert_arrays_equal, which checks also shape/dtype (difference that all() might miss). It doesn't matter here though.

@mattip
Copy link
Member

mattip commented Nov 30, 2020

I am not sure about the micro-optimization. I think the function is most likely to be called without kwargs, so if (kws != NULL) is more likely to be false than args != NULL && PyTuple_GET_SIZE(args) > 0. The fact that the function has been around since the beginning of time before someone noticed the logical error supports my theory.

@seberg
Copy link
Member

seberg commented Nov 30, 2020

Good point, but I still think it will make for much simpler/shorter code. I guess you could keep the if (kwds == NULL) to make it kwds == NULL || (args != NULL && PyTuple_GET_SIZE(args) > 0).

@seberg
Copy link
Member

seberg commented Nov 30, 2020

Ah, looking at it again. The C-level function handles stop == NULL (meaning start is stop), but not not start == NULL, so the swapping is the other way around and it is a bit more complicated than I thought:

if o_stop == NULL:
    # start is used as stop, but we reject this if it is passed by keyword
    if args == NULL or len(args) == 0:  # could check `kwds != NULL` first 
        # If keywords were used and no positional argument passed,
        # start was passed by keyword and cannot be used as stop.
        raise TypeError("uhoh, stop not passed!")
else if o_start == NULL:
    # Pass stop as NULL to indicate that start is stop
    o_start = o_stop
    o_stop = NULL    

@mattip mattip changed the title BUG: np.arange: Allow stop not start as sole kwargs. (gh-17764) BUG: np.arange: Allow stop not start as sole kwargs. Dec 1, 2020
@MaiaKaplan MaiaKaplan closed this Dec 2, 2020
@MaiaKaplan
Copy link
Contributor Author

Thanks for the comments and the suggestion! please also delight with me in my closing/reopening the ticket -- proper newcomer :) I have a few questions:

  1. I think the downstream C-function takes care of swapping o_stop for o_start if o_start is not provided, if I am reading it correctly? If so, I would then leave the block out.

  2. I also want to confirm then that is not an enhancement: that the goal is also to keep the current behaviour of not allowing arange(stop=4) currently:

> np.arange(3)
array([0, 1, 2])
> np.arange(stop=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: arange() missing required argument 'start' (pos 1)
  1. Confirming that the tests are in the appropriate spot? I wasn't able to find any arange tests so I'm not confident in where I put these. I'll hope that when I update the ^ code, the coverage issue will go away.

re:

Ah, looking at it again. The C-level function handles stop == NULL (meaning start is stop), but not not start == NULL, so the swapping is the other way around and it is a bit more complicated than I thought:

if o_stop == NULL:
    # start is used as stop, but we reject this if it is passed by keyword
    if args == NULL or len(args) == 0:  # could check `kwds != NULL` first 
        # If keywords were used and no positional argument passed,
        # start was passed by keyword and cannot be used as stop.
        raise TypeError("uhoh, stop not passed!")
else if o_start == NULL:
    # Pass stop as NULL to indicate that start is stop
    o_start = o_stop
    o_stop = NULL    

@MaiaKaplan MaiaKaplan reopened this Dec 2, 2020
@seberg
Copy link
Member

seberg commented Dec 2, 2020

About point 1. I am not sure the called function supports start = NULL with stop given, so in that case we probably have to swap (which happens if step=... is passed as kwarg).

About 2. my working assumption was you meant to allow arange(stop=4) to be the same as arange(4) and arange(0, stop=4). But, I am happy with whatever you prefer (and it can easily be extended later).

About the test location. It is fine by me, maybe someone has a better idea. I guess there isn't a perfect place :(. test_multiarray.py might seem a bit closer to right, but it is also an annoyingly large and random file. The test_function_base.py is probably paired with function_base.py though. OTOH, it does contain functions that are similar in scope to arange.
What matters more to me is the TestArange class, which is a great addition!

@MaiaKaplan MaiaKaplan force-pushed the arange_keyword branch 2 times, most recently from 9398cfb to 8a70a4f Compare December 4, 2020 16:03
@seberg seberg marked this pull request as ready for review December 4, 2020 16:17
…mpygh-17764)

* This matches args behaviour.
* Avoids `arange(start=4)` from returning a range from 0 to 4
* Allows `arange(stop=4)` to run without error, to return range from 0 to 4
@seberg
Copy link
Member

seberg commented Dec 4, 2020

Thanks Maia! If you are still wondering about the codecov, I expect it is just due to compiler optimizing pretty much everything away.

@seberg seberg merged commit b1862a3 into numpy:master Dec 4, 2020
if (args == NULL || PyTuple_GET_SIZE(args) == 0){
PyErr_SetString(PyExc_TypeError,
"arange() requires stop to be specified.");
return NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I a bit of a sloppy day for me... I just noticed that this branch is missing the Py_XDECREF(typecode);

One reason why I am sloppy about these things though, is that nowadays reference count checkers catch these bugs pretty well most of the time (Unrelated note for anyone who wonders: No I have not run pytest-leaks on master/1.20 after branching, only valgrind which is typically not sensitive enough for this type of leak usually.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a short note in the docs about how to check this would be good. Is there a way to run a checker only on a single test like python <some command> test_start_stop_kwarg?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up #17966

@benbovy benbovy mentioned this pull request Apr 3, 2025
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BUG: Using start/step as keywords only has incorrect behaviour
3 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy