-1

I'm a beginner in python and found this list comprehension

l2, common = l2[:], [ e for e in l1 if e in l2 and (l2.pop(l2.index(e)) or True)]

in an answer to a question about how to find the common elements in two lists(question: Common elements comparison between 2 lists ).

What does the l2.pop part do? Is it there just to check if the element exists or is the removing part of pop() important too? Thank you in advance

user202729
  • 3,358
  • 3
  • 25
  • 36
  • 1
    That's definitely bad practice the linked answer uses... (although it's *allowed* to ask about how an existing answer works) – user202729 Aug 15 '21 at 03:18
  • 1
    ```if e in l2 and (l2.pop(l2.index(e)) or True)```? ```or True```? That is always True –  Aug 15 '21 at 03:19
  • @Sujay ... to clarify, it's not code OP wrote themselves, they're asking how the other SO answer linked works. – user202729 Aug 15 '21 at 04:01

1 Answers1

2

As it was mentioned by @user202729 above, this code represents a practice that should be avoided, since it modifies a mutable object (the list l2) in place inside a list comprehension and uses side effects of this modification. It is easy to make mistakes writing code like this and it may be difficult to spot these mistakes.

In any case, the goal is to create a list common which consists of elements that are common to both l1 and l2. Moreover, if some element appears n times in both lists, then it will be listed n times in common.

The code uses the fact that Python evaluates logical expressions lazily. In the conditon

if e in l2 and ((l2.pop(l2.index(e)) or True)

the first part e in l2 is evaluated first. If it gives False i.e. the element e of l1 is not in l2, then the part after and is not executed, e is not included in common, and the list comprehension moves on to the next element of l1.

On the other hand, if e in l2 evaluates to True, then the part after and gets evaluated. The value of this part is always True (due to the or True bit), so e becomes an element of common. However, Python first runs l2.pop(l2.index(e)) which removes one occurrence of the element e from the list l2. For this reason, if the iteration encounters the same element e in the list l1 again, then this element will be included in common for the second time only if it appears in l2 also more than once.

bb1
  • 7,174
  • 2
  • 8
  • 23
  • 1
    It's also needlessly complicated, could use `[l2.remove(e) or e for e in l1 if e in l2]`. – Kelly Bundy Aug 15 '21 at 04:10
  • 1
    @KellyBundy. Sure, or `[l2.pop(l2.index(e)) for e in l1 if e in l2]`. – bb1 Aug 15 '21 at 04:20
  • Thank you! Just to clarify, the return from pop doesn't actually do anything and any function that'll remove e from l2 could actually do? – fairykingoberon Aug 15 '21 at 05:11
  • @fairykingoberon Yes, the return from `pop` is not used. You could write a function that removes an element from `l2` and returns an object with no True/False value, and such a function would not work here. But aside from such contrived examples, any function would do. – bb1 Aug 15 '21 at 05:23
  • *"such a function would not work"* - Why not? Can you show such a function? – Kelly Bundy Aug 15 '21 at 15:32
  • @KellyBundy The return value is used to evaluate a boolean expression, so it needs to have an associated boolean value. As an example of something that will not work, you can write a function that removes an element of `l2` but returns some numpy array with more than one element. – bb1 Aug 15 '21 at 16:17
  • Ah, no truth value, raising an error. Naming "True/False" misled me. Good example. Though now I'm wondering whether ["Any object can be tested for truth value"](https://docs.python.org/3/library/stdtypes.html#truth-value-testing) should be rephrased. Oh well. – Kelly Bundy Aug 15 '21 at 16:34