How to automatically “register” methods in a python class as a list class variable?

When defining a Python class, I’d like to use decorators to register some of its methods into a class variable list. Here’s an example of incorrect python that outlines what I’m looking for:

class MyClass:

    dangerous_methods = []

    @classmethod
    def dangerous_method(cls, func):
        cls.dangerous_methods.append(func)
        return func

    @MyClass.dangerous_method
    def incinerate(self):
        pass

    def watch_tv(self):
        pass

    @MyClass.dangerous_method
    def stab(self):
        pass

    def print_dangerous_methods(self):
        print(self.dangerous_methods)


obj = MyClass()
obj.print_dangerous_methods()

with the expected output being

[<function MyClass.incinerate at 0x000001A42A629280>, <function MyClass.stab at 0x000001A42A629281>]

Is it possible to do this without torturing Python too much?

Answer

This is one way to implement that:

class MyClass:
    def __init__(self):
        self.dangerous_methods = []

    def dangerous_method(func):
        def inner(self):
            self.dangerous_methods.append(func)
            return func(self)
        return inner

    @dangerous_method
    def incinerate(self):
        print('Incinerate called!')
        pass

    def watch_tv(self):
        print('Watch_tv called!')
        pass

    @dangerous_method
    def stab(self):
        print('Stab called!')
        pass

    def print_dangerous_methods(self):
        print(self.dangerous_methods)


obj = MyClass()
obj.incinerate()
# Incinerate called!
obj.watch_tv()
# Watch_tv called!
obj.stab()
# Stab called!
obj.incinerate()
# Incinerate called!
obj.print_dangerous_methods()
# [<function MyClass.incinerate at 0x0000029C11666EE8>, <function MyClass.stab at 0x0000029C11666B88>, <function MyClass.incinerate at 0x0000029C11666EE8>]

Just note that in this way, functions are being added to the list ONLY once they’ve called and there is a risk that a function being added to the list multiple times. However, if you know that there are some functions in mind that you want to add to the list and they’re constants, you can simply add them while the object is being constructed:

class MyClass:
    def __init__(self):
        self.dangerous_methods = [self.incinerate, self.stab]

    def incinerate(self):
        print('Incinerate called!')
        pass

    def watch_tv(self):
        print('Watch_tv called!')
        pass

    def stab(self):
        print('Stab called!')
        pass

    def print_dangerous_methods(self):
        print(self.dangerous_methods)


obj = MyClass()
obj.print_dangerous_methods()
# [<bound method MyClass.incinerate of <__main__.MyClass object at 0x0000029C11388F08>>, <bound method MyClass.stab of <__main__.MyClass object at 0x0000029C11388F08>>]