1

I have a class in my Python module that frobs widgets. It has several ways to do this, but I don't know ahead of time which ones, if any, will actually work, and I might figure out new ways to frob the widgets later. Also, frobbing widgets can be expensive. So I'd like to have an instance of my class be able to look at itself, find all of the methods that it has for frobbing widgets, and to start trying to frob widgets until it succeeds, at which point it should stop caring about the methods it hasn't yet tried.

class WidgetFrobber:
    def simpleFrobAttempt(widget, data):
        # fastest way, might not work
    def cleverFrobAttempt(widget, data):
        # fiddly, fast, and doesn't always work
    def boringFrobAttempt(widget, data):
        # works most of the time, often slow
    def desperateFrobAttempt(widget, data):
        # doesn't always work, pathetically slow

With that in place, I'd like to define a method of the class that looks for methods named ^[a-z]+FrobAttempt$, makes a list of them, and tries to frob widgets until the widget is successfully frobbed (at which point it should stop caring about other methods) or it runs out of possible methods. Since it's my code, I can make sure that the whateverFrobAttempt methods all have the same naming conventions and required arguments. It's best if there's some ordering to the list of methods, so that the ones with a better average speed get tried first, but it's acceptable if they're attempted in random-ish order.

Is there a sane way to do this such that when new weirdFrobAttempt methods are added, they'll be tried automatically, or am I better off just maintaining a hardcoded list of such methods and iterating over that ?

Brighid McDonnell
  • 4,293
  • 4
  • 36
  • 61
  • 1
    How about an explicit registering mechanism? A widget could call `register_frobbing_method()` to register a new frobbing method. This would avoid any blac magic and make the process explicit. – Sven Marnach Jan 07 '12 at 20:11
  • You should totally expand on that thought and make it an answer. :) – Brighid McDonnell Jan 07 '12 at 20:17
  • You can use `dir(WidgetFrobber)` to get list of class attributes, or call `dir` on instance, to get instance attributes. But Sven Marnach advice is better. – reclosedev Jan 07 '12 at 20:22
  • What's the point of the `WidgetFrobber` class? Why can't the widgets frob themselves? – ekhumoro Jan 08 '12 at 01:46
  • Ekhumoro: The widgets are frobbing themselves. It's just a question of how much effort it'll take - frobbing a widget is expensive, so I don't want to keep trying after it's already succeeded. – Brighid McDonnell Jan 08 '12 at 05:10

1 Answers1

2

I like sven's idea of having a register. This is hacky, but if storing a global list of method names is ok then we could do something like this:

FROB_METHOD_REGISTER = []
def frob_method(fn):
    def decorate_frob(fn):
        FROB_METHOD_REGISTER.add(fn.__name__)
        return fn
    return decorate_frob

class WidgetFrobber:
    def get_frob_methods(self):
        return [getattr(self, method) for method in FROB_METHOD_REGISTER]

    @frob_method
    def simpleFrobAttempt(widget, data):
        # fastest way, might not work
    @frob_method
    def cleverFrobAttempt(widget, data):
        # fiddly, fast, and doesn't always work
    @frob_method
    def boringFrobAttempt(widget, data):
        # works most of the time, often slow
    @frob_method
    def desperateFrobAttempt(widget, data):
        # doesn't always work, pathetically slow

so then you can do

for method in my_widget_frobber.get_frob_methods():
    #do your thing

Not sure if this is the best or most pythonic way, and I'd be tempted to make the decorator use some sort of priority queue/ranking schema if there are some methods that are strictly better than others.

You could use __dict__. for example

[fn for name, fn in WidgetFrobber.__dict__.iteritems() if "FrobAttempt" in name]

would give you the set of methods you are looking for, but I would not recommend using things like __dict__ if there is a way to build code that is more clear and doesn't access python internals. And there almost always should be.

Brighid McDonnell
  • 4,293
  • 4
  • 36
  • 61
stein
  • 180
  • 7
  • Oh hey, that decorator idea is something I hadn't thought of! I was thinking something that required more effort, but I like this idea. Just adding `@_frob_method` to the new `whateverFrobAttempt` methods is a totally reasonable amount of effort. – Brighid McDonnell Jan 08 '12 at 05:16
  • Actually that's not working, and I begin to suspect that it can't work, based on http://stackoverflow.com/questions/3885459 and the bit about decorators being applied during class construction, not during instance construction. :( – Brighid McDonnell Jan 08 '12 at 21:26
  • oof, yeah. http://stackoverflow.com/questions/1263451 too, sorry should have caught this. I still don't like the idea of accessing internals. modified to something that works, but is pretty hacky. – stein Jan 09 '12 at 07:40
  • Yeah, it seems quite possible that "change the architecture" is the right answer. That's okay. I asked the question because I started down the path of using `__dict__` and said "wait a minute, this seems wrong." – Brighid McDonnell Jan 09 '12 at 16:12
  • I changed the architecture, but this answer (after a couple of edits to produce a working decorator - it needs that nested function!) started me down the right path, so I'm going to accept it. – Brighid McDonnell Jan 10 '12 at 04:08
  • 1
    i'd be curious to see your solution, occasionally we hit a very similar class of problems and use the decorator trick at a module level/ – stein Jan 10 '12 at 05:17
  • I didn't actually stray too far from this - I ended up making the registry of methods a module-level variable, and decorating widget-frobbing *classes* instead of widget-frobbing *methods*. The classes in turn subclass a Frobber class that hands them common functionality. – Brighid McDonnell Jan 10 '12 at 14:36