python-3.xoop

Python, a method that is always executed before other methods in an instance of the class


I want to make it so that before executing the method of the class instance, another mandatory method was carried out:

class Example:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def method_before(self):
        print(f"x = {self.x}, y = {self.y}")

    def sum_numbers(self):
        self.method_before()
        print(self.x + self.y)

    def mult_numbers(self):
        self.method_before()
        print(self.x * self.y)


test_instance = Example(x=10, y=2)
test_instance.sum_numbers()

This is simplified example. How can I make the method method_before() run automatically without explicitly specifying it in other methods? Thank you if you share the ways


Solution

  • Here are three potential paths forward. Unfortunately, none of them are completely free of "additional specifications". Note I renamed method_before() to flag it as "non-public" and make excluding it with the dunder methods more convenient.

    Conventional Decorator:

    class Example():
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        @staticmethod
        def _method_before(f):
            def wrapper(self, *args, **kwargs):
                print(f"x = {self.x}, y = {self.y}")
                return f(self, *args, **kwargs)
            return wrapper
    
        @_method_before
        def sum_numbers(self):
            print(self.x + self.y)
    
        @_method_before
        def mult_numbers(self, z):
            print(self.x * self.y * z)
    

    Post Definition Decorator:

    class Example():
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        @staticmethod
        def _method_before(f):
            def wrapper(self, *args, **kwargs):
                print(f"x = {self.x}, y = {self.y}")
                return f(self, *args, **kwargs)
            return wrapper
    
        def sum_numbers(self):
            print(self.x + self.y)
    
        def mult_numbers(self, z):
            print(self.x * self.y * z)
    
    method_list = [f for f in dir(Example) if callable(getattr(Example, f)) and not f.startswith("_")]
    for m in method_list:
        setattr(Example, m, getattr(Example, "_method_before")(getattr(Example, m)))
    

    MetaClass:

    class Example_Meta(type):
        def __init__(self, name, bases, dict):
            method_list = [f for f in dir(self) if callable(getattr(self, f)) and not f.startswith("_")]
            for m in method_list:
                setattr(self, m, getattr(self, "_method_before")(getattr(self, m)))
    
    class Example(metaclass=Example_Meta):
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
        @staticmethod
        def _method_before(f):
            def wrapper(self, *args, **kwargs):
                print(f"x = {self.x}, y = {self.y}")
                return f(self, *args, **kwargs)
            return wrapper
    
        def sum_numbers(self):
            print(self.x + self.y)
    
        def mult_numbers(self, z):
            print(self.x * self.y * z)
    

    All versions should allow you to do:

    foo = Example(1, 2)
    foo.sum_numbers()
    foo.mult_numbers(10)
    
    x = 1, y = 2
    3
    x = 1, y = 2
    20