Skip to content

Commit 1efc78a

Browse files
authored
Merge pull request realpython#971 from gbrova/gbrova/prefer-list-comps-over-map
Prefer list comprehensions over map and filter
2 parents f066175 + c283dbd commit 1efc78a

File tree

2 files changed

+74
-71
lines changed

2 files changed

+74
-71
lines changed

docs/writing/structure.rst

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -809,16 +809,12 @@ and can be used as a key for a dictionary.
809809

810810
One peculiarity of Python that can surprise beginners is that
811811
strings are immutable. This means that when constructing a string from
812-
its parts, it is much more efficient to accumulate the parts in a list,
813-
which is mutable, and then glue ('join') the parts together when the
814-
full string is needed. One thing to notice, however, is that list
815-
comprehensions are better and faster than constructing a list in a loop
816-
with calls to ``append()``.
817-
818-
One other option is using the map function, which can 'map' a function
819-
('str') to an iterable ('range(20)'). This results in a map object,
820-
which you can then ('join') together just like the other examples.
821-
The map function can be even faster than a list comprehension in some cases.
812+
its parts, appending each part to the string is inefficient because
813+
the entirety of the string is copied on each append.
814+
Instead, it is much more efficient to accumulate the parts in a list,
815+
which is mutable, and then glue (``join``) the parts together when the
816+
full string is needed. List comprehensions are usually the fastest and
817+
most idiomatic way to do this.
822818

823819
**Bad**
824820

@@ -830,7 +826,7 @@ The map function can be even faster than a list comprehension in some cases.
830826
nums += str(n) # slow and inefficient
831827
print nums
832828
833-
**Good**
829+
**Better**
834830

835831
.. code-block:: python
836832
@@ -840,20 +836,12 @@ The map function can be even faster than a list comprehension in some cases.
840836
nums.append(str(n))
841837
print "".join(nums) # much more efficient
842838
843-
**Better**
844-
845-
.. code-block:: python
846-
847-
# create a concatenated string from 0 to 19 (e.g. "012..1819")
848-
nums = [str(n) for n in range(20)]
849-
print "".join(nums)
850-
851839
**Best**
852840

853841
.. code-block:: python
854842
855843
# create a concatenated string from 0 to 19 (e.g. "012..1819")
856-
nums = map(str, range(20))
844+
nums = [str(n) for n in range(20)]
857845
print "".join(nums)
858846
859847
One final thing to mention about strings is that using ``join()`` is not always

docs/writing/style.rst

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ Conventions
521521
Here are some conventions you should follow to make your code easier to read.
522522

523523
Check if a variable equals a constant
524-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
524+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
525525

526526
You don't need to explicitly compare a value to True, or None, or 0 -- you can
527527
just add it to the if statement. See `Truth Value Testing
@@ -588,80 +588,104 @@ Short Ways to Manipulate Lists
588588

589589
`List comprehensions
590590
<http://docs.python.org/tutorial/datastructures.html#list-comprehensions>`_
591-
provide a powerful, concise way to work with lists. Also, the :py:func:`map` and
592-
:py:func:`filter` functions can perform operations on lists using a different,
593-
more concise syntax.
591+
provide a powerful, concise way to work with lists.
594592

595-
Filtering a list
596-
~~~~~~~~~~~~~~~~
593+
`Generator expressions
594+
<http://docs.python.org/tutorial/classes.html#generator-expressions>`_
595+
follow almost the same syntax as list comprehensions but return a generator
596+
instead of a list.
597597

598-
**Bad**:
598+
Creating a new list requires more work and uses more memory. If you are just going
599+
to loop through the new list, prefer using an iterator instead.
599600

600-
Never remove items from a list while you are iterating through it.
601+
**Bad**:
601602

602603
.. code-block:: python
603604
604-
# Filter elements greater than 4
605-
a = [3, 4, 5]
606-
for i in a:
607-
if i > 4:
608-
a.remove(i)
605+
# needlessly allocates a list of all (gpa, name) entires in memory
606+
valedictorian = max([(student.gpa, student.name) for student in graduates])
609607
610-
Don't make multiple passes through the list.
608+
**Good**:
611609

