Skip to content
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

Get the first item from an iterable #5503

Closed
hoxbro opened this issue Jul 4, 2023 · 4 comments · Fixed by #5549
Closed

Get the first item from an iterable #5503

hoxbro opened this issue Jul 4, 2023 · 4 comments · Fixed by #5549
Assignees
Labels
accepted Ready for implementation rule Implementing or modifying a lint rule

Comments

@hoxbro
Copy link
Contributor

hoxbro commented Jul 4, 2023

Sometimes I need to get the first item of an iterable like a dict_keys() object. A natural way to do this is to do it with list(...)[0], though it can be much faster to do it with next(iter(...)).

I have tried to see if a rule already describes this, but I could not find it.

Some examples:

Examples with list(...)[0] with length of 1000
Dictionary:	1000 loops, best of 5: 4.35 usec per loop
List:		1000 loops, best of 5: 1 usec per loop
Range:		1000 loops, best of 5: 5.6 usec per loop

Examples with next(iter(...)) with length of 1000
Dictionary:	1000 loops, best of 5: 34.3 nsec per loop
List:		1000 loops, best of 5: 33.1 nsec per loop
Range:		1000 loops, best of 5: 27.7 nsec per loop
Code
N=1000
echo "Examples with list(...)[0] with length of $N"
echo -n -e "Dictionary:\t"
python -m timeit --s="a = {i: i for i in range($N)}" --n 1000 "list(a)[0]"
echo -n -e "List:\t\t"
python -m timeit --s="a = [i for i in range($N)]" --n 1000 "list(a)[0]"
echo -n -e "Range:\t\t"
python -m timeit --s="a = range($N)" --n 1000 "list(a)[0]"

echo -e "\nExamples with next(iter(...)) with length of $N"
echo -n -e "Dictionary:\t"
python -m timeit --s="a = {i: i for i in range($N)}" --n 1000 "next(iter(a))"
echo -n -e "List:\t\t"
python -m timeit --s="a = [i for i in range($N)]" --n 1000 "next(iter(a))"
echo -n -e "Range:\t\t"
python -m timeit --s="a = range($N)" --n 1000 "next(iter(a))"
@dhruvmanila
Copy link
Member

This is not only faster but would consume less memory as it doesn't need to create the entire list to access the only element :)

@dhruvmanila dhruvmanila added the rule Implementing or modifying a lint rule label Jul 4, 2023
@evanrittenhouse
Copy link
Contributor

You can assign this to me. I'll add it as a RUF rule.

@charliermarsh
Copy link
Member

This looks reasonable, but let's be really careful in reviewing any violations that pop up in the ecosystem CI.

@evanrittenhouse
Copy link
Contributor

evanrittenhouse commented Jul 5, 2023

@charliermarsh Yup!

@charliermarsh charliermarsh added the accepted Ready for implementation label Jul 10, 2023
charliermarsh pushed a commit that referenced this issue Jul 10, 2023
## Summary

Fixes #5503. Ready for final review as the `mkdocs` issue involving SSH
keys is fixed.

Note that this will only throw on a `Name` - it will be refactorable
once we have a type-checker. This means that this is the only sort of
input that will throw.
```python
x = range(10)
list(x)[0]
```

I thought it'd be confusing if we supported direct function results.
Consider this example, assuming we support direct results:
```python
# throws
list(range(10))[0]

def createRange(bound):
    return range(bound)

# "why doesn't this throw, but a direct `range(10)` call does?"
list(createRange(10))[0]
```
If it's necessary, I can go through the list of built-ins and find those
which produce iterables, then add them to the throwing list.

## Test Plan

Added a new fixture, then ran `cargo t`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted Ready for implementation rule Implementing or modifying a lint rule
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants