This is a revisit to a question already asked here about a year and a half ago.

Project Euler's Problem 40 involves generating the sequence of digits that appear in the natural numbers, all concatenated together (123456789101112131415161718192021…). As part of the solution, I wrote this:

def counting_digits():
    for number in count(1):
        for digit in str(number):
            yield digit

After finishing the solution, I tried this:

def counting_digits():
    return (digit for number in count(1) for digit in str(number))

My questions:

  1. In the face of Python 3, Which solution is more pythonic?
  2. As someone who just wants to understand the code. Which is easier/clearer to read?
  3. If you haven't read the statement for Project Euler's Problem 40, Which version lets you better know what the function does?

Performance is not part of the question: pythonism and understandability is the issue.

  • \$\begingroup\$ As a programmer who is somewhat familiar with Python, but writes it fairly infrequently, I find the yield version more readable. \$\endgroup\$ – Matt Ball May 22 '11 at 15:23
  • \$\begingroup\$ @Matt But isn't the yield concept harder to grasp unless you come from Ruby or such? \$\endgroup\$ – Apalala May 22 '11 at 15:44
  • \$\begingroup\$ Since you didn't do any operation on c, the last one makes more sense. \$\endgroup\$ – Kabie May 22 '11 at 15:46
  • \$\begingroup\$ How about yield int(c)? \$\endgroup\$ – Apalala May 22 '11 at 15:49
  • 4
    \$\begingroup\$ I think generator expressions are only useful when they are simple enough that they don't need explanation and you can use them inline in whatever your code is doing. The moment you want to move into its own function the yield syntax is clearer. \$\endgroup\$ – Winston Ewert May 23 '11 at 2:45

The one where you return a generator is easier to read were it the case that there was only one for. Also, as OP pointed out, the associativity of nested for loops is not what you'd expect in Python.

The top one may be slightly easier to read for some people; it would be MUCH easier to read if there were comments or if the variables were semantically named, and the indented structure allows for comments much better. With semantically-named variables, the first version is easier to read; I'd only use single-letter variable names as the pattern in list comprehensions.

  • \$\begingroup\$ In which case(s) is the generator more efficient? \$\endgroup\$ – Matt Ball May 22 '11 at 15:27
  • \$\begingroup\$ In the case of nested generators, see stackoverflow.com/questions/6027558/… where I mention asymptotic speed. \$\endgroup\$ – ninjagecko May 22 '11 at 15:29
  • \$\begingroup\$ That's not so much an issue with yielding though, as it is with re-yielding, so perhaps not that applicable. \$\endgroup\$ – ninjagecko May 22 '11 at 15:51
  • \$\begingroup\$ @ninjagecko I edited the variable names. \$\endgroup\$ – Apalala May 22 '11 at 15:53
  • 2
    \$\begingroup\$ It seems that the double for is the killer, because Python's way of determining scope (the earlier for dominates the following one) is unusual. You'd expect the result to be more closely related to the for that is closer. \$\endgroup\$ – Apalala May 22 '11 at 16:17

Someone who doesn't know Python would look at the first version and ask "What's yield do?" After a quick google search of "python yield" they'd easily grok it.

The same person would look at that second version and say "WTF?" What would we expect them to google to understand it?

I won't speak for the Python community at large, but as an experienced Python programmer I find the first version clearer and more pythonic. When I encounter nested generators or list comprehensions I still need to stop for a moment to puzzle it out. I think the docs concede that nested generators and list comprehensions can be confusing when they advise

To avoid apprehension when nesting list comprehensions, read from right to left.

And shortly after that they say

In real world, you should prefer built-in functions to complex flow statements.

  • \$\begingroup\$ +1 As I mentioned, it was unexpected for me that right after writing this question, I found that my own code was harder to understand in the single-liner version. \$\endgroup\$ – Apalala May 22 '11 at 19:55

"pythonic" and "easier/clearer" is a completely subjective issue.

The first example is more readable and understandable - even for a Java developer. The second piece of code is more dense but I would not call it pythonic. Both pieces of code are fine for me.

  • \$\begingroup\$ I think that Java doesn't yet support the concept of yield (suspended/lazy execution). Why would the more 'structured code' version be clearer for a Java developer? \$\endgroup\$ – Apalala May 22 '11 at 15:48
  • \$\begingroup\$ You are correct that Java doesn't support yield, but the yield version still looks more like Java than the generator version. \$\endgroup\$ – Matt Ball May 22 '11 at 15:55

I think this is very subjective. In my case, I've used Python for about 9 months (albeit rather a lot) and find the syntax of the generator expression more concise and readable. Given the simplicity, using the generator inline would be preferable, in this case. If you needed something a bit more complicated, I'd lean towards a function.

  • \$\begingroup\$ I agree, except that I'd rather place the generator in a well-named function than place it inline and have to explain it with a comment. \$\endgroup\$ – Apalala May 22 '11 at 16:00
  • \$\begingroup\$ @Apalala Maybe a good rule of thumb is to avoid using a generator expression when you feel it'd be necessary to add a comment explaining what it does. In this case I think a comment would be unnecessary. \$\endgroup\$ – zeekay May 22 '11 at 16:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.