10

For specific debugging purposes I'd like to wrap the del function of an arbitrary object to perform extra tasks like write the last value of the object to a file.

Ideally I want to write monkey(x) and it should mean that the final value of x is printed when x is deleted

Now I figured that del is a class method. So the following is a start:

class Test:
    def __str__(self):
        return "Test"

def p(self):
    print(str(self))

def monkey(x):
    x.__class__.__del__=p

a=Test()
monkey(a)
del a

However if I want to monkey specific objects only I suppose I need to dynamically rewrite their class to a new one?! Moreover I need to do this anyway, since I cannot access del of built-in types?

Anyone knows how to implement that?

Gerenuk
  • 141
  • 6

4 Answers4

8

While special 'double underscore' methods like __del__, __str__, __repr__, etc. can be monkey-patched on the instance level, they'll just be ignored, unless they are called directly (e.g., if you take Omnifarious's answer: del a won't print a thing, but a.__del__() would).

If you still want to monkey patch a single instance a of class A at runtime, the solution is to dynamically create a class A1 which is derived from A, and then change a's class to the newly-created A1. Yes, this is possible, and a will behave as if nothing has changed - except that now it includes your monkey patched method.

Here's a solution based on a generic function I wrote for another question: Python method resolution mystery

def override(p, methods):
    oldType = type(p)
    newType = type(oldType.__name__ + "_Override", (oldType,), methods)
    p.__class__ = newType


class Test(object):
    def __str__(self):
        return "Test"

def p(self):
    print(str(self))

def monkey(x):
    override(x, {"__del__": p})

a=Test()
b=Test()
monkey(a)
print "Deleting a:"
del a
print "Deleting b:"
del b
Community
  • 1
  • 1
Boaz Yaniv
  • 6,334
  • 21
  • 30
  • 2
    Two problems with this approach: First, it won't work with heap types like int or list, because you cannot re-assign their `__class__`. Second, type checking will lead to different results depending on wether an object was monkey()'ed or not. That might lead to very confusing errors. – pillmuncher May 15 '11 at 20:25
  • 1
    In my comment above there's a typo: it should read 'non-heap types'. – pillmuncher May 15 '11 at 20:45
  • I don't think there's a solution for non-heap types. As for *direct* (i.e. non-derived) type checking, this is something that really shouldn't be done on a language that relies so heavily on duck-typing, like Python. There might be rare cases when its useful, but really, type-checking should be considered much more unsafe than deriving on the fly. – Boaz Yaniv May 15 '11 at 21:03
  • I agree on the _direct type checking_ equals _bad_ almost all of the time. But even with _derived type checking_ there's a problem: given classes `Base` and `Derived(Base)` and `x = Base()` and `y = Derived()`, then `isinstance(y, x.__class__)` is True, whereas `isinstance(y, monkey(x).__class__)` is not, so changing some `x = Base()` to `x = monkey(Base())` might break the program. – pillmuncher May 15 '11 at 21:43
  • Wow, thanks! I think that's what I need. However it would be nice if the newly created class had the same super classes, too. Is it possible? Despite duck typing I plan to make some subclass tests to make error messages for users who are not careful. – Gerenuk May 15 '11 at 21:51
  • 3
    The new class is derived from the old class - so yeah, it already has the same superclasses. As long as you check the classes with `isinstance(x, C)` and not with `type(x) == C` (which you shouldn't ever do as a rule), everything will be ok. – Boaz Yaniv May 16 '11 at 01:19
  • what if you want to override class-wise, not instance-wise? can it be done without the dynamically-created class? – n611x007 Apr 29 '14 at 17:22
  • 1
    @naxa: Then just override the method on the class directly, as the example given in the question. You don't need to do anything special if you want to override on the class level: it just works! – Boaz Yaniv Apr 30 '14 at 13:21
1

del a deletes the name 'a' from the namespace, but not the object referenced by that name. See this:

>>> x = 7
>>> y = x
>>> del x
>>> print y
7

Also, some_object.__del__ is not guaranteed to be called at all.

Also, I already answered your question here (in german).

pillmuncher
  • 10,094
  • 2
  • 35
  • 33
0

You can also inherit from some base class and override the __del__ method (then only thing you would need would be to override class when constructing an object). Or you can use super built-in method.

Dr McKay
  • 2,548
  • 2
  • 21
  • 26
  • This trick is for some adhoc debugging so I'd rather not define real base classes. Also I'd need to remember call super from any derived class with a __del__ function. And finally this wouldn't work for built-ins. I hope there is a way to also monkey a normal list?! – Gerenuk May 15 '11 at 18:48
  • http://stackoverflow.com/questions/192649/can-you-monkey-patch-methods-on-core-types-in-python - second answer here is a great explanation – Dr McKay May 15 '11 at 18:59
0

Edit: This won't actually work, and I'm leaving it here largely as a warning to others.

You can monkey patch an individual object. self will not get passed to functions that you monkey patch in this way, but that's easily remedied with functools.partial.

Example:

def monkey_class(x):
    x.__class__.__del__ = p

def monkey_object(x):
    x.__del__ = functools.partial(p, x)
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • Have you tried that? Not sure if I did some mistake, but apparently x.__del__ is ineffective. Probably because del is strictly a class method?! Somehow I cannot get it work :( And I get the feeling that the .partial keeps a reference to x so that it isn't garbage collected?! – Gerenuk May 15 '11 at 19:13
  • It doesn't work since special methods are only called with the class dict. This solution would work only for 'regular' methods. – Boaz Yaniv May 15 '11 at 20:02
  • @Boaz Yaniv: Oops, you learn something new every day. I'm leaving this answer here as a warning to others. :-) – Omnifarious May 15 '11 at 21:16