When I started using decorators I was confused how it works. I was using decorators like @staticmethod and @classmethod and I didn’t care how it is done. It works. But what about custom decorators? I knew that
@decor
def func():
pass
is the same as
def func():
pass
func = decor(func)
but the first one looks better 🙂 Lets look at an example:
def decor(f):
def substitute(*args):
return "result = " + str(f(*args))
return substitute
@decor
def func(x):
return x * x
The principle is that the decorator function returns another function which should or should not use the decorator’s parameter f (the original function). If you don’t need the original/decorated function then the substitute function should be in the global scope and not as a “subfunction” of the decorator. It also depends on the purpose of the substitute function. Example:
def substitute(*args):
return "no more f " + repr(args)
def decor(f):
return substitute
@decor
def func(x):
return x * x
Now back to the function in a function (FiF). I thought when a function returns then it’s local scope (parameters, local variables) ends and should be garbage collected. So how can the substitute work? It is easy. Nothing is garbage collected until all references are released. This is the same case. You have a local scope represented like some kind of a dictionary which includes the substitute function. Since the decorator works like
new_func = decor(func)
then you have a reference to the substitute in the variable new_func so the decorator’s parameter f lives as long as the variable new_func.
Function factories
Another point of view is that you can use FiF as a function factory. You can “group” common behavior and represent it in a form of “macros”. Example:
def power_factory(exponent):
def power_function(x):
return x ** exponent
return power_function
macro_power_2 = power_factory(2)
macro_power_10 = power_factory(10)
print macro_power_2(8) # will print 64 (2 ** 8 = 64 :-)
Previous example is similar to the concept of partial functions.
Addressing, identity
It is neccessary to realize one thing – since FiFs lives in a local scope then their addresses are always different. Example will explain:
def fixed():
def floating():
pass
return floating
a = fixed
b = fixed
print id(fixed), id(a), id(b)
# output: 8518512 8518512 8518512
f = fixed()
g = fixed()
print id(f), id(g)
# output: 8507440 8509000
You can see that the fixed‘s address is constant due to its “global nature” but the float‘s address is different. Float was manufactured in the factory. In other words the float doesn’t exist when the module is loaded but it “appears” in the fixed‘s runtime.
Variables scope, name binding, write access
There is no problem when you are only reading variables defined in the superior function:
def f():
i = 10
def g():
print i
g()
f() # output: 10
But trouble will rise when you will try to modify write to that variable:
def f():
i = 10
def g():
print i
i = 5 # write to previously read variable
g()
f()
will result in:
Traceback (most recent call last): File "x.py", line 10, in <module> f() File "x.py", line 8, in f g() File "x.py", line 5, in g print i UnboundLocalError: local variable 'i' referenced before assignment
Whoo, why the print?!? It’s a little bit complicated and you can find some explanation in docs. Anyway you can’t write to the superior scope. Following example looks like it is possible by a simple swap. It works but instead of modifying the f‘s it will create a local variable in the g‘s scope:
def f():
i = 10
def g():
i = 5
a = i
print i
g()
print i
f()
# output:
# 5
# 10
No Comments
No comments yet.
RSS feed for comments on this post.
Sorry, the comment form is closed at this time.