2

I would like to create a contextmanager that would intercept any object instantiation from a given class or subclasses of this classes and do something with the object.

I sudo code it could look like something like this:

with my_context_watchdog(my_callback, BaseClass):
     obj_1 = BaseClass(name) # launch callback
     obj_2 = SubClass(name) # launch callback

     obj_3 = RandomClass(name) # does not launch callback

def my_callback(new_obj):
     print("new object name:", new_obj.name)

Is there anyway I could do something like this in Python?

The best would be that the callback is executed only after the object has been fully instantiated.

Important: I can't modify any of the object classes, they are in a library I don't have any control on it. I just want to raise a callback when some of the Classes of this library are instantiated.

Maybe the contextmanager is not the best idea, I'm opened to any other solution.

Jonathan DEKHTIAR
  • 3,456
  • 1
  • 21
  • 42

2 Answers2

2

The context manager doesn't actually see code in the with block. If you see a library doing something "magical" like this, there is usually a hidden global variable somewhere in the background, and the functions called inside the with block actively cooperate with the context manager.

That said, there's two options for you. One makes sense and one is very ugly.

The one that makes sense:

# somewhere in the context manager:
class ContextManagerReturnVal:
    def instantiate(self, cls, *args, **kwargs):
        obj = cls(*args, **kwargs)
        if issubclass(cls, self.base_class):
            self.callback(cls, *args, **kwargs)
        return obj

with watchdog(callback, BaseClass) as instantiate:
    obj1 = instantiate(BaseClass, name)
    obj2 = instantiate(SubClass, name)

And the ugly one is monkey-patching the BaseClass. You said in a comment that you can't modify the BaseClass - depends on what exactly you mean by that. If you can't modify the source code, you could still modify the class at run-time, like here: Monkey patching a class in another module in Python You would have to store the original __init__ method and return it after your context manager exits... This is of course not recommended because you're basically sticking your fingers where you shouldn't and possibly introducing hard-to-find bugs.

Other than that, I'm afraid it can't be done.

matejcik
  • 1,912
  • 16
  • 26
  • Hum... I'm not a big fan of having to use instantiate() to create objects... It will break a large part of the code base and would like to keep it as transparent as possible. Thanks anyway, I'm gonna have a look to monkey_patching may looks like promising. – Jonathan DEKHTIAR Aug 15 '18 at 23:39
1

I can see you can implement rather easily a slightly different context manager:

with my_context_watchdog(some_instance):
     pass

... but this is probably as good as a checker function and not the functionality you'd want. The magic of a context manager is __enter__ method, so there is just that much what can happen there, no any kind of massive overloads. There is probably a way to voerride __init__ method

You can also make some constructor that can differentiate the classes names and think of applying it to __new__ method too:

def new(cls):
     if cls.__name__ == 'BaseClass':
          pass
          # do something
     else:
          pass
          # do a different thing
Evgeny
  • 4,173
  • 2
  • 19
  • 39
  • I should have precised, I can't modify BaseClass nor SubClass. So impossible to modify __init__ and __new__. I will edit my post – Jonathan DEKHTIAR Aug 15 '18 at 23:28
  • you can achive some tracktion with standalone *new(cls)* function, but that would be a bit of wierd code, like ```obj_1 = new(BaseClass, name)```. – Evgeny Aug 15 '18 at 23:31
  • also a direction: https://stackoverflow.com/questions/16017397/injecting-function-call-after-init-with-decorator – Evgeny Aug 15 '18 at 23:32
  • That would definitely be a solution, however it's not intercepting the object creation, it's more encapsulating it. Why would require to rewrite a large part of the code base and break code habits. I would highly prefer not doing it something like this – Jonathan DEKHTIAR Aug 15 '18 at 23:32
  • 1
    Oh that one is nice, I'll have a look. Thanks buddy! – Jonathan DEKHTIAR Aug 15 '18 at 23:34
  • It would be an interesting solution to intercept the object contruction indeed, but it looks to be an interpreter-level hack. I doubt a context manger alone can help. – Evgeny Aug 15 '18 at 23:35