Is the use of del bad?
Asked Answered
B

7

52

I commonly use del in my code to delete objects:

>>> array = [4, 6, 7, 'hello', 8]
>>> del(array[array.index('hello')])
>>> array
[4, 6, 7, 8]
>>> 

But I have heard many people say that the use of del is unpythonic. Is using del bad practice?

>>> array = [4, 6, 7, 'hello', 8]
>>> array[array.index('hello'):array.index('hello')+1] = ''
>>> array
[4, 6, 7, 8]
>>> 

If not, why are there many ways to accomplish the same thing in python? Is one better than the others?

Option 1: using del

>>> arr = [5, 7, 2, 3]
>>> del(arr[1])
>>> arr
[5, 2, 3]
>>> 

Option 2: using list.remove()

>>> arr = [5, 7, 2, 3]
>>> arr.remove(7)
>>> arr
[5, 2, 3]
>>> 

Option 3: using list.pop()

>>> arr = [5, 7, 2, 3]
>>> arr.pop(1)
7
>>> arr
[5, 2, 3]
>>> 

Option 4: using slicing

>>> arr = [5, 7, 2, 3]
>>> arr[1:2] = ''
>>> arr
[5, 2, 3]
>>> 

I am sorry if this question appears to be opinion-based, but I am looking for a reasonable answer to my question, and I will add a bounty after 2 days if I don't get a suitable answer.

Edit:

Since there are many alternates to using del to delete certain parts of objects, the one unique factor left of del is its ability to remove objects completely:

>>> a = 'hello'
>>> b = a
>>> del(a)
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
'hello'
>>> 

However, what is the point of using it to 'undefine' objects?

Also, why does the following code change both variables:

>>> a = []
>>> b = a
>>> a.append(9)
>>> a
[9]
>>> b
[9]
>>> 

But the del statement does not achieve the same effect?

>>> a = []
>>> b = a
>>> del(a)
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
[]
>>> 
Blaspheme answered 28/4, 2014 at 2:3 Comment(20)
many people [citation needed]. Who says that, and what was the context in which it was said?Broughton
array.remove('hello') is simpler than retrieving the index from the value then deleting the item on the index. Another reason that people may prefer array.remove() over del(array...) is that array.remove() is more "Object Oriented".Dangelo
The thing is that all of these operations modify the input list and are linear time. That's somewhat of a waste, because you can create a new list with the element removed in linear time as well, while retaining the old listCutwork
btw Python lists are lists not arrays; there are also arrays in python so it's better not to call your list variables "array".Waring
The use of del is probably not bad, but the existence of the syntax del for deleting list indices is an awkward feature of the language, just like len(..), both of which annoy newcomers who complain about it going against the grain of other popular languages without a clear justification.Cleanshaven
optimization is always pointless in Python, you are far behind of what you can do in other languages anywayMeltage
@SargeBorsch So if someone has a triply nested loop that can be done in one step, we should just tell them to switch to C, right?Lussi
@EvgeniSergeev The justification is often polymorphism. E.g., for len, why have list.length, str.length, array.length, etc., when you can have one function that gives you the length of any object that can reasonably have one? (i.e., any object with a __len__)?Lussi
possible duplicate of When is del useful in python?Glossary
@Two-BitAlchemist In a duck typed language we get polymorphism just by having the methods that do the same thing having the same name. A good usecase for len that someone mentioned is map(len, listoflists), so it has its place. I don't see why we shouldn't have a .size() method on built-in data structures too. The built-in len(..) does a bit of checking after calling an object's .__len__(), but it seems to be more paranoid than useful #496509Cleanshaven
@EvgeniSergeev len has been hanging around Python longer than that's been reasonably possible to implement. I guess that could have been done in Py3k but it's not like Python has a ridiculous number of global built-ins like len and needs to pare down.Lussi
@Two-BitAlchemist I'm not saying it should be deprecated. I'm saying that .__len__() shouldn't be magic. (It should be called .size() and len(x) will call x.size() or you can just use x.size().)Cleanshaven
actually functions can be nicer than methods — look at Haskell :)Waring
@aj8uppal: you should open another question about the very last part of this question about del and append.Waring
@ErikAllik, good point, done. :)Blaspheme
Downvoter, why the downvote?Blaspheme
@aj8uppal could you share the link to the question about the del and = behavior?Lexical
Here: #23377978Blaspheme
anything missing from my answer suddenly? :) just curious.Waring
Sorry, I don't know why that happened :)Blaspheme
W
49

