Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #42036 > unrolled thread

Decorator help

Started by"Joseph L. Casale" <jcasale@activenetwerx.com>
First post2013-03-27 19:49 +0000
Last post2013-03-31 00:06 +0000
Articles 5 — 3 participants

Back to article view | Back to comp.lang.python


Contents

  Decorator help "Joseph L. Casale" <jcasale@activenetwerx.com> - 2013-03-27 19:49 +0000
    Re: Decorator help Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-03-27 23:29 +0000
      Re: Decorator help Jason Swails <jason.swails@gmail.com> - 2013-03-27 22:38 -0400
        Re: Decorator help Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2013-03-28 04:58 +0000
      RE: Decorator help "Joseph L. Casale" <jcasale@activenetwerx.com> - 2013-03-31 00:06 +0000

#42036 — Decorator help

From"Joseph L. Casale" <jcasale@activenetwerx.com>
Date2013-03-27 19:49 +0000
SubjectDecorator help
Message-ID<mailman.3824.1364413867.2939.python-list@python.org>
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.


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 approach here, 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

[toc] | [next] | [standalone]


#42048

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-03-27 23:29 +0000
Message-ID<515380f4$0$29998$c3e8da3$5496439d@news.astraweb.com>
In reply to#42036
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

[toc] | [prev] | [next] | [standalone]


#42068

FromJason Swails <jason.swails@gmail.com>
Date2013-03-27 22:38 -0400
Message-ID<mailman.3841.1364438295.2939.python-list@python.org>
In reply to#42048

[Multipart message — attachments visible in raw view] — view raw

On Wed, Mar 27, 2013 at 7:29 PM, Steven D'Aprano <
steve+comp.lang.python@pearwood.info> wrote:

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

[toc] | [prev] | [next] | [standalone]


#42099

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2013-03-28 04:58 +0000
Message-ID<5153cdef$0$29984$c3e8da3$5496439d@news.astraweb.com>
In reply to#42068
On Wed, 27 Mar 2013 22:38:11 -0400, Jason Swails wrote:

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


/face-palm


So I did. Thanks for picking the nit.




-- 
Steven

[toc] | [prev] | [next] | [standalone]


#42354

From"Joseph L. Casale" <jcasale@activenetwerx.com>
Date2013-03-31 00:06 +0000
Message-ID<mailman.4008.1364688371.2939.python-list@python.org>
In reply to#42048
> 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 until I 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

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web