Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Using 'apply' as a decorator, to define constants

28 views
Skip to first unread message

Jonathan Fine

unread,
Aug 21, 2009, 9:36:35 AM8/21/09
to
Hi

It might seem odd to use 'apply' as a decorator, but it can make sense.

I want to write:
# Top level in module.
tags = <complicated list>
where the list is most easily constructed using a function.

And so I write:
@apply
def tags():
value = []
# complicated code
return value

And now 'tags' has the result of running the complicated code.


Without using 'apply' as a decorator one alternative is
def tmp():
value = []
# complicated code
return value
tags = tmp()
del tmp


Like all uses of decorators, it is simply syntactic sugar. It allows
you to see, up front, what is going to happen. I think, once I get used
to it, I'll get to like it.

The way to read
@apply
def name():
# code
is that we are defining 'name' to be the return value of the effectively
anonymous function that follows.

--
Jonathan

alex23

unread,
Aug 21, 2009, 12:09:02 PM8/21/09
to
On Aug 21, 11:36 pm, Jonathan Fine <jf...@pytex.org> wrote:
> It might seem odd to use 'apply' as a decorator, but it can make sense.

Yes, it's an idiom I've used myself for property declarations, but one
I find myself using less often:

class ColourThing(object):
@apply
def rgb():
def fset(self, rgb):
self.r, self.g, self.b = rgb
def fget(self):
return (self.r, self.g, self.b)
return property(**locals())

Unfortunately, apply() has been removed as a built-in in 3.x. I'm not
sure if it has been relocated to a module somewhere, there's no
mention of such in the docs.

> Without using 'apply' as a decorator one alternative is
>      def tmp():
>          value = []
>          # complicated code
>          return value
>      tags = tmp()
>      del tmp

You can save yourself the tidy up by using the same name for the
function & the label:

def tags():
value = []
# ...
return value
tags = tags()

> Like all uses of decorators, it is simply syntactic sugar.  It allows
> you to see, up front, what is going to happen.  I think, once I get used
> to it, I'll get to like it.

The question is, is it really that useful, or is it just a slight
aesthetic variation? Given that apply(f, args, kwargs) is identical to
f(*args, **kwargs), it's understandable that's apply() isn't seen as
worth keeping in the language.

Why I've personally stopped using it: I've always had the impression
that decorators were intended to provide a convenient and obvious way
of augmenting functions. Having one that automatically executes the
function at definition just runs counter to the behaviour I expect
from a decorator. Especially when direct assignment... foo = foo
() ...is a far more direct and clear way of expressing exactly what is
happening.

But that's all IMO, if you feel it makes your code cleaner and don't
plan on moving to 3.x any time soon (come on in! the water's great!),
go for it :)

Jonathan Fine

unread,
Aug 21, 2009, 3:23:51 PM8/21/09
to
alex23 wrote:

> Unfortunately, apply() has been removed as a built-in in 3.x. I'm not
> sure if it has been relocated to a module somewhere, there's no
> mention of such in the docs.

The old use of apply()

> You can save yourself the tidy up by using the same name for the
> function & the label:
>
> def tags():
> value = []
> # ...
> return value
> tags = tags()

I don't like that because there's no hint at
def tags():
that this is /not/ the value of tags.

>> Like all uses of decorators, it is simply syntactic sugar. It allows
>> you to see, up front, what is going to happen. I think, once I get used
>> to it, I'll get to like it.
>
> The question is, is it really that useful, or is it just a slight
> aesthetic variation? Given that apply(f, args, kwargs) is identical to
> f(*args, **kwargs), it's understandable that's apply() isn't seen as
> worth keeping in the language.

Yes, I agree with that, completely.

> Why I've personally stopped using it: I've always had the impression
> that decorators were intended to provide a convenient and obvious way
> of augmenting functions.

Yes, that was the intended use case.

> Having one that automatically executes the
> function at definition just runs counter to the behaviour I expect
> from a decorator.

I'd expect the name of the decorator to explain what is going on. If
apply() were a well known part of the language, that would be fine.

> Especially when direct assignment... foo = foo
> () ...is a far more direct and clear way of expressing exactly what is
> happening.

