[Edu-sig] More on decorators
kirby urner
kirby.urner at gmail.com
Sun Mar 30 19:38:34 CEST 2008
Here's a little module about the power of decorators.
We show decorators used in two capacities:
1. to invoke the built-in classmethod wrapper, giving us
a way to invoke methods on a class with no "self objects"
2. to wrap a user-defined cost with a user-defined sales_tax
with a flexible percentage (defaults to 10%).
==== taxes.py ====
from random import randint
def sales_tax(sale, percent = 0.1):
def vat(x):
before = sale(x)
after = (1 + percent) * before
print("Before: %s; After: %s" % (before, after))
return after
return vat
class Register:
shipping = 1.70 # fixed cost
@sales_tax
def __cost(price):
return price + Register.shipping
@classmethod
def transact(klass, price):
return klass.__cost(price)
def test():
print(Register)
for i in range(10):
Register.transact(randint(1,100))
However, I'd consider the above too complicated, so refactor
below:
==== taxes.py ====
from random import randint
def sales_tax(sale, percent = 0.1):
def vat(x):
before = sale(x)
after = (1 + percent) * before
print("Before: %s; After: %s" % (before, after))
return after
return vat
# fixed cost
shipping = 1.70
@sales_tax
def cost(price):
return price + shipping
class Register:
@classmethod
def transact(klass, price):
return cost(price)
def test():
print(Register)
for i in range(10):
Register.transact(randint(1,100))
What's cooler is we're also demonstrating how decorator
syntax makes as much sense outside a class definition,
plus we're playing a different scope game, with Register
reaching outside its class for a more global cost function
(it really didn't have been inside the class def in the first
example), which in turn relies on the global shipping
variable.
In the lesson plan I'm dreaming up, it'd be useful to
show both examples, as we're showing off the freedoms
you have when it comes to mixing functions and classes
top level, in addition to decorator syntax.
Regarding decorators, I think I good way to teach them
*is* to recapitulate their motivation.
Having to define the function first, before wrapping it
in clunky f = g(f) syntax at the end, was making for
less readable code.
@ is Python's function composer, with the second
argument a function def and source of the name, the
first argument functional wrapper for the second
i.e.
@f
def g(args)
...
is the same as g = f(g(args))
Earlier in the buildup to such as the above, we'll look
at simpler more algebraic examples of function
composition .
>>> def f(h):
def k(x):
return h(x) + 2
return k
>>> @ f
def g(x): return x**2
>>> print(g(10))
102
>>> print(g(12))
146
Then go for fancier user-defined examples, including
with argument passing.
>>> def f(a):
if a == 1:
def n(targ):
def z(x):
return targ(x) + 2
return z
return n
if a == 2:
def n(targ):
def z(x):
return targ(x) + 3
return z
return n
>>> f(1)(g)
<function z at 0x83c492c>
>>> f(1)
<function n at 0x83c48ec>
>>> @f(1)
def g(x): return x * x
>>> g(10)
102
>>> @f(2)
def g(x): return x * x
>>> g(10)
103
Kirby
More information about the Edu-sig
mailing list