3

I'm working on a module that I'm hoping to be somewhat dynamic, in that anyone can add features relatively easily.

The basic idea is to have a class, CriticBase, which handles all criticisms for this deployment. The critics would be any class that has inherited from CriticBase.

Pseudo Example:

class CriticBase(Object) { 
    def self.Execute(): 
        for critic in self.__subclasses__: critic.run()
}

class DatabaseCritic(CriticBase) { def run( //things ) }
class DiskSpaceCritic(CriticBase) { def run( //things ) }
etc...

def DoWork():
    Controller = CriticBase()
    a = DatabaseCritic()
    b = DiskSpaceCritic()
    ...
    Controller.Execute()

I hope that kind of makes sense. Basically the idea is to have a framework that's fairly straightforward for other devs to add to. All you need to do is define some subclass of CriticBase, and everything else is handled for you by the critic framework.

However, it's pretty ugly to me to just assign these classes to something that's never going to be used. Is there such a thing as lingering objects in Python? Could I do away with the assignment, and still have the reference to the instantiated class from the base class? Or do I have to have it assigned to something, otherwise it will be garbage collected?

MrDuk
  • 16,578
  • 18
  • 74
  • 133
  • I'm not sure, but maybe it could help http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python – FunkySayu Jul 17 '15 at 13:54
  • 3
    Maybe you could redesign it better to use a Publish and Subscribe pattern. Have you considered it? – Pablo Jul 17 '15 at 13:54
  • Interesting suggestion - I'm not sure it fits for this particular tool, but I'll definitely consider how it could be designed to work with this. The issue this raises (not a huge one) is that it would now require new features to be coded to `push` rather that it being somewhat automagic. – MrDuk Jul 17 '15 at 13:58

2 Answers2

3

My understanding is that you don't want other devs to instantiate the sub-classes. Actually, they don't need to do that, as long as the method run() is a class method:

# The framework provides this base class
class CriticBase(object):
    @classmethod
    def execute(cls): 
        for critic_class in cls.__subclasses__(): 
            critic_class.run()

# Devs only need to provide a definition of subclass
class DatabaseCritic(CriticBase):
    @classmethod
    def run(cls):
       # do something specific about database

class DiskSpaceCritic(CriticBase):
    @classmethod
    def run(cls):
       # do something specific about disk space

# now the base class do all the work
def DoWork():
    CriticBase.execute()

With this approach, you use python's inheritance machinery to collect the subclasses into a list, and your code is free from useless instantiations.

NeoWang
  • 17,361
  • 24
  • 78
  • 126
  • 1
    When you decorate a method as @classmethod, the class object itself is passed to the method as the first argument. https://docs.python.org/2/library/functions.html#classmethod – NeoWang Jul 17 '15 at 15:32
  • 1
    And how do you use instance variables or method variables in this approach? Would not be better to simply define decorated functions in this way? – Pablo Jul 17 '15 at 15:46
  • 1
    If I understand it right, this case doesn't require multiple instances for the subclasses, just some work to be done for each subclass. Function decorator is another way to go, but within this decorator, you still need to collect the functions somewhere (a global list, maybe). Might as well let python do this work. – NeoWang Jul 17 '15 at 15:55
2

Well, you could achieve this using a Publish and Subscribe pattern. Roughly, it could be:

class CriticServer:

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

    def insert_client(self, client):
        self.clients.append(client)

    def execute(self):
        for client in self.clients:
            client.run()

class CriticClient:
    # move what you would inherit from server to here
    def __init__(self, server):
        server.insert_client(self)

class DatabaseCriticClient(CriticClient):
    def run(self):
        pass

class DiskSpaceCriticClient(CriticClient):
    def run(self):
        pass

def main():
    server = CriticServer()
    DiskSpaceCriticClient(server)
    DatabaseCriticClient(server) 
    server.execute()  

Since I don't know too much details about your project, I'm tempt to say that would be a better idea to create a base class for clients instead of sub-classing the server.

Maybe it has not too much magic, but it works nice and it is easy to understand and to extend (which is sometimes better than pure magic).

Pablo
  • 1,311
  • 9
  • 20