Actually, I think the decorator approach is clearer. But that's just my
opinion, and not with the benefit of a lot of experience.

> But that's all IMO, if you feel it makes your code cleaner and don't
> plan on moving to 3.x any time soon (come on in! the water's great!),
> go for it :)

Thank you for your comments, Alex. And particularly for telling me that
apply() is no longer a builtin for Python 3.

--
Jonathan

Jonathan Gardner

unread,
Aug 21, 2009, 5:53:55 PM8/21/09
to
On Aug 21, 6:36 am, Jonathan Fine <jf...@pytex.org> wrote:
>      @apply
>      def tags():
>          value = []
>          # complicated code
>          return value
>

Is this different from:

tags = []
# complicated code

I can see the argument that you are cleaning up a lot of intermediary
variables upon return, though.

Jonathan Gardner

unread,
Aug 21, 2009, 6:17:40 PM8/21/09
to
On Aug 21, 9:09 am, alex23 <wuwe...@gmail.com> wrote:
> On Aug 21, 11:36 pm, Jonathan Fine <jf...@pytex.org> wrote:
>
> class ColourThing(object):
>     @apply
>     def rgb():
>         def fset(self, rgb):
>             self.r, self.g, self.b = rgb
>         def fget(self):
>             return (self.r, self.g, self.b)
>         return property(**locals())
>

This is brilliant. I am going to use this more often. I've all but
given up on property() since defining "get_foo", "get_bar", etc... has
been a pain and polluted the namespace.


> Unfortunately, apply() has been removed as a built-in in 3.x. I'm not
> sure if it has been relocated to a module somewhere, there's no
> mention of such in the docs.

apply = lambda f: f()

It's one of those functions that is easier to define than import.


> Why I've personally stopped using it: I've always had the impression
> that decorators were intended to provide a convenient and obvious way
> of augmenting functions. Having one that automatically executes the
> function at definition just runs counter to the behaviour I expect
> from a decorator. Especially when direct assignment... foo = foo
> () ...is a far more direct and clear way of expressing exactly what is
> happening.
>

If anyone reads the decorator and doesn't think "this thing below is
defined as the result of this decorator function" then they don't
understand decorators at all.

alex23

unread,
Aug 21, 2009, 10:35:38 PM8/21/09
to
Jonathan Gardner <jgard...@jonathangardner.net> wrote:
> This is brilliant. I am going to use this more often. I've all but
> given up on property() since defining "get_foo", "get_bar", etc... has
> been a pain and polluted the namespace.

Unfortunately I can't remember who I first learned it from - it was
definitely in a post to this group - otherwise all credit would be
their's.

> It's one of those functions that is easier to define than import.

And so obvious now :)

> If anyone reads the decorator and doesn't think "this thing below is
> defined as the result of this decorator function" then they don't
> understand decorators at all.

Well, it's not so much a question of the reader's intelligence as that
Python already has a readily identifiable assignment operator. Having
a second mechanism for assignment with no real similarity to the first
just adds cognitive friction to reading the code...not because the
reader doesn't understand what is happening, but because it's not
obvious _why_ this second form would have been chosen.

Nothing that couldn't be mitigated with a comment, I guess.

# @apply used to prevent having to repeat references

That would work for me.

Steven D'Aprano

unread,
Aug 21, 2009, 10:54:27 PM8/21/09
to
On Fri, 21 Aug 2009 15:17:40 -0700, Jonathan Gardner wrote:

>> Unfortunately, apply() has been removed as a built-in in 3.x. I'm not
>> sure if it has been relocated to a module somewhere, there's no mention
>> of such in the docs.
>
> apply = lambda f: f()
>
> It's one of those functions that is easier to define than import.

>>> apply = lambda f: f()

>>> __builtin__.apply(len, 'a')
1
>>> apply(len, 'a')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: <lambda>() takes exactly 1 argument (2 given)


It's a little bit more difficult to define it *correctly*. Here's a
working version of apply:


def apply(object, *args, **kwargs):
"""apply(object[, args[, kwargs]]) -> value

Call a callable object with positional and keyword arguments.

>>> apply(max, 'one', 'two', 'three', 'four', key=len)
'three'

"""
return object(*args, **kwargs)

