[Tutor] Which is better style for a function that modifies a list?

boB Stepp robertvstepp at gmail.com
Wed Jun 23 23:51:09 EDT 2021


On Wed, Jun 23, 2021 at 10:09 PM dn via Tutor <tutor at python.org> wrote:
>
> On 24/06/2021 12.31, boB Stepp wrote:
> > On Wed, Jun 23, 2021 at 6:58 PM boB Stepp <robertvstepp at gmail.com> wrote:
> >>
> >> I am tremendously enjoying "Practical Programming -- An Introduction
> >> to Computer Science Using Python 3.6, 3rd Edition" by Paul Gries,
> >> Jennifer Campbell and Jason Montojo, c. 2017.  I highly recommend it
> >> to anyone who needs to learn fundamental Python and/or introductory
> >> computer science.

[OT:  I finally encountered my first real quibble with the book's
authors on page 155.  Here they give an example:

values = [4, 10, 3, 8, -6]
for i in range(len(values)):
    print(i)

where they wish to demonstrate sometimes you need to use the index of
a list when looping.  That's a good point to make, but of course we
are more satisfied with using

for i, value in enumerate(values):
    [...]

I actually bothered tonight to file a "suggestion" on the book's website.

Another thing they did later on this page was to loop over a list's
elements and modify them in place.  I felt they should have issued a
strong warning about the perils of doing such things.]

> >>
> >> In its chapter on lists the authors point out that a function like
> >>
> >> def remove_last_item(L: list) -> list:
> >>     """Return list L with the last item removed."""
> >>
> >>     del L[-1]
> >>     return L
> >>
> >> does not require the return statement since the original passed in
> >> list will be modified in place, so there is no need for the function
> >> to return anything.  I know from experience reading the Tutor and main
> >> lists that this is a frequent "gotcha" that catches many fellow
> >> learners.  So I wonder:
> >>
> >> 1)  For such a function that mutates the passed-in list is it
> >> clearer/better style to keep or omit the return statement?  Which
> >> reads better?
> >
> > In light of an earlier thread I started plus now examining all of the
> > list methods I conclude that the Pythonic way must be to omit the
> > "return" statement, so that "None" is returned (Or return "None"
> > explicitly.) whenever a list is modified in place without returning a
> > new list.
>
>
> +1, why add an unnecessary LoC?
>
> Unless... the intent is to return an entirely new list.
>
> Except... Zen of Python says "explicit > implicit".
>
> ???

It seems to me that modern Python is just as likely to violate the Zen
as follow it.  I guess it was never meant to be taken particularly
literally anyway.

>
> I'm concerned that the function offers little, um, functionality. In
> short, would it not be better to write:
>
> del original_list[ -1 ]
>
> rather than:
>
> remove_last_item( original_list )

Ah, dn, you must give the authors a break here.  They were merely
trying to provide a bare-bones example to illustrate a point that they
wanted the reader to understand.  My question is merely extrapolating
from their simple example to explore good Python style and practice.

> There is a branch of programming-thought called "Functional Programming"
> and one of the aspects of code it seeks to avoid is "side-effects". The
> 'gotcha' implicit within mutable collections has been discussed in an
> earlier thread. If we want to remove_/del is it really a side-effect?

As I gain more knowledge and do more consideration of intentional side
effects in code -- or worse, unintentional! -- it seems a breeding
ground for subtle bugs in large, complex code bases.  If I ever get a
decent handle on Python perhaps I will investigate one of these true
functional programming languages.

>
> Perhaps if we were considering the wider objective(s), and thereafter
> why we are removing the list's (or string's, or ...) last element, ie
> what other functionality we will be asking of the 'list', our thinking
> may lead us to create a custom class/object, sub-classed from list (or ...)?
>
> Whilst this seems an OTT solution to the bare-scenario painted above,
> assuming other justification, a custom class would enable:
>
> particular_purpose_list = CustomClass( etc )
> ...
> particular_purpose_list.remove_last_item()
>
> Because the internal "state" of our object-instance is maintained within
> "self", there is now little doubt of what is happening, and to what.
> There is no 'gotcha' (assuming good practice code). Plus, it elevates
> the suspicion of a side-effect to being a desired-impact. All of which
> results in the code/data-structure being more tightly testable because
> it implements a more bounded purpose.
>
>
> Your thoughts?

I find no disagreement here *if* a worthwhile purpose for having such
a method existed for a custom data type.  And more than likely such a
remove the last item from such a data type might not even be part of
the public API but be a hidden implementation detail to the user of
the class.  Still, though, the tests better be good!

Cheers!
boB Stepp


More information about the Tutor mailing list