-1

Is using one line loops, even nested loops always a good practice in Python? I see a lot of people just love "one-liners", but to me they're hard to read sometimes, especially if we're talking about nested loops.

Moreover most of nested loops I've seen so far exceed the recommended 79 characters per line.

So I'd like to know if "one-liners" offer anything extra apart being compact? Do they use less memory maybe?

Cœur
  • 37,241
  • 25
  • 195
  • 267
user3053452
  • 640
  • 1
  • 12
  • 38
  • Depends on the specific one-liner. Certain Python constructs (e.g.: list comprehensions) can potentially be optimized by the interpreter to produce "better" machine code than if you wrote a normal for-loop to fill a list. However as you pointed out readability of code suffers if you overcomplicate them and it's the programmer's job to decide at what point the code becomes unreadable (and therefore unmaintainable) – UnholySheep Sep 18 '16 at 19:39
  • If would help if you could show some comprehension which is unreadable. Often it's salvageable by using correct naming and some whitespace. But it's hard to demonstrate without concrete sample. – Łukasz Rogalski Sep 18 '16 at 19:48
  • Just look at the stacked list comprehension in my example.. it need not even be a physical one-liner even though it technically is – NaN Sep 18 '16 at 19:59
  • One-liners are often more readable. In that case they are good. If they are less readable, then they are not good ;) – zvone Sep 18 '16 at 23:28

4 Answers4

5

Yes, they may be easily faster, since more code may be run in C (in explicit loop all of immediate steps has to be available to interpreter, in one-liner list comprehension it does not have to). There is also overhead of .append method call, method look-up etc. In list comprehension all of that is avoided:

import timeit


def f1():
    result = []
    for x in range(100):
        for y in range(100):
            result.append(x * y)
    return result


def f2():
    return [x * y for y in range(100) for x in range(100)]


print('loop: ', timeit.timeit(f1, number=1000))
print('oneliner:', timeit.timeit(f2, number=1000))

Results (Python 3):

loop:  1.2545137699926272
oneliner: 0.6745600730064325
Łukasz Rogalski
  • 22,092
  • 8
  • 59
  • 93
  • Great answer, exactly what I was looking for. Looks like it's time I dive into list comprehensions and learn them properly. Just out of curiosity: what if I come across a long nested loop that exceeds PEP recommandation of 79 chars? (I know that I could just name iterator 'x' or something similar to keep it short, but what if even with this it would exceed 79 chars) – user3053452 Sep 19 '16 at 12:01
  • [You may use newlines in list comprehensions](http://stackoverflow.com/questions/5809059/line-continuation-for-list-comprehensions-or-generator-expressions-in-python). Another common technique for cartesian nested loops is using [`itertools.product`](https://docs.python.org/3/library/itertools.html#itertools.product) instead of nested looping. – Łukasz Rogalski Sep 19 '16 at 12:18
1

Depends on the one-liner, some can be much more efficient and very readable.

Take the case of list comprehension. Lets say you want to take all numbers from 1 to 10 and output a list of their product multiplied by 2.

Input: [1,2,3,...,9,10]
Output: [2,4,6,...,18,20]

You can do it in a for loop like so:

output = []
for i in range(1, 11):
    output.append(i*2)

Or you can just use list comprehension:

[i*2 for i in range(1,11)]

You'll see the list comprehension is MUCH faster, and also quite readable.

The same can be said for dict comprehension, set comprehension and generator expressions. Also the use of map() and filter() are highly encouraged as long as it's understandable.

Bharel
  • 23,672
  • 5
  • 40
  • 80
0

For discussion... they all do the same.

'a' a list comprehension... the smoking one-liner... 'b' same thing, but you can provide annotation within a list comprehension... it is still technically a one-liner 'c' the conventional approach. If speed is a concern, I am less worried about the speed unless, I perform the same task day in day out and it takes a really long time to perform. Comparing speed when you are talking micro or nanoseconds may be of academic interest, but it will not impact the vast majority of users. I always vote for clarity of expression over compactness of code.

a = [i**2 for i in range(20) if i > 5 and i < 10]

b = [i**2               # do this  
     for i in range(20) # using these  
     if (i > 5) and     # where this and  
        (i < 10)        # this is good  
     ]  

c = []  
for i in range(20):  
    if (i > 5) and (i < 10):  
        c.append(i**2)  

EDIT The example given of producing the product of a range of numbers, is a good indicator, that the oneliner need not be the issue, but the method used to obtain the result. I will illustrate with determining the product using only 10 values... try it with 100 if you like. I will do it in full format, but could reduce everything to a oneliner if need (import excluded).

>>> import numpy as np
>>> a = np.arange(10)
>>> b = np.arange(10).reshape(10,1)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> b
array([[0],
       [1],
       [2],
       [3],
       [4],
       [5],
       [6],
       [7],
       [8],
       [9]])
>>> a*b
array([[ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
       [ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18],
       [ 0,  3,  6,  9, 12, 15, 18, 21, 24, 27],
       [ 0,  4,  8, 12, 16, 20, 24, 28, 32, 36],
       [ 0,  5, 10, 15, 20, 25, 30, 35, 40, 45],
       [ 0,  6, 12, 18, 24, 30, 36, 42, 48, 54],
       [ 0,  7, 14, 21, 28, 35, 42, 49, 56, 63],
       [ 0,  8, 16, 24, 32, 40, 48, 56, 64, 72],
       [ 0,  9, 18, 27, 36, 45, 54, 63, 72, 81]])

or as a one-liner

>>> out = np.arange(10) * np.arange(10).reshape(10,1)

and of course there are built-in functions to do this without the full expression I have shown.

The point being made, speed isn't everything. Clarity of code AND choosing the right tools to do the job should be considered first.

NaN
  • 2,212
  • 2
  • 18
  • 23
  • `if i > 5 and i < 10` -> `if 5 < i < 10` and suddenly we talk about super simple comprehension - one loop, one if, reads super easy. – Łukasz Rogalski Sep 18 '16 at 19:47
  • check my addendum to show an even better alternative. This option scales nicely when data inputs are quite large.... which is another consideration. Not all approaches time uniformly and are often dependent on data size. – NaN Sep 18 '16 at 19:57
  • 1
    How is saying "use numpy" an alternative to list comprehensions? numpy is a (3rd party) library specifically for numerical computation, while list comprehensions (and other one-liners) can be generic and used for many different cases that are not related to numerical computation – UnholySheep Sep 18 '16 at 20:01
  • that was my exact point. If you have a task, choosing the right tool is as important as choosing the method of implementing the algorithm. BTW numpy is not limited to numerical computation, it handles text data quite nicely since vectorization of operations is one of its tenants. Please note the first sentence in my post.... For discussion. If everyone wants to bring out time comparisons, then the point of computer methods and their application will be lost. – NaN Sep 18 '16 at 20:24
-1

Yes. This is good practice.

Usually experienced programmers use anonymous functions (see, lambda: http://www.secnetix.de/olli/Python/lambda_functions.hawk) in one-line loops and it gives better performance.

Zulfugar Ismayilzadeh
  • 2,643
  • 3
  • 16
  • 26