The other answers are looking at it from a technical point of view (i.e. what's the best way to modify a list), but I would say the (much) more important reason people recommend, for example, slicing, is that it doesn't modify the original list.

The reason for this in turn is that usually, the list came from somewhere. If you modify it, you can unknowningly cause serious and hard-to-detect side effects, which can cause bugs elsewhere in the program. Or even if you don't cause a bug immediately, you'll make your program overall harder to understand and reason about, and debug.

For example, list comprehensions/generator expressions are nice in that they never mutate the "source" list they are passed:

[x for x in lst if x != "foo"]  # creates a new list
(x for x in lst if x != "foo")  # creates a lazy filtered stream

This is of course often more expensive (memory wise) because it creates a new list but a program that uses this approach is mathematically purer and easier to reason about. And with lazy lists (generators and generator expressions), even the memory overhead will disappear, and computations are only executed on demand; see http://www.dabeaz.com/generators/ for an awesome introduction. And you should not think too much about optimization when designing your program (see https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil). Also, removing an item from a list is quite expensive, unless it's a linked list (which Python's list isn't; for linked list, see collections.deque).


In fact, side-effect free functions and immutable data structures are the basis of Functional Programming, a very powerful programming paradigm.

However, under certain circumstances, it's OK to modify a data structure in place (even in FP, if the language allows it), such as when it's a locally created one, or copied from the function's input:

def sorted(lst):
    ret = list(lst)  # make a copy
    # mutate ret
    return ret

— this function appears to be a pure function from the outside because it doesn't modify its inputs (and also only depends on its arguments and nothing else (i.e. it has no (global) state), which is another requirement for something to be a Pure Function).

So as long as you know what you're doing, del is by no means bad; but use any sort of data mutation with extreme care and only when you have to. Always start out with a possibly less efficient but more correct and mathematically elegant code.

...and learn Functional Programming :)

P.S. note that del can also be used to delete local variables and thus eliminate references to objects in memory, which is often useful for whatever GC related purposes.


Answer to your second question:

As to the second part of your question about del removing objects completely — that's not the case: in fact in Python, it is not even possible to tell the interpreter/VM to remove an object from memory because Python is a garbage collected language (like Java, C#, Ruby, Haskell etc) and it's the runtime that decides what to remove and when.

Instead, what del does when called on a variable (as opposed to a dictionary key or list item) like this:

del a

is that it only removes the local (or global) variable and not what the variable points to (every variable in Python holds a pointer/reference to its contents not the content itself). In fact, since locals and globals are stored as a dictionary under the hood (see locals() and globals()), del a is equivalent to:

del locals()['a']

or del globals()['a'] when applied to a global.

so if you have:

a = []
b = a

you're making a list, storing a reference to it in a and then making another copy of that reference and storing it into b without copying/touching the list object itself. Therefore, these two calls affect one and the same object:

a.append(1)
b.append(2)
 # the list will be [1, 2]

whereas deleting b is in no way related to touching what b points to:

a = []
b = a
del b
# a is still untouched and points to a list

Also, even when you call del on an object attribute (e.g. del self.a), you're still actually modifying a dictionary self.__dict__ just like you are actually modifying locals()/globals() when you do del a.

P.S. as Sven Marcnah has pointed out that del locals()['a'] does not actually delete the local variable a when inside a function, which is correct. This is probably due to locals() returning a copy of the actual locals. However, the answer is still generally valid.

Waring answered 28/4, 2014 at 2:26 Comment(3)
"This is of course often slower" Maybe that's not even the case for this particular scenario, because deleting something from the middle of a list is also linear time. In any case the cost is low compared to the benefitsCutwork
"del a is equivalent to del locals()['a']": Not really, since the latter does nothing inside a function. You can't modify the dictionary returned by locals() in any meaningful way. (You can modify globals() though.)Personable
@NiklasB. If you're deleting just one thing, del is probably quicker than a copy (it likely needs to shift the elements over, and will need to shift at most 100% of them). However, like Lays chips, sometimes people can't just stop at one del and really should have used a list comprehension or filter.Sacci
B
12

