[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