Home > Net >  What's the most concise way to "forward" method calls on a Python object?
What's the most concise way to "forward" method calls on a Python object?

Time:04-14

I'm writing some report generation code, where a class CoverageReporter is doing all of the heavy lifting, with just a few attributes changing between different instances of CoverageReporter. Let's say we want to alias

SubsystemCoverageReporter(*args, **kwargs)

to

CoverageReporter('subsystem', *args, **kwargs)

in a concise way. Currently, I accomplish it like so:

class SubsystemCoverageReporter(object):
    def __init__(self, *args, **kwargs):
        self.reporter = CoverageReporter('subsystem', *args, **kwargs)

    def __getattr__(self, name):
        return getattr(self.reporter, name)

Another one, subsystem 2, has almost exactly the same boilerplate, eg:

class SubsystemTwoCoverageReporter(object):
    def __init__(self, *args, **kwargs):
        self.reporter = CoverageReporter('subsystem2', *args, **kwargs)

    def __getattr__(self, name):
        return getattr(self.reporter, name)

Is there a more concise Pythonic way to express this? I'd prefer not to rely on inheritance, but if there's no better ways I'm open to that.

CodePudding user response:

Say you had your class CoverageReporter like so:

class CoverageReporter:
    def __init__(self, _type, _name):
        self.type = _type
        self.name = _name
        
    def do_something(self):
        print(f"{self.type}:{self.name} did something")
        
    def do_something_else(self):
        print(f"{self.type}:{self.name} did something else")     

You could define a function which creates the CoverageReporter and returns that object.

def SubsystemCoverageReporter(*args, **kwargs):
    return CoverageReporter('subsystem', *args, **kwargs)


def SubsystemTwoCoverageReporter(*args, **kwargs):
    return CoverageReporter('subsystem2', *args, **kwargs)

s1 = SubsystemCoverageReporter("Foo")
s2 = SubsystemTwoCoverageReporter("Bar")

s1.do_something()         # subsystem:Foo did something
s2.do_something_else()    # subsystem2:Bar did something else

Note that this isn't exactly equivalent to what you have, since SubsystemCoverageReporter isn't a class, so you won't be able to inherit it. If you want that option available, then off the top of my head I think inheritance is the best solution you have.

class SubsystemCoverageReporter(CoverageReporter):
    def __init__(self, *args, **kwargs):
        super().__init__('subsystem', *args, **kwargs)
        
class SubsystemTwoCoverageReporter(CoverageReporter):
    def __init__(self, *args, **kwargs):
        super().__init__('subsystem2', *args, **kwargs)

s1 = SubsystemCoverageReporter("Foo")
s2 = SubsystemTwoCoverageReporter("Bar")

s1.do_something()         # subsystem:Foo did something
s2.do_something_else()    # subsystem2:Bar did something else

CodePudding user response:

If you want to avoid repeating yourself, use the same solution you always would, wrap the repeated code in a function and parameterize the parts that can change:

def make_klass(subsystem):
    class SubsystemCoverageReporter(object):
        def __init__(self, *args, **kwargs):
            self.reporter = CoverageReporter(subsystem, *args, **kwargs)
    
        def __getattr__(self, name):
            return getattr(self.reporter, name)
    return SubsystemCoverageReporter


SubsystemCoverageReporter = make_klass("subsystem")
SubsystemTwoCoverageReporter = make_klass("subsystem2")

Note, the classes you create will have the same value for their __name__ attribute. That is hopefully not important, but you can get around it, e.g.:

SubsystemCoverageReporter.name = "SubsystemCoverageReporter" SubsystemTwoCoverageReporter.name = "SubsystemTwoCoverageReporter"

Although that is ugly.

You could use the type constructor directly, or if metaclasses are an issue, use types.new_class instead and it will handle it correctly. But in the simplest case,

def make_klass(name, subsystem):
    def __init__(self, *args, **kwargs):
        self.reporter = CoverageReporter(subsystem, *args, **kwargs)
    def __getattr__(self, name):
        return getattr(self.reporter, name)
    klass = type(name, (object,), dict(__init__=__init__, __getattr__=__getattr__))
    return klass

SubsystemCoverageReporter = make_klass("SubsystemCoverageReporter", "subsystem")
SubsystemTwoCoverageReporter = make_klass("SubsystemTwoCoverageReporter", "subsytem2")

Although note, you are repeating yourself here a little.

But again, normally you shouldn't care about the __name__ attribute

  • Related