2

Is there a method like pop that temporarily removes, say an element of a list, without permanently changing the original list? One that would do the following:

  1. list = [1,2,3,4]
  2. newpop(list, 0) returns [2,3,4]
  3. list is unchanged

I come from R, where I would just do c(1,2,3,4)[-4] if I wanted to temporarily remove the last element of some list, so forgive me if I'm thinking backwards here.

I know I could write a function like the following:

def newpop(list, index):
   return(list[:index] + list[index+1 :]

, but it seems overly complex? Any tips would be appreciated, I'm trying to learn to think more Python and less R.

Helen
  • 533
  • 12
  • 37
  • 2
    why do you think your code is complex? It looks fine – deadshot Sep 21 '20 at 18:30
  • I tried comparing it to R, where I would just do the simple step I showed above, and it does the trick. So, intuitively I'm thinking that there must be a simpler way, or at least some reason why it is so simple in R, and not as simple in Python. That would help with my understanding of the Python language, probably =) – Helen Sep 21 '20 at 18:32
  • Do you want to literally pop the item from the list temporarily (and then add it back when you are done),or create a copy of the list without that element? – tobias_k Sep 21 '20 at 18:32
  • 1
    what does "temporary" mean, i.e. when you expect to restore the original list? Do you use the "temporary" list or you just need the element at position index? – buran Sep 21 '20 at 18:32
  • 2
    The short answer: no, there is no equivalent built-in method. – juanpa.arrivillaga Sep 21 '20 at 18:33
  • @tobias_k: I would like to do some work on the list without the "popped out" element, for example in a loop, place back the element in the list, and do same operation with some other element "popped out", without deleting the original list. – Helen Sep 21 '20 at 18:33
  • 2
    It seems to make more sense to simply make a new list, like your function already does – juanpa.arrivillaga Sep 21 '20 at 18:34
  • @buran: yes, I use the temporary list. Sorry for my phrasing. Maybe this was a stupid question, I'm new to Python as you understand. – Helen Sep 21 '20 at 18:38
  • @juanpa.arrivillaga: Ok, thanks! =) – Helen Sep 21 '20 at 18:38
  • I think you meant to ask if python has immutable lists, and the answer is yes but they are called tuples. See this [question](https://stackoverflow.com/questions/11142397/does-python-have-an-immutable-list) – smac89 Sep 21 '20 at 18:44
  • @smac89 yeah, but python immutable types aren't really designed for functional updates like this, Python is at is core an imperative language – juanpa.arrivillaga Sep 21 '20 at 18:46
  • @smac89: I'm afraid I don't know what a tuple is, you are giving me more credit than I deserve =) – Helen Sep 21 '20 at 18:54

1 Answers1

2

I am probably taking the "temporary" bit way too litaral, but you could define a contextmanager to pop the item from the list and insert it back in when you are done working with the list:

from contextlib import contextmanager

@contextmanager
def out(lst, idx):
    x = lst.pop(idx)   # enter 'with'
    yield              # in `with`, no need for `as` here
    lst.insert(idx, x) # leave 'with'

lst = list("abcdef")
with out(lst, 2):
    print(lst)
    # ['a', 'b', 'd', 'e', 'f']
print(lst)
# ['a', 'b', 'c', 'd', 'e', 'f']

Note: This does not create a copy of the list. All changes you do to the list during with will reflect in the original, up to the point that inserting the element back into the list might fail if the index is no longer valid.

Also note that popping the element and then putting it back into the list will have complexity up to O(n) depending on the position, so from a performance point of view this does not really make sense either, except if you want to save memory on copying the list.


More akin to your newpop function, and probably more practical, you could use a list comprehension with enumerate to create a copy of the list without the offending position. This does not create the two temporary slices and might also be a bit more readable and less prone to off-by-one mistakes.

def without(lst, idx):
    return [x for i, x in enumerate(lst) if i != idx]

print(without(lst, 2))
# ['a', 'b', 'd', 'e', 'f']

You could also change this to return a generator expression by simply changing the [...] to (...), i.e. return (x for ...). This will then be sort of a read-only "view" on the list without creating an actual copy.

tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • This might be a kind of nifty example of context managers for people who haven't used them before, but for most practical purposes, the original code in the question is probably less error-prone and more useful. – user2357112 Sep 21 '20 at 19:08
  • @user2357112supportsMonica In my defense, I think I made that clear in the explanation, and I did not expect this to be the accepted answer, either. – tobias_k Sep 21 '20 at 19:10
  • @tobias_k: I loved this enumerate in list comprehension, thank you very much! – Helen Sep 22 '20 at 06:36