I'm trying to write a function decorator that tests for bounds of x, y
#this is my bound test function
def boundtest(func):
def onDecorator(self, x, y, *args, **kwargs):
print(x, y, *args, **kwargs)
assert x in range(self.width) and y in range(self.height)
return func(x, y, *args, **kwargs)
return onDecorator
class Game:
#these are the functions that need bound checking
@boundtest
def at(self, x: int, y: int) -> int:
return self.map[x, y]
@boundtest
def set(self, x: int, y: int, data):
self.map[x, y] = data.value
When I execute game.set(1, 1, Color.RED)
I get:
Traceback (most recent call last):
File "C:\Users\Ben\Desktop\Projects\bubble-breaker-bot\game.py", line 61, in <module>
game.set(1, 1, Color.RED)
File "C:\Users\Ben\Desktop\Projects\bubble-breaker-bot\game.py", line 21, in onDecorator
return func(x, y, *args, **kwargs)
TypeError: set() missing 1 required positional argument: 'data'
I need the boundtest function to check if x
and y
are in range of self.width
, and self.height
respectively while being able to pass an arbitrary amount of parameters to the function it is decorating.
Why does this happen?
Decorators are applied to function objects, not to bound methods. This means you need to pass on the self
argument manually:
def boundtest(func):
def onDecorator(self, x, y, *args, **kwargs):
print(x, y, *args, **kwargs)
assert x in range(self.width) and y in range(self.height)
return func(self, x, y, *args, **kwargs)
return onDecorator
Python uses a process called binding to turn a function into a bound method, and calling a bound method automatically passes in whatever it is bound to as the first argument; this is how self
is passed into a method when you call a fuction on an instance. See the Descriptor HowTo for details. Instead of manually passing on self
, you could invoke descriptor binding manually, by calling func.__get__()
to produce a bound method:
def boundtest(func):
def onDecorator(self, x, y, *args, **kwargs):
print(x, y, *args, **kwargs)
assert x in range(self.width) and y in range(self.height)
bound_method = func.__get__(self, type(self))
return bound_method(x, y, *args, **kwargs)
return onDecorator
That binding behaviour was applied to the onDecorator
function object your decorator returned when game.set
was being resolved, but not to the wrapped func
object.