Python simply contains many different ways to remove items from a list. All are useful in different situations.

# removes the first index of a list
del arr[0]

# Removes the first element containing integer 8 from a list
arr.remove(8)

# removes index 3 and returns the previous value at index 3
arr.pop(3)

# removes indexes 2 to 10
del arr[2:10]

Thus they all have their place. Clearly when wanting to remove the number 8, example number 2 is a better option than 1 or 3. So it's really what makes sense based on the circumstances and what is most logically sound.

EDIT

The difference between arr.pop(3) and del arr[3] is that pop returns the removed item. Thus it can be useful for transferring removed items into other arrays or data structures. Otherwise the two do not differ in use.

Bookstack answered 28/4, 2014 at 2:11 Comment(1)
Minor clarification: Shouldn't "Remove the first index of a list" be del arr[0]? Unless I'm missing some nuance of del?Lockwood
T
10

Nope, I don't think using del is bad at all. In fact, there are situations where it's essentially the only reasonable option, like removing elements from a dictionary:

k = {'foo': 1, 'bar': 2}
del k['foo']

Maybe the problem is that beginners do not fully understand how variables work in Python, so the use (or misuse) of del can be unfamiliar.

Transubstantiate answered 28/4, 2014 at 2:10 Comment(0)
P
6

The use of del itself is not bad per se; however, it has two aspects that contribute to particular code smells:

  1. It is a side-effect, part of a sequence of steps, and not meaningful by itself.
  2. It may be that del occurs in code that has manual memory management indicative of poor understanding of Python scoping and automatic memory management. In the same way that the with statement is more idiomatic for handling file handles than file.close, using scope and context is more idiomatic than manually nuking members.

But this is hardly canon – if the del keyword were truly "bad" it would not be in the core of the language. I'm just trying to play Devil's Advocate – to explain why some programmers may call it "bad" and, possibly, give you a position to argue against. ;)

Penurious answered 28/4, 2014 at 2:24 Comment(5)
del is "entirely monadic"?? I think you're drawing a very very very loose and even arbitrary connection between Haskell's IO monad (which, I agree, is semi-synonymous with "side effects" in the FP/Haskell world) and the general notion of side-effectful code; but this connection is completely misleading — if somebody who does not know what a Monad is, reads this answer, he will be totally misled and disoriented once he sees the word "monad" used appropriately.Waring
del removes a name binding -- how is that not meaningful?Riane
@EthanFurman it's a tree falls in the forest question. A del doesn't have any meaning unless something else observes its effects. Ideally, a compiler would eliminate meaningless side-effect commands using DCE, but it's not always possible.Penurious
del is not unique in this. a = 1 + c is not meaningful by itself either, nor any code anywhere unless and until it is observed / used by another part of the system. I see no reason to single del out like this.Riane
@EthanFurman Then argue with the scope of the entire question up top. As I said, I don't actually think del is bad.Penurious
S
2

