Decorator help

J

Joseph L. Casale

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


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.


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 approachhere, any guidance
would be greatly appreciated!


Class MyDecorator(object):

    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2
    ...


Class MyClass(object):
    def __init__(self):
        self.var_a = ....
    ....
    @MyDecorator(...)
    def meth_one(self, in_data):
        ...


Thanks!
jlc
 
S

Steven D'Aprano

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.
 
J

Jason Swails

On Wed, Mar 27, 2013 at 7:29 PM, Steven D'Aprano <
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

In the spirit of nit-picking, I'll point out that Steven meant to use the
'class' keyword instead of 'def' for MyClass.

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

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

And here as well.

It's potentially worth pointing out that this code will actually compile.
It will even run, assuming you provide MyClass with a single argument.
But it will always return None :).

As per usual, the response was thorough and helpful -- I appreciate
responses like these and how they've helped improve my command of Python.

All the best,
Jason
 
S

Steven D'Aprano

In the spirit of nit-picking, I'll point out that Steven meant to use
the 'class' keyword instead of 'def' for MyClass.


/face-palm


So I did. Thanks for picking the nit.
 
J

Joseph L. Casale

When you say "class vars", do you mean variables which hold classes?


You guessed correctly, and thanks for pointing out the ambiguity in my references.

The one doesn't follow from the other. Writing decorators as classes is 
 fairly unusual. Normally, they will be regular functions.


I see, this I didn't know. I'll stick to this guideline now.

 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.
 Is your mind boggled yet? :)


Steven,
That was some of the coolest stuff I have seen a while. I had to wait untilI had
enough time to actually run this through and utilize it my own work. I haven't
enjoyed Python this much since I first started using it.


Can't thank you enough for the time and thorough example, that imparted loads
of insight.


jlc
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,013
Latest member
KatriceSwa

Latest Threads

Top