views:

95

answers:

2

As of 2.4 (2.6 for classes), python allows you to decorate a function with another function:

def d(func): return func

@d
def test(first): pass

It's a convenient syntactic sugar. You can do all sorts of neat stuff with decorators without making a mess. However, if you want to find out the original function that got decorated you have to jump through hoops (like Cls.method.__func__.__closure__[0].cell_contents or worse).

I found myself wishing for a better way and found that there had been some discussion on python-dev about adding a variable called __decorated__ to the [new] function returned by the decorator. However, it appears that didn't go anywhere.

Being an adventuresome person, and having had pretty heavy python experience for about 4 years, I thought I would look into implementing __decorated__ in the python compiler source, just to see how it goes.

To be honest I have never delved into the C underneath the hood, so my first hours have been just trying to make sense of how the underlying C code works. So firstly, what would be the best resources to get my head around what I would have to change/add for __decorator__?

Secondly, if a decorator returns a new function then __decorated__ would just return the original, decorated function. However, if the decorator returns the original function, what should happen? Here are three options I could think of (the third is my favorite):

  1. Don't add __decorator__.
  2. Add __decorator__ but set it to None.
  3. Add __decorator__ and set it to the original function anyway.

So if it were to happen, what do you think would be the best option?

UPDATE:

Someone else brought to my attention a scenario that I had missed. What happens when the decorator returns neither the original function nor a function that wraps the original? At that point nothing is holding a reference to the original function and it will get garbage collected. (Thanks Oddthinking!)

So in that case, I think that I would still go with the third option. The object returned by the decorator would gain a __decorated__ name that references the original function. This would mean that it would not be garbage-collected.

It seems weird to me that the function from a class definition would utterly disappear because you decorated it. In my mind that is even more reason to have a __decorated__ attribute applied for every decorator. However, it's more likely that my intuition is faulty and that the current behavior is what most people would expect. Any thoughts?

p.s. this is an extension of an earlier, more general question I had. I also went for more info on the first part with a separate post.

+1  A: 

i feel free to use some single underscored "private" attribute like _mydecor

possible multi-decorator solution:

def decorate(decoration): 
    def do_decor(func): 
        if hasattr(func, '_mydecor'): 
            func._mydecor.add(decoration) 
        else: 
            func._mydecor = set([decoration]) 
        return func 
    return do_decor 

def isDecorated(func, decoration): 
    return (decoration in getattr(func, '_mydecor', set())) 

@decorate('red') 
@decorate('green') 
def orangefunc(): pass 

print isDecorated(orangefunc, 'green') # -> True 
print isDecorated(orangefunc, 'blue')  # -> False 
mykhal
That works well if you control the decorator. I suppose you could wrap the decorator to get this done when you don't control it.
Eric Snow
+1  A: 

Well, independent of any discussion over whether this is a good idea, I'd go for option #3 because it's the most consistent: it always shows that the function has been decorated by the presence of the attribute, and accessing the value of the attribute always returns a function, so you don't have to test it against None.

You should also consider this, though: what would you propose to do about manual decoration? e.g.

def test(first): pass
test = d(test)

Regarding the first part of the question, I haven't looked at the Python interpreter source code very much so I wouldn't be able to point you to anything particularly useful.

David Zaslavsky
Good point about manual decoration.
Eric Snow
I would think you would have to manually add the `__decorated__` attribute to the new function, which would be even uglier.
Eric Snow