Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apply a default decorator to all methods on a class, and exclude them selectively

I have a class with a large number of methods, the majority of which require that a single method is run first to populate a list in the class. However I'd also like to use lazy loading so that I can create instances without the initial load, until a method is called which requires the heavy loading. Therefore, I'd like to create a class where all methods are assumed to require the running of a given method, unless the method is selectively excluded. Here's my pseudo-code:

@all_methods_run_load
class myClass
    things = []
    params = []

    @dont_run_load
    def __init__(self, params):
      self.params = params

    @dont_run_load
    def load(self):
      self.things = do_a_ton_stuff()

    def get_things():
      return self.things

    def get_some_things():
      return apply_a_filter(self.things)

    def get_first_thing():
      return self.things[0]

    def get_last_thing():
      return self.things[-1]

Hopefully that makes sense. I'm pretty new to decorators themselves and still memorizing them, so I'm afraid the answer might blow my mind, however the thought crossed my mind and I can't help but investigate further :)

like image 591
DanH Avatar asked Dec 15 '25 12:12

DanH


1 Answers

Although Aidan Kane is probably correct that you shouldn't be doing things this way, a solution to your stated problem could be phrased like so:

Here's the function that can decorate the methods.

import functools

def run_load(function):
    """Make a function run the class' load method before running."""
    @functools.wraps(function)
    def inner(self, *args, **kwargs):
        self.load()
        return function(self, *args, **kwargs)
    return inner

Functions are mutable so you can just tag them.

def without_load(function):
    """Tag a method so that it shouldn't be decorated to call self.load."""
    function.without_load = True
    return function

You can decorate the class by just going through its members and setattr-ing them (found here).

import inspect

def decorate_all_with(decorator, predicate=None):
    """Apply a decorator to all methods that satisfy a predicate, if given."""

    if predicate is None:
        predicate = lambda _: True

    def decorate_all(cls):
        for name, method in inspect.getmembers(cls, inspect.isfunction):
            if predicate(method):
                setattr(cls, name, decorator(method))

        return cls

    return decorate_all

And that's it!

def should_be_loaded(function):
    try:
        return not bool(function.without_load)
    except AttributeError:
        return True

@decorate_all_with(run_load, should_be_loaded)
class MyClass:
        things = []
        params = []

        @without_load
        def __init__(self, params):
            self.params = params

        @without_load
        def load(self):
            self.things = do_a_ton_stuff()

        def get_things(self):
            return self.things

        def get_some_things(self):
            return apply_a_filter(self.things)

        def get_first_thing(self):
            return self.things[0]

        def get_last_thing(self):
            return self.things[-1]
like image 171
Veedrac Avatar answered Dec 17 '25 01:12

Veedrac