9

Say I make a list comprehension that looks something like this:

i = range(5)
a = [f(i) for i in i]

for some function f. Will using a dummy name identical to the iterator ever yield unexpected results? Sometimes I have variable names that are individual letters, and to me it is more readable to stick with the same letter rather than assigning a new one, like [f(x) for x in x] instead of [f(i) for i in x] (for instance, if the letter of the iterator x is meaningful, I will wonder what the heck i is).

Luke Davis
  • 2,548
  • 2
  • 21
  • 43
  • 10
    Ill advised, if for no other reason, because it confuses the heck out of the human reading the code. – Fred Larson May 01 '17 at 20:26
  • 1
    What about `[f(x) for x in X]`? – Elmex80s May 01 '17 at 20:27
  • How reading `[f(x) for x in x]` is more readable than `[f(i) for i in x]`? Here `x` is originally a list and `i` (in your later example) is element of list. Second is more readable. **How naming a list and element of list with a same variable is more readable?** – Moinuddin Quadri May 01 '17 at 20:28
  • 2
    What about `map()`, as in `map(f,i)`? – arshajii May 01 '17 at 20:28
  • 6
    I think you might get unexpected results if you use nested iterations, but if there is only one `for` statement, you *might* be able to get away with it. That being said, **please do not do this**. – juanpa.arrivillaga May 01 '17 at 20:28
  • While it may make sense to you, I'm pretty sure it confuse most people who see. I recommend sticking with making the variable names different, like @FredLarson said. – Christian Dean May 01 '17 at 20:28
  • 6
    Or just `[f(_) for _ in i]`. If you don't feel like creating a name for a variable which you'll be using only once, or not at all, just call it `_`, a.k.a. "nothing important here, please move on". – ForceBru May 01 '17 at 20:28
  • `[i for i in [f(i) for i in [f(i) for i in i]][::-1][::-1]]`... – blacksite May 01 '17 at 20:30
  • @ForceBru oooh... I **really** like that. I don't like assigning random letters to the dummy variables, because the one-character name of the iterator is often meaningful. – Luke Davis May 01 '17 at 20:30
  • At some point, you're going to try to use the `i` name to refer to the iterable instead of the element, and you'll waste hours wondering why things aren't working and getting mad at Python. Or maybe you'll need to run things on Python 2, and you'll be confused and astounded when the value of `i` outside the comprehension changes after the comprehension finishes. – user2357112 May 01 '17 at 20:32
  • 1
    I strongly suggest using single letter variables **only** for idiomatic usage: `for k, v in dct:`. Variables should be distinct. Can you make things super confusing by overloading variables? Yes. Should you? No. This is valid Py3 which solves a problem and is [intentionally] almost unreadable: `print(['no','yes'][any([_ for _ in[([_ for _ in _ if _ in"aeiou"],print("{} has {} vowel(s).".format(_,len([_ for _ in _ if _ in"aeiou"]))))for _ in wordlist[::3]]if len(_[False])>=3])])`. See the [repl.it](https://repl.it/HaIG/0) – TemporalWolf May 01 '17 at 20:53
  • I am highly surprised to learn this question got 7 up-votes! – Elmex80s May 02 '17 at 00:01
  • 1
    @Elmex80s So was I, honestly. – Luke Davis May 02 '17 at 07:12

2 Answers2

10

TL;DR: It is safe, technically, but it's a poor choice stylistically.

In a list comprehension, before binding the free variable of the for-loop to any object, Python will use a GET_ITER opcode on the iterable to get an iterator. This is done just once at the beginning of the loop.

Therefore in the body of the "loop" of the list comprehension (which actually creates a scope in Python 3), you may rebind the name which originally pointed to the iterable without any consequence. The iteration deals with a reference to the iterator directly, and whether or not it has a name in scope is irrelevant. The same should hold true in Python 2, though the scoping implementation details are different: the name of the collection will be lost after the comprehension, as the loop variable name will remain bound to the final element of iteration.

There is no advantage to writing the code in this way, and it is less readable than just avoiding the name collision. So, you should prefer to name the collection so that it more obvious that it is a collection:

[f(x) for x in xs]
wim
  • 338,267
  • 99
  • 616
  • 750
  • 6
    In Python 2, things (usually) don't break in the comprehension itself, but they break as soon as anything tries to use `i` after the comprehension, since the comprehension stomped on the original value of `i`. – user2357112 May 01 '17 at 20:50
  • Yeah. Added a warning note about the scope leak bug in Python 2. – wim May 01 '17 at 21:00
  • Nice job of stating the technical reasons of why this safe. – Christian Dean May 01 '17 at 21:08
  • "Safe" in the sense that firing an unloaded gun at yourself is "safe", or stabbing the table between your fingers is "safe", or juggling disarmed explosives is "safe" - maybe if you don't make any mistakes, it won't go horribly wrong, but you are violating every rule of safety and you have only yourself to blame when the problems inevitably happen. – user2357112 May 02 '17 at 01:48
  • 1
    @user2357112 I think you misunderstood the intent of my comment. When I said wim did a good job of showing the _technical_ reasons of it was safe, I was referring to how he explained how Python generates and executes certain opcodes. I found it interesting, and was informing him of how he did a good job of showing what was going on behind the scenes. I **was not** however, saying that this practice is safe. This is a very bad idea that would result in multiple headaches. – Christian Dean May 02 '17 at 03:00
7

While you can get away with using duplicate variable names due to the way Python executes list comprehensions - even nested list comprehensions - don't do this. It may seem more readable in your opinion, but for most people, it would be very confusing.

This leads to a much more important point, however. Why use a name like i, j, or x at all? Using single letter variable names cause confusion and are ambiguous. Instead, use a variable name which clearly conveys your intent.

Or, if you don't have an need at all for the values in the iterable your iterating through(eg. You simple want to repeat a block of code a certain number of times), use a "throw-away" variable, _, to convey to the reader of your code that the value is not important and should be ignored.

But don't use non-descriptive, duplicate, single letter variable names. That will only serve to confuse future readers of your code, make your intent unclear, and create code hard to maintain and debug.

Because in the end, would you rather maintain code like this

[str(x) for x in x]

or this?

[str(user_id) for user_id in user_ids]
Christian Dean
  • 22,138
  • 7
  • 54
  • 87