diff --git a/docs/writing/structure.rst b/docs/writing/structure.rst index d9c61b491..842a44dd4 100644 --- a/docs/writing/structure.rst +++ b/docs/writing/structure.rst @@ -809,16 +809,12 @@ and can be used as a key for a dictionary. One peculiarity of Python that can surprise beginners is that strings are immutable. This means that when constructing a string from -its parts, it is much more efficient to accumulate the parts in a list, -which is mutable, and then glue ('join') the parts together when the -full string is needed. One thing to notice, however, is that list -comprehensions are better and faster than constructing a list in a loop -with calls to ``append()``. - -One other option is using the map function, which can 'map' a function -('str') to an iterable ('range(20)'). This results in a map object, -which you can then ('join') together just like the other examples. -The map function can be even faster than a list comprehension in some cases. +its parts, appending each part to the string is inefficient because +the entirety of the string is copied on each append. +Instead, it is much more efficient to accumulate the parts in a list, +which is mutable, and then glue (``join``) the parts together when the +full string is needed. List comprehensions are usually the fastest and +most idiomatic way to do this. **Bad** @@ -830,7 +826,7 @@ The map function can be even faster than a list comprehension in some cases. nums += str(n) # slow and inefficient print nums -**Good** +**Better** .. code-block:: python @@ -840,20 +836,12 @@ The map function can be even faster than a list comprehension in some cases. nums.append(str(n)) print "".join(nums) # much more efficient -**Better** - -.. code-block:: python - - # create a concatenated string from 0 to 19 (e.g. "012..1819") - nums = [str(n) for n in range(20)] - print "".join(nums) - **Best** .. code-block:: python # create a concatenated string from 0 to 19 (e.g. "012..1819") - nums = map(str, range(20)) + nums = [str(n) for n in range(20)] print "".join(nums) One final thing to mention about strings is that using ``join()`` is not always diff --git a/docs/writing/style.rst b/docs/writing/style.rst index 33ed1ad01..8f15a1b02 100644 --- a/docs/writing/style.rst +++ b/docs/writing/style.rst @@ -521,7 +521,7 @@ Conventions Here are some conventions you should follow to make your code easier to read. Check if a variable equals a constant -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 @@ -588,80 +588,104 @@ Short Ways to Manipulate Lists `List comprehensions `_ -provide a powerful, concise way to work with lists. Also, the :py:func:`map` and -:py:func:`filter` functions can perform operations on lists using a different, -more concise syntax. +provide a powerful, concise way to work with lists. -Filtering a list -~~~~~~~~~~~~~~~~ +`Generator expressions +`_ +follow almost the same syntax as list comprehensions but return a generator +instead of a list. -**Bad**: +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. -Never remove items from a list while you are iterating through it. +**Bad**: .. code-block:: python - # Filter elements greater than 4 - a = [3, 4, 5] - for i in a: - if i > 4: - a.remove(i) + # needlessly allocates a list of all (gpa, name) entires in memory + valedictorian = max([(student.gpa, student.name) for student in graduates]) -Don't make multiple passes through the list. +**Good**: .. code-block:: python - while i in a: - a.remove(i) + 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**: -Python has a few standard ways of filtering lists. -The approach you use depends on: +.. 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 -* Python 2.x vs. 3.x -* Lists vs. iterators -* Possible side effects of modifying the original list -Python 2.x vs. 3.x -:::::::::::::::::: +Never use a list comprehension just for its side effects. -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. +**Bad**: .. code-block:: python - list(filter(...)) + [print(x) for x in seqeunce] -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): +**Good**: - * Comprehensions create a new list object. - * Generators iterate over the original list. +.. code-block:: python -The :py:func:`filter` function: + for x in sequence: + print(x) - * in 2.x returns a list (use itertools.ifilter if you want an iterator) - * in 3.x returns an iterator -Lists vs. iterators -::::::::::::::::::: +Filtering a list +~~~~~~~~~~~~~~~~ + +**Bad**: + +Never remove items from a list while you are iterating through it. + +.. code-block:: python + + # Filter elements greater than 4 + a = [3, 4, 5] + for i in a: + if i > 4: + a.remove(i) + +Don't make multiple passes through the list. + +.. code-block:: python + + while i in a: + a.remove(i) + +**Good**: -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. +Use a list comprehension or generator expression. .. code-block:: python # comprehensions create a new list object 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 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 @@ -673,10 +697,6 @@ Modifying the original list can be risky if there are other variables referencin # replace the contents of the original list 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 @@ -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" 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.