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