Lately I came across a way of using mutables which I had not thought of before.

Let's say you have a list comprehension which calls a method, and you put the result in a list like the following (normal stuff):

my_list = [my_object.do_stuff() for my_object in all_objects]

The do_stuff method does some loop computations to build diverse things, it's quite a bit complicated. For now everything's okay, but it turns out you need to do part of that computation again, and put the result in another list, just afterwards:

my_other_list = [my_object.do_just_a_part_of_stuff() for my_object in all_objects]

You find out that to avoid doing the same bit of computation twice, you can use a mutable like a set the following way:

my_set = set()
my_list = [my_object.do_stuff(my_set) for my_object in all_objects]
my_other_variable = do_something_with(my_set)

As you can see, my_set is empty at first, passed as an argument to do_stuff which modifies it in place just enough while it performs the computations mentioned above, and then once modified, my_set gets used for my_other_list.

Is this kind of pattern commonly considered good practice? And does it even have a name? I must say I find this quite simple and elegant, but I'm also wondering if that's not a bit hazardous and perhaps not so easy to read when you first encounter the codebase.

closed as primarily opinion-based by amon, Robert Harvey, GlenH7, user22815, user40980 Aug 21 '15 at 0:17

Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. If this question can be reworded to fit the rules in the help center, please edit the question.

  • What does "the same bit of computation" look like? Is it non-trivial, from a performance/memory perspective? – Robert Harvey Aug 20 '15 at 19:47
  • 2
    In any case, if it fulfills some need like better performance or less memory consumption, and you can convince yourself that it can be done safely and accurately, then it doesn't matter whether some strangers on the Internet consider it "good practice" or not. – Robert Harvey Aug 20 '15 at 19:49
  • @RobertHarvey it consists of building a tree of parents of the object, looking into each of these parents to find some stuff, and some other things like that so it's definitely worth not doing it twice. my_set gets updated in-place at some point of these "bits of computation". – Jivan Aug 20 '15 at 19:51
  • 3
    If it's a tree, you can deal with only a part of the tree, and return a new tree having a replaced branch. cf. Okasaki/Purely Functional Data Structures. That said, the mutable version seems perfectly reasonable. – Robert Harvey Aug 20 '15 at 19:53

It could be worse, but it's overly coupled, and a sign your do_stuff is too big. I would only do it if I needed the extra performance, and couldn't think of any other way.

You didn't give us much to go on, but usually problems like that can be much more cleanly decomposed in the following way:

common = [my_object.get_common() for my_object in all_objects]
my_list = list_from_common(common)
my_other_list = other_list_from_common(common)

I'd say that mutation inside a list comprehension looks unexpected and thus can be more error prone. On a code review I'd ask to rewrite it using an explicit loop which is typical for mutation.

Also, you don't need [element for element in my_set] to transform a set to a list, list(my_set) suffices. You can also just iterate over a set as you'd do over a list or any other iterable.

  • Yep, I was overly simplifying the transition from set to list - the actual transformation is more complicated than that – Jivan Aug 20 '15 at 21:10

Not the answer you're looking for? Browse other questions tagged or ask your own question.