[Edu-sig] More on decorators

David MacQuigg macquigg at ece.arizona.edu
Tue Apr 1 05:21:59 CEST 2008


"Getting this right can be slightly brain-bending ..."  quote from discussion on Decorators in What's New in Python 2.4. :(

Kirby, I love your examples below!!  Helped me understand the generality of the @ syntax (which I gave up on before the developers had finished their discussion), and the usefulness of classmethod (which I had assumed was a solution in search of a problem).

I do use staticmethod, and I've got some good examples at http://ece.arizona.edu/~edatools/Python/Animals.py, but never saw a real need for classmethod.  Your example at least makes the utility plausible, although I could easily do the same without a classmethod.  But I think I see what you are trying to do.  Maybe you could expand the example a bit to make it more compelling.  In the current example, the variable klass has only one value, Register.  You could have multiple classes (Retail, Wholesale, etc.) to drive home that klass really is an important variable, and can't just be hard-coded as Register.

The motivation for staticmethod is easy in the context of my OO zoo.  The show() methods print out the properties of each of the animals in the zoo.  The problem is, you can't call a show() method for a particular class of animal until the zoo has at least one instance of that animal.  The problem can be solved by making the show() methods simple functions, outside of any class, but that is ugly.  The show() methods belong inside their classes, and to do that, they must be static methods.

Good examples are crucial.  So much syntax, even in Python, is unnecessary clutter.  Thank you for showing me classmethod was not just clutter.

-- Dave

At 10:38 AM 3/30/2008 -0700, kirby urner wrote:

>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
>_______________________________________________
>Edu-sig mailing list
>Edu-sig at python.org
>http://mail.python.org/mailman/listinfo/edu-sig





More information about the Edu-sig mailing list