Reword the discussion on short ways to manipulate lists

- Remove emphasis on map and filter, in favor of generator expressions.
 - Move list comprehensions and generator expressions under "Short ways
   to manipulate lists" rather than "Filtering a list".
 - Add some examples.
This commit is contained in:
George Brova
2019-01-15 01:00:13 +01:00
parent 90af77a526
commit c283dbdf9b
+66 -51
View File
@@ -521,7 +521,7 @@ Conventions
Here are some conventions you should follow to make your code easier to read. Here are some conventions you should follow to make your code easier to read.
Check if a variable equals a constant Check if a variable equals a constant
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You don't need to explicitly compare a value to True, or None, or 0 -- you can You don't need to explicitly compare a value to True, or None, or 0 -- you can
just add it to the if statement. See `Truth Value Testing just add it to the if statement. See `Truth Value Testing
@@ -588,9 +588,70 @@ Short Ways to Manipulate Lists
`List comprehensions `List comprehensions
<http://docs.python.org/tutorial/datastructures.html#list-comprehensions>`_ <http://docs.python.org/tutorial/datastructures.html#list-comprehensions>`_
provide a powerful, concise way to work with lists. Also, the :py:func:`map` and provide a powerful, concise way to work with lists.
:py:func:`filter` functions can perform operations on lists using a different,
more concise syntax. `Generator expressions
<http://docs.python.org/tutorial/classes.html#generator-expressions>`_
follow almost the same syntax as list comprehensions but return a generator
instead of a list.
Creating a new list requires more work and uses more memory. If you are just going
to loop through the new list, prefer using an iterator instead.
**Bad**:
.. code-block:: python
# needlessly allocates a list of all (gpa, name) entires in memory
valedictorian = max([(student.gpa, student.name) for student in graduates])
**Good**:
.. code-block:: python
valedictorian = max((student.gpa, student.name) for student in graduates)
Use list comprehensions when you really need to create a second list, for
example if you need to use the result multiple times.
If your logic is too complicated for a short list comprehension or generator
expression, consider using a generator function instead of returning a list.
**Good**:
.. code-block:: python
def make_batches(items, batch_size):
"""
>>> list(make_batches([1, 2, 3, 4, 5], batch_size=3))
[[1, 2, 3], [4, 5]]
"""
current_batch = []
for item in items:
current_batch.append(item)
if len(current_batch) == batch_size:
yield current_batch
current_batch = []
yield current_batch
Never use a list comprehension just for its side effects.
**Bad**:
.. code-block:: python
[print(x) for x in seqeunce]
**Good**:
.. code-block:: python
for x in sequence:
print(x)
Filtering a list Filtering a list
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@@ -616,52 +677,15 @@ Don't make multiple passes through the list.
**Good**: **Good**:
Python has a few standard ways of filtering lists. Use a list comprehension or generator expression.
The approach you use depends on:
* Python 2.x vs. 3.x
* Lists vs. iterators
* Possible side effects of modifying the original list
Python 2.x vs. 3.x
::::::::::::::::::
Starting with Python 3.0, the :py:func:`filter` function returns an iterator instead of a list.
Wrap it in :py:func:`list` if you truly need a list.
.. code-block:: python
list(filter(...))
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):
* Comprehensions create a new list object.
* Generators iterate over the original list.
The :py:func:`filter` function:
* in 2.x returns a list (use itertools.ifilter if you want an iterator)
* in 3.x returns an iterator
Lists vs. iterators
:::::::::::::::::::
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.
.. code-block:: python .. code-block:: python
# comprehensions create a new list object # comprehensions create a new list object
filtered_values = [value for value in sequence if value != x] filtered_values = [value for value in sequence if value != x]
# Or (2.x)
filtered_values = filter(lambda i: i != x, sequence)
# generators don't create another list # generators don't create another list
filtered_values = (value for value in sequence if value != x) filtered_values = (value for value in sequence if value != x)
# Or (3.x)
filtered_values = filter(lambda i: i != x, sequence)
# Or (2.x)
filtered_values = itertools.ifilter(lambda i: i != x, sequence)
Possible side effects of modifying the original list 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
# replace the contents of the original list # replace the contents of the original list
sequence[::] = [value for value in sequence if value != x] sequence[::] = [value for value in sequence if value != x]
# Or
sequence[::] = (value for value in sequence if value != x)
# Or
sequence[::] = filter(lambda value: value != x, sequence)
Modifying the values in a list Modifying the values in a list
@@ -705,11 +725,6 @@ It's safer to create a new list object and leave the original alone.
# assign the variable "a" to a new list without changing "b" # assign the variable "a" to a new list without changing "b"
a = [i + 3 for i in a] a = [i + 3 for i in a]
# Or (Python 2.x):
a = map(lambda i: i + 3, a)
# Or (Python 3.x)
a = list(map(lambda i: i + 3, a))
Use :py:func:`enumerate` keep a count of your place in the list. Use :py:func:`enumerate` keep a count of your place in the list.