Note that this:

* actually does what apply() is supposed to do;
* defines the function name, useful for tracebacks;
* has a docstring, useful for interactive use and documentation;
* includes an example suitable for automated testing with doctest.

--
Steven

Jonathan Fine

unread,
Aug 22, 2009, 3:09:52 AM8/22/09
to
Jonathan Gardner wrote:
> On Aug 21, 9:09 am, alex23 <wuwe...@gmail.com> wrote:
>> On Aug 21, 11:36 pm, Jonathan Fine <jf...@pytex.org> wrote:
>>
>> class ColourThing(object):
>> @apply
>> def rgb():
>> def fset(self, rgb):
>> self.r, self.g, self.b = rgb
>> def fget(self):
>> return (self.r, self.g, self.b)
>> return property(**locals())
>>
>
> This is brilliant. I am going to use this more often. I've all but
> given up on property() since defining "get_foo", "get_bar", etc... has
> been a pain and polluted the namespace.


I think we can do better, with a little work. And also solve the
problem that 'apply' is no longer a builtin in Python3.

Here's my suggestion:
===
import wibble # Informs reader we're doing something special here.

class ColourThing(object):

@wibble.property
def rgb():
'''This is the docstring for the property.'''


def fset(self, rgb):
self.r, self.g, self.b = rgb
def fget(self):
return (self.r, self.g, self.b)

return locals()
===

And here's wibble.property()
===
_property = property # Avoid collision.
def property(fn):
return _property(doc=fn.__doc__, **fn())
===

We can add apply() to the wibble module. By the way, 'wibble' is a
placeholder to the real name. Any suggestions?

--
Jonathan

Leonhard Vogt

unread,
Aug 22, 2009, 4:58:47 AM8/22/09
to

> Why I've personally stopped using it: I've always had the impression
> that decorators were intended to provide a convenient and obvious way
> of augmenting functions. Having one that automatically executes the
> function at definition just runs counter to the behaviour I expect
> from a decorator. Especially when direct assignment... foo = foo
> () ...is a far more direct and clear way of expressing exactly what is
> happening.

if you have to define it yourself, you could call it store_result or
something else instead of apply.

@store_result
def tags():
return result

Leonhard

Steven D'Aprano

unread,
Aug 22, 2009, 5:03:39 AM8/22/09
to
On Sat, 22 Aug 2009 08:09:52 +0100, Jonathan Fine wrote:

> Jonathan Gardner wrote:
>> On Aug 21, 9:09 am, alex23 <wuwe...@gmail.com> wrote:
>>> On Aug 21, 11:36 pm, Jonathan Fine <jf...@pytex.org> wrote:
>>>
>>> class ColourThing(object):
>>> @apply
>>> def rgb():
>>> def fset(self, rgb):
>>> self.r, self.g, self.b = rgb
>>> def fget(self):
>>> return (self.r, self.g, self.b)
>>> return property(**locals())
>>>
>>>
>> This is brilliant. I am going to use this more often. I've all but
>> given up on property() since defining "get_foo", "get_bar", etc... has
>> been a pain and polluted the namespace.
>
>
> I think we can do better, with a little work. And also solve the
> problem that 'apply' is no longer a builtin in Python3.

There's a standard idiom for that, using the property() built-in, for
Python 2.6 or better.

Here's an example including a getter, setter, deleter and doc string,
with no namespace pollution, imports, or helper functions or deprecated
built-ins:

class ColourThing(object):
@property
def rgb(self):
"""Get and set the (red, green, blue) colours."""
return (self.r, self.g, self.b)
@rgb.setter
def rgb(self, rgb):


self.r, self.g, self.b = rgb

@rgb.deleter
def rgb(self):
del self.r, self.g, self.b



--
Steven

Jonathan Fine

unread,
Aug 22, 2009, 5:51:27 AM8/22/09
to
Steven D'Aprano wrote:

