I’m actually struggling with some piece of code. I do know that it can be refactored, but I can’t find the nice-smart-elegant solution.
Here are two functions (much more functions of that kind are in my code):
def fooA(param1, param2):
if param2 == True:
code_chunk_1
fooA_code #uses only param1
if param2 == True:
code_chunk_2
def fooB(param1, param2):
if param2 == True:
code_chunk_1
fooB_code #uses only param1
if param2 == True:
code_chunk_2
My first idea was to use this decorator:
def refactorMe(func):
def wrapper(*args):
if args[-1]:
code_chunk_1
func(*args)
if args[-1]:
code_chunk_2
return wrapper
And finally:
@refactorMe
def fooA(param1, param2):
fooA_code #uses only param1
@refactorMe
def fooB(param1, param2):
fooB_code #uses only param1
Unfortunately, I’m not happy with this solution:
- This decorator is “intrusive” and specific to the fooA & fooB functions
- param2 is not used anymore in the fooA & fooB body, but we must keep it in the function signature
Perhaps I’m not using the decorator for its initial purpose?
Is there any other way to refactor the code?
Thanks a lot!
What you are describing is a situation where you have some boilerplate, some behaviour, followed by some boiler plate. Essentially a situation where you could use a Higher Order Function (like map, reduce or filter).
You could do what Ned suggests (though, I’d use functools.partial rather than defining fooA/fooB longhand):
… but that effectively gets you back to the same place as with your decorator, introducing some clutter into the namespace along the way.
You could rewrite your decorator to allow functions that only take one parameter, but return functions that take two:
Getting rid of the star magic is an improvement as this decorator is not general to all functions so we should be explicit about it. I like the fact that we change the number of parameters less as anyone looking at the code could easily be confused by the fact that when we call the function we are adding an extra parameter. Furthermore it just feels like decorators that change the signature of the function they decorate should be bad form.
In summary:
Decorators are higher order functions, and templating behaviour is precisely what they’re for.
I would embrace the fact that this code is specific to your fooXXX functions, by making the decorator internal and having it take precisely the number of arguments needed (because foo(*args, **kwargs) signatures makes introspection a pain).
I’d leave the calls taking two parameters, even though one is unused just so that the decorator doesn’t change the signature. This isn’t strictly necessary as if you document the functions as they look after decoration and you are restricting the use of the decorator to this small set of functions then the fact that the signature changes shouldn’t be that big a deal.