I don't think I've ever heard anyone say that del is evil, at least no more than any other language feature. The question between del and other approaches really comes down to your use cases. The following cases are great for del:

  1. Deleting variables from your current scope. Why would you want to do this? Imagine you are declaring a module that calculates a package variable, but that consumers of that module never need it. While you could create a whole new module for it, that could be overkill or could obscure what is actually being calculated. For an example, you might want the following:

    GLOBAL_1 = 'Some arbitrary thing'
    GLOBAL_2 = 'Something else'
    
    def myGlobal3CalculationFunction(str1, str2):
        # Do some transforms that consumers of this module don't need
        return val
    
    GLOBAL_3 = myGlobal3CalculationFunction(GLOBAL_1, GLOBAL_2)
    # Mystery function exits stage left
    del myGlobal3CalculationFunction
    

    Basically nobody disagrees with using del to delete variables from scope, when necessary. The same applies to values in dictionaries, or pretty much anything accessed by name or similar immutable references (class properties, instance properties, dict values, etc.).

  2. The other case is where you want to delete an item from a list or similar ordered sequence. Which really aren't that different from the first case in some ways (seeing as they can all be accessed as key-value containers, with lists just happening to have reliably-ordered integer keys). In all of these cases, you are in the same boat of wanting to remove a reference to some data that exists in that particular instance (since even classes are an instance of being a class). You're doing an in-place modification.

    Does having ordered and special indices mean anything is different for lists? The fundamental difference with a list is that doing an in-place modification makes all your old keys basically useless, unless you are very careful. Python gives you the great ability to represent data very semantically: rather than having a list of [actor, verb, object] and mapping indices, you can have a nice dict of {'actor' : actor, 'verb' : verb, 'object' : object}. There's often a lot of value in that kind of that sort of access (which is why we access functions by name, rather than by number): if the order isn't important, why make it rigid? If your order IS important, why are you messing up something makes all your references to it invalid (i.e., element positions, distance between elements).

The issue comes down to why you would be directly deleting a list value by index. In most cases, operations that modify single elements of lists in-place have obvious implementations through other functions. Killing an item with a given value? You remove it. Implementing a queue or a stack? You pop it (don't lock it). Reducing the ref count for an instance in the list? l[i] = None works just as well, and your old indices still point to the same things. Filtering elements? You filter or use a list comprehension. Making a copy of the list, minus some elements? You slice it. Getting rid of duplicate, hashable elements? You can list(set([])) or look at itertools if you just need to traverse the unique elements once.

After you get rid of all those cases, you end up with about two common use-cases for using del for a list. First, you might be deleting random elements by index. There are more than a few cases where this might be useful, and del is totally appropriate. Second, you have stored indices that represent where you are in the list (i.e., walking from room to room in a hallway where you randomly destroy a room sometimes, from the Charlie Sheen Programming Style Guide). This gets tough if you have more than one index for the same list, as using del means that all the indices need to be adjusted accordingly. This is less common, since structures that you walk using indices are often not ones that you delete elements from (e.g., coordinate grids for a game board). It does happen though, such as while-looping over a list to poll jobs and deleting the ones that have completed.

This indicates the fundamental issue with deleting elements from a list in-place by index: you're pretty much stuck doing it one at a time. If you have the indices of two elements to delete, then delete the first one? There's a good chance your old index doesn't point to what it used to. Lists are for storing order. Since del alters the absolute order, you're stuck walking or jumping along the list. Again, there are solid use-cases (e.g., random destruction), but there are tons of other cases that are just wrong. Particularly among new Python programmers, people do horrible things with while loops on functions (i.e., loop until you find a value that matches an input, del the index). Del requires an index as an input, and as soon as it is run, makes all existing indices referring to that list refer to entirely different data. You can see where that's a maintenance nightmare if multiple indices are maintained. Again, it's not bad. It's just that it's seldom in practice the best way to do things with a list in Python.

Sacci answered 3/5, 2014 at 1:40 Comment(0)
H
1

Regarding the questoin in your "EDIT",

>>> a = []
>>> b = a
>>> a.append(9)
>>> a
[9]
>>> b
[9]
>>> del a
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
[9]
>>>

this is easily explained, remember that:

>>> id(a) == id(b) 
True

(a and b point to the same object in memory) and that memory in python is managed by the GC. When calling del on an object, you merely decrease it's reference count by 1 (along with deleting the name from the scope), the object is destroyed when the reference count reaches 0. In that case, b still holds a reference to the object, therefore it's not destroyed and still accessible.

You can find more info here

Histogram answered 30/4, 2014 at 7:51 Comment(0)
E
1

del just mutates the variable, which is sometimes unnecessary. Therefore, your above solutions may be better. However, del is the only way to 'destroy' variables, and to remove them forever:

>>> a = 9
>>> del(a)
>>> a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> 

Also, you can remove items from dictionaries:

>>> dict = {1: 6}
>>> dict[1]
6
>>> del(dict[1])
>>> dict
{}
>>> 
Erik answered 1/5, 2014 at 4:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.