Registering methods with decorator at class level

Viewed 349

I'd like to be able to register/return methods at a class level. The closest answer I could find was here: Auto-register class methods using decorator, except it was centered on a global register and I'm looking for something specific to the class per below.

Code:

class ExampleClass:

    def get_reports(self):
        # return list of all method names with @report decorator 
        pass

    def report(self):
        # decorator here
        pass

    @report
    def report_x(self):
        return

    @report
    def report_y(self):
        return

    def method_z(self):
        pass

where I'd like ExampleClass.get_reports() to return ['report_x', 'report_y'].

Not all reports will be preceded with report_, so there is likely no way to just look at method names. I'm trying to figure out how to do this to apply to a variety of situations, so just looking for 'report_' does not work in this context.

2 Answers

You can declare a Reporter class like this and use an instance as a class property. I used the __call__ override to shorten the decorator, but you can name the function report and decorate methods as @report.report

class Reporter:
    def __init__(self):
         # Maintain a set of reported methods
        self._reported = set()

    def __call__(self, fn, *args, **kwargs):
        # Register method
        self._reported.add(fn.__name__)
        def decorate(*args, **kwargs):
            return fn(*args, **kwargs)
        return decorate    

class ExampleClass:
    report = Reporter()

    def get_reports(self):
         # return list of all method names with @report decorator 
         return list(self.report._reported)

    @report
    def report_x(self):
        return

    @report
    def report_y(self):
        return

    def method_z(self):
        pass

This turns out to be similar to Mach_Zero's answer. So key differences, this returns the methods, not the method names, and the implementation of get_reports() is somewhat simpler with the use of __iter__.

Code:

class Reports:

    def __init__(self):
        self.reports = []

    def __call__(self, func):
        self.reports.append(func)
        return func

    def __iter__(self):
        return iter(self.reports)


class ExampleClass:

    report = Reports()

    @classmethod
    def get_reports(cls):
        # return list of all method names with @report decorator
        return list(cls.report)

    @report
    def report_x(self):
        return

    @report
    def report_y(self):
        return

    def method_z(self):
        pass

Test Code:

print(ExampleClass.get_reports())

Results:

[
    <function ExampleClass.report_x at 0x000000000AF7B2F0>, 
    <function ExampleClass.report_y at 0x000000000AF7B378>
]
Related