1

In Ruby, one can create callbacks for when methods are defined on an object:

module Chatty
  def self.method_added(method_name)
    puts "Adding #{method_name.inspect}"
  end
  def self.some_class_method() end
  def some_instance_method() end
end
# => Adding :some_instance_method

Does Python have any similar callback?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
fny
  • 31,255
  • 16
  • 96
  • 127

1 Answers1

7

In Python, methods are just attributes that happen to be callable*. You'd have to hook in to attributes being set to see new methods being added to a class.

You'd have to use a metaclass to intercept new attributes being added to a class:

import types

class NewMethodAlerter(type):
    def __setattr__(self, name, obj):
        if isinstance(obj, types.FunctionType):
            print(f'New method {name} added!')
        super().__setattr__(name, obj)


class Demo(metaclass=NewMethodAlerter):
    def existing_method(self):
        pass

def new_method(self): pass
Demo.new_method = new_method

which then looks like this:

>>> class Demo(metaclass=NewMethodAlerter):
...     def existing_method(self):
...         pass
...
>>> def new_method(self): pass
>>> Demo.new_method = new_method
New method new_method added!

If you wanted to know about the initial set of attributes, the result of executing the class body, then you have two options: use a metaclass, or in Python 3.6 and up, the __init_subclass__ method. Either one is called to create new classes, and can be used to inspect the attributes:

class InitialMethodAlerter(type):
    def __new__(typ, name, bases, attrs):
        for name, obj in attrs.items():
            if isinstance(obj, types.FunctionType):
                print(f'Method {name} defined!')
        return super().__new__(typ, name, bases, attrs)

class Demo(metaclass=InitialMethodAlerter):
    def existing_method(self):
        pass

or the __init_subclass__ method:

class InitialMethodAlerter:
    @classmethod
    def __init_subclass__(cls, **kwargs):
        for name, obj in vars(cls).items():
            if isinstance(obj, types.FunctionType):
                print(f'Method {name} defined!')

class Demo(InitialMethodAlerter):
    def existing_method(self):
        pass

You may want to read up on metaclasses at What is a metaclass in Python?


*Well, the attributes are functions actually. Functions are descriptor objects, which causes them to be bound when accessed via an instance. That binding process produces a method object, that when called takes the original function and passes in the instance to which it was bound.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • So there's no way to intercept methods defined in the class definition? – fny Sep 14 '17 at 20:32
  • @faraz: what are you trying to achieve here? I may have misunderstood. The metaclass is *also* told about all the initial attributes for the class, as the class is created. – Martijn Pieters Sep 14 '17 at 20:33
  • I'd expect `New method existing_method added!` to appear after the class is defined. – fny Sep 14 '17 at 20:37
  • @faraz: your question was confusing me there, because of the terminology 'added'. I've updated the question, but it's more and more a duplicate of [What is a metaclass in Python?](//stackoverflow.com/q/100003) now. – Martijn Pieters Sep 14 '17 at 20:42
  • @faraz One small clarification to help you understand why you are not seeing the message about the existing method: the `__setattr__` method isn't called when the class body is executed and all of the initial class members are created. There is a separate set of machinery invoked for class *creation*. – Rick Sep 14 '17 at 20:44
  • Thank you so much for this! I've just started diving into metaprogramming in Python, and this was immensely helpful. – fny Sep 14 '17 at 20:48