How to handle 'self' argument with Python decorators
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) # comma开发者_如何学Gonds = [ '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
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.
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
精彩评论