Decorator help

Steven D'Aprano steve+comp.lang.python at pearwood.info
Wed Mar 27 19:29:56 EDT 2013


On Wed, 27 Mar 2013 19:49:54 +0000, Joseph L. Casale wrote:

> I have a class which sets up some class vars, then several methods that
> are passed in data and do work referencing the class vars.


When you say "class vars", do you mean variables which hold classes? Like 
"string vars" are variables which hold strings, and "int vars" are 
variables that hold ints? Classes (also known as types) are first-class 
objects in Python (no pun intended), unlike Java, and so you can store 
them in lists, returns them from functions, and bind them in variables.

for aclass in list_of_classes:
    do_something_with(aclass)


Consequently, when people talk about "class variables", it is ambiguous, 
which is why we prefer to use "class attribute" to refer to attributes on 
the class. For example:

class Parrot(object):
    breed = "Norwegian Blue"  # this is a class attribute

    def __init__(self, name="Polly"):
        self.name = name  # this is an instance attribute

    def speak(self):
        s = "%s the %s says, 'Hello sailor!'"
        print(s % (self.name, self.breed))
        

Class attributes are attached to the class object itself, and are shared 
between all instances. Instance attributes are attached to the instance, 
where they over-ride any class attribute of the same name, and are not 
shared.

In the following I am going to assume that you actually are talking about 
attributes, rather than an actual variable holding a class.


> I want to decorate these methods, the decorator needs access to the
> class vars, so I thought about making the decorator its own class and
> allowing it to accept args.

The one doesn't follow from the other. Writing decorators as classes is 
fairly unusual. Normally, they will be regular functions. If your 
decorator needs to store so much state that it needs to be a class, 
you're probably trying to do too much from a single decorator.

There's more that you need to describe, such as what it is that the 
decorator actually does, and whether it does it once, when the decorator 
is called, or repeatedly, when the decorated method is called.

The second case is the easiest. Suppose you have a class like this, with 
many methods which have code in common. Here's a toy example:


def MyClass(object):
    x = "class attribute"

    def __init__(self, y):
        self.y = y

    def spam(self):
        do_stuff(self.x)
        do_stuff(self.y)
        print("spam spam spam spam")

    def ham(self):
        do_stuff(self.x)
        do_stuff(self.y)
        return "ham, a growing boy's best friend"

    def eggs(self, food="bacon"):
        do_stuff(self.x)
        do_stuff(self.y)
        return "green eggs and %s" % food



A simple decorator function can simplify the common code:


import functools

def decorate(func):
    @functools.wraps(func)
    def inner(self, *args, **kwargs):
         # call the common, repeated, code
         do_stuff(self.x)
         do_stuff(self.y)
         # call the function being wrapped
         return func(self, *args, **kwargs)
    return inner


def MyClass(object):
    x = "class attribute"

    def __init__(self, y):
        self.y = y

    @decorate
    def spam(self):
        print("spam spam spam spam")

    @decorate
    def ham(self):
        return "ham, a growing boy's best friend"

    @decorate
    def eggs(self, food="bacon"):
        return "green eggs and %s" % food



Notice that because the decorator doesn't do any work until the decorated 
function is called, there is no difficulty in accessing attributes 
regardless of whether they are attached to the class or the instance. 
They won't be looked up until self is known.


A more complicated case is where you need to do some pre-processing, and 
you *don't* want that calculation repeated every time the method is 
called. Decorators are fantastic for that case too, but here you cannot 
access instance attributes, since the instance doesn't exist yet. But you 
can access *class attributes*, as more-or-less ordinary local variables 
*inside* the class definition. Here's a working sketch of the sort of 
thing you can do. Copy and paste the following into a Python interactive 
session, and then see if you can follow what is being done when.


# === cut ===
import functools

def decorator_factory(a, b):
    # This is a factory function that returns a decorator.
    # First we do so pre-processing. This gets called only once (per 
    # usage of the decorator).
    value = a*10 + b - 1
    print("precalculation of value = %s" % value)
    def decorator(func):
        print("decorator called on method '%s'" % func.__name__)
        @functools.wraps(func)
        def inner(self, fe, fi, fo):
            return func(self, fe, fi, fo, fum=value)
        return inner
    # return the decorator
    return decorator


class MyClass(object):
    spam = 42
    ham = 23
    @decorator_factory(spam, ham)
    def my_method(self, fe, fi, fo, fum):
        print(fe, fi, fo, fum)


x = MyClass()
x.my_method(1, 2, 3)

# === end cut ==


Is your mind boggled yet? :-)

Of course, decorators don't *have* to be functions, they can be any 
callable object, such as an instance with a __call__ method. But beware 
of making your code too clever. Decorators are powerful, but you can over 
do it, and make your code unreadable.



> I was hoping to do all the work on in_data from within the decorator,
> which requires access to several MyClass vars. Not clear on the
> syntax/usage with this approach here, any guidance would be greatly
> appreciated!

It's not clear what you actually need to do, so I can't give you any more 
guidance apart from the sort of thing that is possible with decorators.




-- 
Steven



More information about the Python-list mailing list