Confusion about decorators

H

Henrik Faber

Hi group,

I'm a bit confused regarding decorators. Recently started playing with
them with Python3 and wanted (as an excercise) to implement a simple
type checker first: I know there are lots of them out there, this is
actually one of the reasons I chose that particular function (to compare
my solution against other, proven solutions).

Starting with a blank slate, I did something along the lines of:

class _TypeCheckedFunction():
def __init__(self, decoratedfunction):
self._decoratedfunction = decoratedfunction

def __call__(self, *args, **kwargs):
[...] Actual checking

def typecheck(wrappedfunction):
checkfunction = _TypeCheckedFunction(wrappedfunction)
functools.update_wrapper(checkfunction, wrappedfunction)
return checkfunction

And decorate my methods like

@typecheck
def setbar(self, bar: str):

This works somewhat. The problem is, however, when the method is
actually called. This is what happens:

1. The decorator is called upon import of the decorated class. It
creates a _TypeCheckedFunction(setbar) object.
2. When setbar is actually called (blubb.setbar("fooobar")), the
__call__ method of the previously created _TypeCheckedFunction is invoked.
3. When trying to call self._decoratedfunction from within that object,
this fails: "self" is missing! self._decoratedfunction is only the
*function*, not the bound function of the object that contains setbar().
Therefore I cannot proceed here.

Solutions that I have seen working usually consist of two functions
wrapped in each other, but I do not know why the additional introduction
of a class makes everything fail.

Can someone please enlighten me?

Best regards,
Henrik
 
A

Andrea Crotti

Hi group,

I'm a bit confused regarding decorators. Recently started playing with
them with Python3 and wanted (as an excercise) to implement a simple
type checker first: I know there are lots of them out there, this is
actually one of the reasons I chose that particular function (to compare
my solution against other, proven solutions).

Starting with a blank slate, I did something along the lines of:

class _TypeCheckedFunction():
def __init__(self, decoratedfunction):
self._decoratedfunction = decoratedfunction

def __call__(self, *args, **kwargs):
[...] Actual checking

def typecheck(wrappedfunction):
checkfunction = _TypeCheckedFunction(wrappedfunction)
functools.update_wrapper(checkfunction, wrappedfunction)
return checkfunction

And decorate my methods like

@typecheck
def setbar(self, bar: str):

This works somewhat. The problem is, however, when the method is
actually called. This is what happens:

1. The decorator is called upon import of the decorated class. It
creates a _TypeCheckedFunction(setbar) object.
2. When setbar is actually called (blubb.setbar("fooobar")), the
__call__ method of the previously created _TypeCheckedFunction is invoked.
3. When trying to call self._decoratedfunction from within that object,
this fails: "self" is missing! self._decoratedfunction is only the
*function*, not the bound function of the object that contains setbar().
Therefore I cannot proceed here.

Solutions that I have seen working usually consist of two functions
wrapped in each other, but I do not know why the additional introduction
of a class makes everything fail.

Can someone please enlighten me?

Best regards,
Henrik

Not sure how that could work in general, what does "bar: str" should do?
Is that a dictionary?

Anyway there is already an implementation if you're interested for type
checking:
http://oakwinter.com/code/typecheck/

You can have a look at how they do it.
 
H

Henrik Faber

Not sure how that could work in general, what does "bar: str" should do?
Is that a dictionary?

No. It's PEP 3107 function annotations.
Anyway there is already an implementation if you're interested for type
checking:
http://oakwinter.com/code/typecheck/

*sigh* no, not really -- this is exactly why I wrote "I know there are
lots of them out there". I've actually seen and run
http://code.activestate.com/recipes/577299-method-signature-type-checking-decorator-for-pytho/

However, this doesn't do it for me -- I want to know why my solution
fails, not just use some other solution without really understanding it.
I really would like to understand what's going on.

I'm especially puzzled about the fact that in my solution, __call__ is
called with only the method's arguments (i.e. "fooobar") in my example
instead of two arguments (self, "fooobar").

Best regards,
Henrik
 
A

Arnaud Delobelle

Hi group,

I'm a bit confused regarding decorators. Recently started playing with
them with Python3 and wanted (as an excercise) to implement a simple
type checker first: I know there are lots of them out there, this is
actually one of the reasons I chose that particular function (to compare
my solution against other, proven solutions).

Starting with a blank slate, I did something along the lines of:

class _TypeCheckedFunction():
       def __init__(self, decoratedfunction):
               self._decoratedfunction = decoratedfunction

       def __call__(self, *args, **kwargs):
               [...] Actual checking

def typecheck(wrappedfunction):
       checkfunction = _TypeCheckedFunction(wrappedfunction)
       functools.update_wrapper(checkfunction, wrappedfunction)
       return checkfunction

And decorate my methods like

       @typecheck
       def setbar(self, bar: str):

This works somewhat. The problem is, however, when the method is
actually called. This is what happens:

1. The decorator is called upon import of the decorated class. It
creates a _TypeCheckedFunction(setbar) object.
2. When setbar is actually called (blubb.setbar("fooobar")), the
__call__ method of the previously created _TypeCheckedFunction is invoked..
3. When trying to call self._decoratedfunction from within that object,
this fails: "self" is missing! self._decoratedfunction is only the
*function*, not the bound function of the object that contains setbar().
Therefore I cannot proceed here.

Solutions that I have seen working usually consist of two functions
wrapped in each other, but I do not know why the additional introduction
of a class makes everything fail.

Can someone please enlighten me?

You can (need to?) use the descriptor protocol to deal with methods.

from functools import partial

class _TypeCheckedFunction():
def __init__(self, decoratedfunction):
self._decoratedfunction = decoratedfunction

def __call__(self, *args, **kwargs):
[...] Actual checking

def __get__(self, obj, objtype):
return partial(self, obj)

(Untested)

HTH
 
H

Henrik Faber

Can someone please enlighten me?

You can (need to?) use the descriptor protocol to deal with methods.

from functools import partial [...]
def __get__(self, obj, objtype):
return partial(self, obj)

Whoa. This is absolutely fantastic, it now works as expected (I get a
reference to "self").

I am very amazed -- I've been programming Python for about 5 years now
and have never even come close to something as a "descriptor protocol".
Python never ceases to amaze me. Do you have any beginners guide how
this works? The Pydoc ("Data Model") is comprehensive, but I really
don't know where to start to look.

Still amazed!

Best regards,
Henrik
 
A

Arnaud Delobelle

Can someone please enlighten me?

You can (need to?) use the descriptor protocol to deal with methods.

from functools import partial [...]
       def __get__(self, obj, objtype):
               return partial(self, obj)

Whoa. This is absolutely fantastic, it now works as expected (I get a
reference to "self").

I am very amazed -- I've been programming Python for about 5 years now
and have never even come close to something as a "descriptor protocol".
Python never ceases to amaze me. Do you have any beginners guide how
this works? The Pydoc ("Data Model") is comprehensive, but I really
don't know where to start to look.

Well, I've been using Python for 10 years :) The best reference I know is:

http://users.rcn.com/python/download/Descriptor.htm
 
H

Henrik Faber

Well, I've been using Python for 10 years :) The best reference I know is:

http://users.rcn.com/python/download/Descriptor.htm

Everyone starts out as a Padawan and I am no exception :)

Maybe five years from now I'll also have made my way to be a Python Jedi
and also shake the ins and outs of descriptors out of my sleeve :)

But I can only repeat myself: Python is such an exceptional language,
the more and more I know about it, the more I fall in love! Fantastic. I
wish we had these types of language when I was a kid!

Best regards and thanks again,
Henrik
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top