3

Really sorry for the extremely stupid title, but if I know what it is, I wouldn't write here (:

def some_decorator( func ):
    # ..

class A:
    @some_decorator
    def func():
        pass
    @func.some_decorator    # this one here - func.some_decorator ?
    def func():
        pass

some_decorator decorates func - that's OK. But what is func.some_decorator and how some_decorator becomes a member ( or something else ? ) of func?

P.S. I'm 90% sure, that there's such question here (as this seems something basic), but I don't know how to search it. If there's a exact duplicate, I'll delete this question.


Note : It's not typo, nor accident, that both member functions are named func. The decorator is for overloading: the question is related to Decorating method (class methods overloading)

Community
  • 1
  • 1
Kiril Kirov
  • 37,467
  • 22
  • 115
  • 187
  • When you're looking for things like this try `dir()`, as in `dir(func)`. – Ben Jackson May 15 '11 at 18:01
  • I know about `dir` and this was the first thing, that came to my mind, but I just saw that as a syntax, in an example like this, without any real implementation, so `dir` is not working here. 10x anyway. – Kiril Kirov May 15 '11 at 18:09
  • What do you actually want to do? (Or are you trying to understand someone eles's code?) – Andrew Jaffe May 15 '11 at 18:32
  • Well, I'm trying to do add functionality for methods overloading, as in the relater question, but not just using the standard syntax `@decorator`, but the one, described here - use `@decorator` for the first method and then use `@first_method.decorator`. I just saw this as a syntax and wanted to understand and try to implement something like this. This seems kinda unnecessary, but I'm just practicing, nothing serious. – Kiril Kirov May 15 '11 at 18:36

2 Answers2

7

Remember that the function definition with decorator is equivalent to this:

def func():
    pass
func = some_decorator(func)

So in the following lines, func doesn't refer to the function you defined but to what the decorator turned it into. Also note that decorators can return any object, not just functions. So some_decorator returns an object with a method (it's unfortunate that the names some_decorator and func are reused in the example - it's confusing, but doesn't change anything about the concept) that is itself a decorator. As the expression after the @ is evaluated first, you still have a reference to the first decorator's method after you defined another plain function func. That decorator is applied to this new function. The full example is then equivalent to this:

class A:
    def func():
        pass
    func = some_decorator(func)

    _decorator = func.some_decorator
    def func():
        pass
    func = _decorator(func)
  • Actually, I'm talking about the case, when `func` and `a_method` are the same names (it's a case about overloading). So, it's not _unfortunate_ that _func_ is reused. – Kiril Kirov May 15 '11 at 18:00
  • @Kiril: It's unfortunate in that it's confusing. It doesn't change anything about the concept - the only difference is that you overwrite the first decorator near the end, but you still pass the second function definition to its method first and end up with the result of that method call as `func`. Edited to clarify the one thing that may seem paradoxial about it. –  May 15 '11 at 18:02
  • @delnan - okay, your last edit makes it more clear to me now. But this is not working for _any_ decorator, right ? (or at least, I couldn't make it work). Looking at this `_decorator = func.some_decorator` - this means, that the decorator should not return a function, but some object, right ? – Kiril Kirov May 15 '11 at 18:18
  • @Kiril: Correct - obviously, this doesn't work for all decorators just like `sum(x.n for x in xs)` doesn't work for all iterables `xs`. –  May 15 '11 at 18:24
  • @delnan - ok, I get it. Sorry for the dumb questions, but I'm a total Python beginner. 10x – Kiril Kirov May 15 '11 at 18:27
  • @Kiril: The question isn't dumb. Apart from "there are no stupid questions, only stupid answers", the code in question is really tricky and confusing and I can't blame anyone for being confused. Took me a while too. –  May 15 '11 at 18:30
  • Investigating this behaviour is actually peeking under the hood to understand how the builtin `property()` descriptor works, so it's definitely an interesting thing to explore. – ncoghlan May 16 '11 at 12:45
2

One way to clarify this is to demonstrate it with a concrete example that behaves like this, the builtin property descriptor:

class C(object):
    @property
    def x(self):
        "This is a property object, not a function"
        return self._x
    @x.setter
    def x(self, val):
        self._x = val

>>> c = C()
>>> c.x = 1
>>> c.x
1
>>> C.x
<property object at 0x2396100>
>>> C.x.__doc__
'This is a property object, not a function'
>>> C.x.getter.__doc__
'Descriptor to change the getter on a property.'
>>> C.x.setter.__doc__ 
'Descriptor to change the setter on a property.'
>>> C.x.deleter.__doc__
'Descriptor to change the deleter on a property.'

The first invocation of property (as a decorator) means that x is not a function - it is a property descriptor. A feature of properties is that they allow you to initially define just the fget method, and then provide fset and fdel later by using the property.setter and property.deleter decorators (although since each of these creates a new property object, you do need to make sure to use the same name each time).

Something similar will usually be the case whenever you see code using this kind of pattern. Ideally, the naming of the decorators involved will make it reasonably clear what is going on (e.g. most people seem to grasp the idiom for defining property attributes reasonably easily).

ncoghlan
  • 40,168
  • 10
  • 71
  • 80