> There's a standard idiom for that, using the property() built-in, for
> Python 2.6 or better.
>
> Here's an example including a getter, setter, deleter and doc string,
> with no namespace pollution, imports, or helper functions or deprecated
> built-ins:
>
> class ColourThing(object):
> @property
> def rgb(self):
> """Get and set the (red, green, blue) colours."""
> return (self.r, self.g, self.b)
> @rgb.setter
> def rgb(self, rgb):
> self.r, self.g, self.b = rgb
> @rgb.deleter
> def rgb(self):
> del self.r, self.g, self.b


Sorry, Steve, but I don't understand this. In fact, I don't even see
how it can be made to work.

Unless an exception is raised,
@wibble
def wobble():
pass
will make an assignment to wobble, namely the return value of wibble.
So in your example above, there will be /three/ assignments to rgb.
Unless you do some complicated introspection (and perhaps not even then)
surely they will clobber each other.

I still prefer:
@wibble.property
def rgb():
'''Red Green Blue color settings (property)'''

def fset(rgb):


self.r, self.g, self.b = rgb

return locals()

--
Jonathan

Steven D'Aprano

unread,
Aug 22, 2009, 6:45:13 AM8/22/09
to
On Sat, 22 Aug 2009 10:51:27 +0100, Jonathan Fine wrote:

> Steven D'Aprano wrote:
>
>> There's a standard idiom for that, using the property() built-in, for
>> Python 2.6 or better.
>>
>> Here's an example including a getter, setter, deleter and doc string,
>> with no namespace pollution, imports, or helper functions or deprecated
>> built-ins:
>>
>> class ColourThing(object):
>> @property
>> def rgb(self):
>> """Get and set the (red, green, blue) colours.""" return
>> (self.r, self.g, self.b)
>> @rgb.setter
>> def rgb(self, rgb):
>> self.r, self.g, self.b = rgb
>> @rgb.deleter
>> def rgb(self):
>> del self.r, self.g, self.b
>
>
> Sorry, Steve, but I don't understand this. In fact, I don't even see
> how it can be made to work.

Nevertheless, it does work, and it's not even magic. It's described (very
briefly) in the docstring for property: help(property) will show it to
you. More detail is here:

http://docs.python.org/library/functions.html#property

As for how it can work, it's not that difficult. All you need is for the
setter and deleter methods to add an appropriate fset and fdel method to
the property, then return it. Here's a toy example which may demonstrate
the process (although unlike property, fget, fset and fdel don't have any
special meanings):

class ToyProperty(object):
def __init__(self, fget, fset=None, fdel=None, fdoc=None):
if fdoc is None:
fdoc = fget.__doc__
self.fget = fget
self.fset = fset
self.fdel = fdel
def getter(self, fget):
self.fget = fget
return self
def setter(self, fset):
self.fset = fset
return self
def deleter(self, fdel):
self.fdel = fdel
return self


> Unless an exception is raised,
> @wibble
> def wobble():
> pass
> will make an assignment to wobble, namely the return value of wibble. So
> in your example above, there will be /three/ assignments to rgb.

Yes. A tiny inefficiency, which only occurs when the class is created. If
you care about that, then (1) use the full form of property() where you
supply the fget, fset and fdel arguments all at once, and (2) you really
need to get out into the fresh air more *wink*

> Unless
> you do some complicated introspection (and perhaps not even then) surely
> they will clobber each other.

So what? The process is no weirder than:

x = ['fget']
x = x + ['fset']
x = x + ['fdel']

--
Steven

Jonathan Fine

unread,
Aug 22, 2009, 8:04:02 AM8/22/09
to

My apologies. I wasn't up to date with my Python versions:

| Changed in version 2.6: The getter, setter, and deleter
| attributes were added.

I was still thinking Python2.5 (or perhaps earlier?). I still don't
like it. All those repetitions of 'rgb'.

--
Jonathan

Jan Kaliszewski

unread,
Aug 22, 2009, 4:11:20 PM8/22/09
to alex23, pytho...@python.org
21-08-2009 o 18:09:02 alex23 <wuw...@gmail.com> wrote:

> Unfortunately, apply() has been removed as a built-in in 3.x.

You can always implement it yourself :)

def apply(function, args=(), keywords={}):
return function(*args, **keywords)

--
Jan Kaliszewski (zuo) <z...@chopin.edu.pl>

0 new messages