views:

199

answers:

2

I am trying to setup some decorators so that I can do something like:

class Ball(object):
    def __init__(self, owner):
        self.owner = owner

class Example(CommandSource):
    @command
    @when(lambda self, ball: ball.owner == self) 
    def throwBall(self, ball):
        # code to throw the ball
        pass

e = Example()
ball = Ball(e)
commands = e.listCommands(ball)  # commands = [ 'throwBall' ]

This currently doesn't work, as when the validation lambda is called, there is no self argument passed.

Now something like this works fine:

class Example(CommandSource):
    @command
    @when(lambda ball: ball.is_round) 
    def throwBall(self, ball):
        pass

But this also doesn't work:

class Example(CommandSource):
    def verify_owner(self, ball):
        return ball.owner == self

    @command
    @when(verify_owner) 
    def throwBall(self, ball):
        pass

The intent is to be able to mark methods in a class as a 'command', and the get a list of commands that are valid to run. Actually running the method is unchanged. I would like to use decorators here as it seems the least klunky way to do this. In effect I am trying to setup a little DSL using Python.

But I'm having difficulty, especially with the self argument. Here is my current implementation of command, when and CommandSource:

def command(cmd):
    if getattr(cmd, 'command', False): return cmd

    def wrapper(*args, **kwargs):
        return cmd(*args, **kwargs)

    wrapper.validators = []
    wrapper.command = True

    return wrapper

def when(predicate):
    def createCommand(cmd):    
        newcmd = command(cmd)
        newcmd.validators.append(predicate)
        return newcmd
    return createCommand

class CommandSource(object):
    def listCommands(self, *args, **kwargs):
        commands = []
        for command in dir(self.__class__):
            func = getattr(self, command, None)

            if func == None or getattr(func, 'command', False) == False:
                continue
            valid = True
            for validator in func.validators:
                if not validator(*args, **kwargs):
                    valid = False
                    break
            if valid:
                commands.append(command)
        return commands
+2  A: 

Change your CommandSource as follow:

class CommandSource(object):

    def listCommands(self, *args, **kwargs):
        commands = []
        for command in dir(self.__class__):
            func = getattr(self, command, None)
            if func == None or getattr(func, 'command', False) == False:
                continue
            for validator in func.validators:
                if not validator(self, *args, **kwargs):
                    break
            else:
                commands.append(command)
        return commands

and use your very first code.

mg
Great, that works, also thanks for using the `else` for the inner `for` loop.
Joseph Kingry
+1  A: 

You need to pass an instance reference to your validator function:

for validator in func.validators:
    if not validator(self, *args, **kwargs):
        valid = False
        break
Ruslan Spivak