612610
.. code-block:: python
613611
614-
while i in a:
615-
a.remove(i)
612+
valedictorian = max((student.gpa, student.name) for student in graduates)
613+
614+
615+
Use list comprehensions when you really need to create a second list, for
616+
example if you need to use the result multiple times.
617+
618+
619+
If your logic is too complicated for a short list comprehension or generator
620+
expression, consider using a generator function instead of returning a list.
616621

617622
**Good**:
618623

619-
Python has a few standard ways of filtering lists.
620-
The approach you use depends on:
624+
.. code-block:: python
625+
626+
def make_batches(items, batch_size):
627+
"""
628+
>>> list(make_batches([1, 2, 3, 4, 5], batch_size=3))
629+
[[1, 2, 3], [4, 5]]
630+
"""
631+
current_batch = []
632+
for item in items:
633+
current_batch.append(item)
634+
if len(current_batch) == batch_size:
635+
yield current_batch
636+
current_batch = []
637+
yield current_batch
621638
622-
* Python 2.x vs. 3.x
623-
* Lists vs. iterators
624-
* Possible side effects of modifying the original list
625639
626-
Python 2.x vs. 3.x
627-
::::::::::::::::::
640+
Never use a list comprehension just for its side effects.
628641

629-
Starting with Python 3.0, the :py:func:`filter` function returns an iterator instead of a list.
630-
Wrap it in :py:func:`list` if you truly need a list.
642+
**Bad**:
631643

632644
.. code-block:: python
633645
634-
list(filter(...))
646+
[print(x) for x in seqeunce]
635647
636-
List comprehensions and generator expressions work the same in both 2.x and 3.x (except that comprehensions in 2.x "leak" variables into the enclosing namespace):
648+
**Good**:
637649

638-
* Comprehensions create a new list object.
639-
* Generators iterate over the original list.
650+
.. code-block:: python
640651
641-
The :py:func:`filter` function:
652+
for x in sequence:
653+
print(x)
642654
643-
* in 2.x returns a list (use itertools.ifilter if you want an iterator)
644-
* in 3.x returns an iterator
645655
646-
Lists vs. iterators
647-
:::::::::::::::::::
656+
Filtering a list
657+
~~~~~~~~~~~~~~~~
658+
659+
**Bad**:
660+
661+
Never remove items from a list while you are iterating through it.
662+
663+
.. code-block:: python
664+
665+
# Filter elements greater than 4
666+
a = [3, 4, 5]
667+
for i in a:
668+
if i > 4:
669+
a.remove(i)
670+
671+
Don't make multiple passes through the list.
672+
673+
.. code-block:: python
674+
675+
while i in a:
676+
a.remove(i)
677+
678+
**Good**:
648679

649-
Creating a new list requires more work and uses more memory. If you a just going to loop through the new list, consider using an iterator instead.
680+
Use a list comprehension or generator expression.
650681

651682
.. code-block:: python
652683
653684
# comprehensions create a new list object
654685
filtered_values = [value for value in sequence if value != x]
655-
# Or (2.x)
656-
filtered_values = filter(lambda i: i != x, sequence)
657686
658687
# generators don't create another list
659688
filtered_values = (value for value in sequence if value != x)
660-
# Or (3.x)
661-
filtered_values = filter(lambda i: i != x, sequence)
662-
# Or (2.x)
663-
filtered_values = itertools.ifilter(lambda i: i != x, sequence)
664-
665689
666690
667691
Possible side effects of modifying the original list
@@ -673,10 +697,6 @@ Modifying the original list can be risky if there are other variables referencin
673697
674698
# replace the contents of the original list
675699
sequence[::] = [value for value in sequence if value != x]
676-
# Or
677-
sequence[::] = (value for value in sequence if value != x)
678-
# Or
679-
sequence[::] = filter(lambda value: value != x, sequence)
680700
681701
682702
Modifying the values in a list
@@ -705,11 +725,6 @@ It's safer to create a new list object and leave the original alone.
705725
706726
# assign the variable "a" to a new list without changing "b"
707727
a = [i + 3 for i in a]
708-
# Or (Python 2.x):
709-
a = map(lambda i: i + 3, a)
710-
# Or (Python 3.x)
711-
a = list(map(lambda i: i + 3, a))
712-
713728
714729
Use :py:func:`enumerate` keep a count of your place in the list.
715730

0 commit comments

Comments
 (0)