Rick
Rick

Reputation: 45231

Is it possible to delete an external object reference (variable) from within a function without using exec, eval, compile?

I believe I am on my way to thoroughly understanding how Python uses variables and references, etc, and how they are passed between functions.

As a result of the way Python does things, if I pass a reference variable to a function and attempt to delete the variable inside:

class C(): pass
c = C()
def euthanize(patient): del patient
euthanize(c)

The external object reference will not be deleted because the function has only removed a local reference variable (which was created when the function was called), not the reference variable from the external context:

assert c #No error

This is also the case for a class:

class C():
    def die(self): c.__del__() #same result for 'del c' here 
    def __del__(self): pass
c = C()
c.die()
assert c #No error

This leads me to believe that there is no way, aside from using something like exec or eval, to delete a variable from outside of the context in which it was created. Is this correct?

Clarification

I'm sure I'm going to get a lot of "Why would you want to do this?" comments. Please refrain. I have come to understand that, for Python, if there isn't a somewhat straight forward way to accomplish a task, then it is probably something I shouldn't be doing in the first place and I should look into another approach. I am asking the question because I want to make sure I am correct that this is one of those situations, and therefore not something most people will find that they need to accomplish (and of course, if you REALLY DO need to do it, there is always exec!). I have noticed people are VERY quick to ask "WHY WOULD YOU WANT TO...?" on SO. This is understandable, but sometimes very annoying. Please just don't do it in this case, alright?

Upvotes: 1

Views: 334

Answers (3)

Rick
Rick

Reputation: 45231

I came up with a way that you can sort-of kind-of do this. You have to use something like a container or a class attribute.

For example:

def euthanize(patient,index): del patient[index]
class C: pass
L = [C()]
euthanize(L,0)
assert len(L) == 0

This isn't exactly what I was asking in the question above, but it is similar because it does remove the external strong reference to the object L[0] which was created in the above code, and the object will eventually be GC'd. This can be tested using weakref.finalize:

import weakref
L = [C()]
weakref.finalize(L[0],lambda: print("L[0] finalized!"))
euthanize(L,0) #OUTPUT: L[0] finalized!

However, to be precise, the function is not deleting an object reference. L is the object reference. The function simply manipulates L. EDIT: And as BrenBarn noted below, if there are any other references to the member of L I am trying to euthanize, those are not removed.

Here's how it would look using a class attribute:

c = type('C',(object,),{})()
setattr(c,'X',1)
def euthanizeX(obj): del obj.X
assert c.X #AttributeError (expected)

Upvotes: 0

ch3ka
ch3ka

Reputation: 12158

one way to do it, but probably not what you had in mind:

>>> class C(): pass
... 
>>> c = C()
>>> def euthanize(patient):
...   del globals()[patient]
... 
>>> euthanize('c')
>>> c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'c' is not defined

another way, not requiring the patient to be passed as string (but still does not clear closures and such):

>>> def euthanize(patient):
...  for p in [x[0] for x in 
               globals().items() if 
                    x[1] is patient]:
...    del globals()[p]
... 
>>> c
<__main__.C object at 0x7fe8c4a3d518>
>>> euthanize(c)
>>> c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'c' is not defined

Upvotes: 1

BrenBarn
BrenBarn

Reputation: 251363

You are basically right, but I think the reason is somewhat broader than you seem to believe. As discussed in the link you mentioned, arguments are passed "by object reference". In other words, when you do euthanize(c), euthanize only gets the object referred to by c; it doesn't know how you referrred to it. In this case you used a bare name c, but euthanize doesn't know that. You might not have used a variable at all; your example would be equivalent if it was euthanize(C()), in which case there is never any name that refers to the C instance.

So, the reason you can't delete a "variable" from the enclosing namespace is that what you are trying to delete is a name in the enclosing namespace, while function arguments pass only values. (This isn't pass-by-value, though, because the value is an object reference, which is mentioned in your link so I'm assuming you get what I mean here.)

Your example with __del__ is a bit of a red herring. __del__ is not really deleting anything. It may be called if there are no further references to an object, but calling del c in your second example might or might not result in c.__del__ being called (right away or later). So even doing c = C() and then c.__del__() right in the same global namespace wouldn't delete the variable (that is, the name) called c.

As ch3ka's answer suggests, the only way to delete a name is to pass it around as a string and then use it to to do something like del globals(someName). However, even this won't always work; it works for globals, but you can't, for instance, use it in a nested function to delete a local variable of the enclosing function.

Upvotes: 1

Related Questions