Reputation: 15405
Can someone thoroughly explain the last line of the following code:
def myMethod(self):
# do something
myMethod = transformMethod(myMethod)
Why would you want to pass the definition for a method through another method? And how would that even work? Thanks in advance!
Upvotes: 5
Views: 311
Reputation: 151077
In your original question, you asked "Why would you want to pass the definition for a method through another method?" Then, in a comment, you asked "Why don't you just modify the method's actual source code?" I actually think that's a very good question, and a difficult one to answer without hand-waving, because decorators only become really useful when your code reaches a certain level of complexity. However, I think the point of decorators will become clearer if you consider the following two functions:
def add_x_to_sequence(x, seq):
result = []
for i in seq:
result.append(i + x)
return result
def subtract_x_from_sequence(x, seq):
result = []
for i in seq:
result.append(i - x)
return result
Now, these two example functions have some flaws -- in real life, for example, you'd probably just rewrite them as list comprehensions -- but let's ignore the obvious flaws for the moment, and pretend that we must write them this way, as for
loops iterating over sequences. We now face the problem that our two functions do almost the same thing, differing only at one key moment. That means we are repeating ourselves here! And that's a problem. Now we have to maintain more lines of code, leaving more room for bugs to appear, and more room for bugs to hide once they've appeared.
One classic approach to this problem might be to create a function that takes a function, and applies it across a sequence, like this:
def my_map(func, x, seq):
result = []
for i in seq:
result.append(func(i, x))
return result
Now all we have to do is define specific funcs to pass to my_map
(which is really just a specialized version of the built-in map
function).
def sub(a, b):
return a - b
def add(a, b):
return a + b
And we can use them like this:
added = my_map(sub, x, seq)
But this approach has its problems. It's a bit harder to read than our original stand-alone functions, for example; and every time we want to add or subtract x
from a list of items, we have to specify the function and value as arguments. If we're doing this a lot, we'd rather have a single function name that always refers to the same action -- that would improve readability, and make it easier to understand what's happening in our code. We could wrap the above in another function...
def add_x_to_sequence(x, seq):
return my_map(add, x, seq)
But now we're repeating ourselves again! And we're also creating a proliferation of functions, cluttering our namespace.
Decorators provide a way out of these problems. Instead of passing a function to another function every time, we can pass it once. First we define a wrapper function:
def vectorize(func):
def wrapper(x, seq):
result = []
for i in seq:
result.append(func(i, x))
return result
return wrapper
Now all we have to do is define a function and pass it to the above, wrapping it:
def add_x_to_sequence(a, b):
return a + b
add_x_to_sequence = vectorize(add_x_to_sequence)
Or, using decorator syntax:
@vectorize
def add_x_to_sequence(a, b):
return a + b
Now we can write many different vectorize
d functions, and our for
logic for all of them happens in just one place. Now we don't have to fix or optimize many different functions separately; all our loop-related bugs and loop-related optimizations happen in the same place; and we still get all the readability benefits of specially-defined functions.
Upvotes: 0
Reputation: 91109
What you describe is a decorator, a form of method/function modification which can be accomplished much easier with the special syntax for decorators.
What you describe is equivalent to
@transformMethod
def myMethod(self):
# do something
Decorators are used very broadly, for example in the form of @staticmethod
, @classmethod
, @functools.wraps()
, @contextlib.contextmanager
etc. etc. etc.
Since a certain Python version (I think it was 2.6), classes can be decorated as well.
Both kinds of decoratiors happily allow to return objects which are not even functions or classes. For example, you can decorate a generator function in a way which turns it into a dict, a set or whatever.
apply = lambda tobeapplied: lambda f: tobeapplied(f())
@apply(dict)
def mydict():
yield 'key1', 'value1'
yield 'key2', 'value2'
print mydict
@apply(set)
def myset():
yield 1
yield 2
yield 1
yield 4
yield 2
yield 7
print myset
What do I do here?
I create a function which takes a "thing to be applied" and in turn returns another function.
This "inner" function takes the function to be decorated, calls it and puts its result in the outer function and returns this result.
f()
returns a generator object which is then put into dict()
or set()
.
Upvotes: 1
Reputation: 208545
This is an example of function wrapping, which is when you have a function that accepts a function as an argument, and returns a new function that modifies the behavior of the original function.
Here is an example of how this might be used, this is a simple wrapper which just prints 'Enter' and 'Exit' on each call:
def wrapper(func):
def wrapped():
print 'Enter'
result = func()
print 'Exit'
return result
return wrapped
And here is an example of how you could use this:
>>> def say_hello():
... print 'Hello'
...
>>> say_hello() # behavior before wrapping
Hello
>>> say_hello = wrapper(say_hello)
>>> say_hello() # behavior after wrapping
Enter
Hello
Exit
For convenience, Python provides the decorator syntax which is just a shorthand version of function wrapping that does the same thing at function definition time, here is how this can be used:
>>> @wrapper
... def say_hello():
... print 'Hello'
...
>>> say_hello()
Enter
Hello
Exit
Upvotes: 2
Reputation: 76725
You need to understand that in Python, everything is an object. A function is an object. You can do the same things with a function object that you can do with other kinds of objects: store in a list, store in a dictionary, return from a function call, etc.
The usual reason for code like you showed is to "wrap" the other function object. For example, here is a wrapper that prints the value returned by a function.
def print_wrapper(fn):
def new_wrapped_fn(*args):
x = fn(*args)
print("return value of %s is: %s" % (fn.__name__, repr(x)))
return x
return new_wrapped_fn
def test(a, b):
return a * b
test = print_wrapper(test)
test(2, 3) # prints: return value of test is: 6
This is such a useful task, and such a common task, that Python has special support for it. Google search for "Python decorators".
Upvotes: 0
Reputation: 799082
Why would you want to pass the definition for a method through another method?
Because you want to modify its behavior.
And how would that even work?
Perfectly, since functions are first-class in Python.
def decorator(f):
def wrapper(*args, **kwargs):
print 'Before!'
res = f(*args, **kwargs)
print 'After!'
return res
return wrapper
def somemethod():
print 'During...'
somemethod = decorator(somemethod)
somemethod()
Upvotes: 2