Interesting decorator use.

  • Thread starter Scott David Daniels
  • Start date
S

Scott David Daniels

I have started doing the following to watch for exceptions in wxPython.
I'd like any input about (A) the interface, and (B) the frame before I
throw it in the recipes book.

import wx, os, sys
errorframe = None

def watcherrors(function):
'''function decorator to display Exception information.'''
def substitute(*args, **kwargs):
try:
return function(*args, **kwargs)
except Exception:
error_type, error, traceback = sys.exc_info()
mb = wx.MessageDialog(errorframe,
'%s\n\nClick OK to see traceback' % error,
'Error in Run',
wx.OK | wx.CANCEL | wx.ICON_ERROR)
if wx.ID_OK == mb.ShowModal():
mb.Destroy()
from traceback import extract_tb
trace = ['%s line %s in %s:\n\t%s' % (
(os.path.splitext(os.path.split(
name)[1])[0], line, fun, text)
for name, line, fun, text in
extract_tb(traceback)]
mb = wx.MessageDialog(errorframe,
'\n'.join(['%s\n' % error] + trace),
'Run Error w/ Traceback',
wx.OK | wx.ICON_ERROR)
result = mb.ShowModal()
mb.Destroy()
raise
if function.__doc__ is not None:
substitute.__doc__ = function.__doc__
return substitute

You can use it as follows to wrap functions and methods:

@watcherrors
def something(somearg)
if somearg is not None:
raise ValueError(somearg)


You can use this by simply placing '@watcherrors' on the line before a
function or method definition. Some of my questions are:
A) Is there a better name? Perhaps "exceptions" or "watcher"?
B) Is a global the right way to define the parent of the driven
message box, or adding another wrapper layer as follows better?

def exceptionwatcher(base, name):
def watcherrors(....
...
def substitute(*args, **kwargs):
try:
return function(*args, **kwargs)
except Exception:
error_type, error, traceback = sys.exc_info()
errorframe = getattr(base, name, None) ### new code
...
return watcherrors

Wrapping the code this way allows you to say, watcher-by-watcher,
where to get the parent for the message box. You would then use
it as follows:

@exceptionwatcher(someholder, 'frame')
def something(somearg)
if somearg is not None:
raise ValueError(somearg)

C) Stuff I've not even thought of.

--Scott David Daniels
(e-mail address removed)
 
T

Tom Willis

I have started doing the following to watch for exceptions in wxPython.
I'd like any input about (A) the interface, and (B) the frame before I
throw it in the recipes book.

import wx, os, sys
errorframe = None

def watcherrors(function):
'''function decorator to display Exception information.'''
def substitute(*args, **kwargs):
...

Pretty cool.

Question on decorators in general. Can you parameterize those?

If I wanted to something and after the function call for example, I
would expect something like this would work.

def prepostdecorator(function,pre,post):
def wrapper(*args,**kwargs):
pre()
result = function(*args,**kwargs)
post()
return result
return wrapper

def dopre():
print "call pre"

def dopost():
print "call post"

@prepostdecorator(pre,post)
def sayhello(Name):
print "Hey %s, nice to meet you" % Name

#sayhello = prepostdecorator(sayhello,dopre,dopost)

if __name__=="__main__":
sayhello("Dude")



but I get ...
TypeError: prepostdecorator() takes exactly 3 arguments (2 given)


Where as

def prepostdecorator(function,pre,post):
def wrapper(*args,**kwargs):
pre()
result = function(*args,**kwargs)
post()
return result
return wrapper

def dopre():
print "call pre"

def dopost():
print "call post"

def sayhello(Name):
print "Hey %s, nice to meet you" % Name

sayhello = prepostdecorator(sayhello,dopre,dopost)

if __name__=="__main__":
sayhello("Dude")

#outputs
call pre
Hey Dude, nice to meet you
call post

Does what I want.


I guess I'm having problems with how function get's in there similair
to how self magically gets in a method, except when you specify other
params. Got linky?
 
S

Steven Bethard

Tom said:
Question on decorators in general. Can you parameterize those?

If I wanted to something and after the function call for example, I
would expect something like this would work.

def prepostdecorator(function,pre,post):
def wrapper(*args,**kwargs):
pre()
result = function(*args,**kwargs)
post()
return result
return wrapper

def dopre():
print "call pre"

def dopost():
print "call post"

@prepostdecorator(pre,post)
def sayhello(Name):
print "Hey %s, nice to meet you" % Name

#sayhello = prepostdecorator(sayhello,dopre,dopost)

if __name__=="__main__":
sayhello("Dude")

but I get ...
TypeError: prepostdecorator() takes exactly 3 arguments (2 given)

You get this TypeError for the same reason that I get the following
TypeError:

py> def prepostdecorator(function, pre, post):
.... pass
....
py> prepostdecorator(1, 2)
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
TypeError: prepostdecorator() takes exactly 3 arguments (2 given)

The expression after @ is a _normal Python expression_. So since you
couldn't call prepostdecorator with 2 arguments in any other situation,
you still can't.

If you want to call prepostdecorator with 2 arguments, you need to write
it this way. A few options:

(1) Use nested functions:

py> def prepostdecorator(pre,post):
.... def decorator(function):
.... def wrapper(*args,**kwargs):
.... pre()
.... result = function(*args,**kwargs)
.... post()
.... return result
.... return wrapper
.... return decorator
....
py> @prepostdecorator(dopre, dopost)
.... def sayhello(name):
.... print "Hey %s, nice to meet you" % name
....
py> sayhello('Tom')
call pre
Hey Tom, nice to meet you
call post

(2) Use functional.partial (PEP 309[1])

py> def prepostdecorator(pre, post, function):
.... def wrapper(*args,**kwargs):
.... pre()
.... result = function(*args,**kwargs)
.... post()
.... return result
.... return wrapper
....
py> @partial(prepostdecorator, dopre, dopost)
.... def sayhello(name):
.... print "Hey %s, nice to meet you" % name
....
py> sayhello('Tom')
call pre
Hey Tom, nice to meet you
call post

(3) Use a class:

py> class prepostdecorator(object):
.... def __init__(self, pre, post):
.... self.pre, self.post = pre, post
.... def __call__(self, function):
.... def wrapper(*args,**kwargs):
.... self.pre()
.... result = self.function(*args,**kwargs)
.... self.post()
.... return result
.... return wrapper
py> @prepostdecorator(dopre, dopost)
.... def sayhello(name):
.... print "Hey %s, nice to meet you" % name
....
py> sayhello('Tom')
call pre
Hey Tom, nice to meet you
call post

Note that in all of the above cases, the result of evaluating the
expression after the @ is a callable object that takes _exactly one_
argument, the function to be decorated.

HTH,

STeVe

[1] http://www.python.org/peps/pep-0309.html
 
S

Steven Bethard

I said:
Tom said:
Question on decorators in general. Can you parameterize those?
[snip]

If you want to call prepostdecorator with 2 arguments, you need to write
it this way. A few options:

Sorry, I forgot my favorite one:

(4) Use a class and functional.partial:

py> class prepostdecorator(object):
.... def __init__(self, pre, post, function):
.... self.pre, self.post, self.function = pre, post, function
.... def __call__(self, *args, **kwargs):
.... self.pre()
.... result = self.function(*args,**kwargs)
.... self.post()
.... return result
....
py> @partial(prepostdecorator, dopre, dopost)
.... def sayhello(name):
.... print "Hey %s, nice to meet you" % name
....
py> sayhello('Tom')
call pre
Hey Tom, nice to meet you
call post

Woo-hoo, I got rid of all the nested functions! ;)

STeVe
 
T

Tom Willis

Tom said:
Question on decorators in general. Can you parameterize those?

If I wanted to something and after the function call for example, I
would expect something like this would work.

def prepostdecorator(function,pre,post):
def wrapper(*args,**kwargs):
pre()
result = function(*args,**kwargs)
post()
return result
return wrapper

def dopre():
print "call pre"

def dopost():
print "call post"

@prepostdecorator(pre,post)
def sayhello(Name):
print "Hey %s, nice to meet you" % Name

#sayhello = prepostdecorator(sayhello,dopre,dopost)

if __name__=="__main__":
sayhello("Dude")

but I get ...
TypeError: prepostdecorator() takes exactly 3 arguments (2 given)

You get this TypeError for the same reason that I get the following
TypeError:

py> def prepostdecorator(function, pre, post):
... pass
...
py> prepostdecorator(1, 2)
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
TypeError: prepostdecorator() takes exactly 3 arguments (2 given)

The expression after @ is a _normal Python expression_. So since you
couldn't call prepostdecorator with 2 arguments in any other situation,
you still can't.

If you want to call prepostdecorator with 2 arguments, you need to write
it this way. A few options:

(1) Use nested functions:

py> def prepostdecorator(pre,post):
... def decorator(function):
... def wrapper(*args,**kwargs):
... pre()
... result = function(*args,**kwargs)
... post()
... return result
... return wrapper
... return decorator
...
py> @prepostdecorator(dopre, dopost)
... def sayhello(name):
... print "Hey %s, nice to meet you" % name
...
py> sayhello('Tom')
call pre
Hey Tom, nice to meet you
call post

(2) Use functional.partial (PEP 309[1])

py> def prepostdecorator(pre, post, function):
... def wrapper(*args,**kwargs):
... pre()
... result = function(*args,**kwargs)
... post()
... return result
... return wrapper
...
py> @partial(prepostdecorator, dopre, dopost)
... def sayhello(name):
... print "Hey %s, nice to meet you" % name
...
py> sayhello('Tom')
call pre
Hey Tom, nice to meet you
call post

(3) Use a class:

py> class prepostdecorator(object):
... def __init__(self, pre, post):
... self.pre, self.post = pre, post
... def __call__(self, function):
... def wrapper(*args,**kwargs):
... self.pre()
... result = self.function(*args,**kwargs)
... self.post()
... return result
... return wrapper
py> @prepostdecorator(dopre, dopost)
... def sayhello(name):
... print "Hey %s, nice to meet you" % name
...
py> sayhello('Tom')
call pre
Hey Tom, nice to meet you
call post

Note that in all of the above cases, the result of evaluating the
expression after the @ is a callable object that takes _exactly one_
argument, the function to be decorated.

HTH,

STeVe

[1] http://www.python.org/peps/pep-0309.html
Wow thanks for the explanation!! Some of it is a bit mind bending to
me at the moment , but I'm going to mess with it a bit.
 
S

Steven Bethard

Tom said:
Wow thanks for the explanation!! Some of it is a bit mind bending to
me at the moment , but I'm going to mess with it a bit.

Oh, I also should have mentioned that there's some explanation at:

http://www.python.org/peps/pep-0318.html

Specifically on parameterizing:

"""The current syntax also allows decorator declarations to call a
function that returns a decorator:

@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
pass

This is equivalent to:

func = decomaker(argA, argB, ...)(func)

The rationale for having a function that returns a decorator is that the
part after the @ sign can be considered to be an expression (though
syntactically restricted to just a function), and whatever that
expression returns is called. See declaration arguments [15]."""

But playing around with it is, IMHO, definitely the best way to figure
it out. =)

STeVe
 
T

Tom Willis

Tom said:
Wow thanks for the explanation!! Some of it is a bit mind bending to
me at the moment , but I'm going to mess with it a bit.

Oh, I also should have mentioned that there's some explanation at:

http://www.python.org/peps/pep-0318.html

Specifically on parameterizing:

"""The current syntax also allows decorator declarations to call a
function that returns a decorator:

@decomaker(argA, argB, ...)
def func(arg1, arg2, ...):
pass

This is equivalent to:

func = decomaker(argA, argB, ...)(func)

The rationale for having a function that returns a decorator is that the
part after the @ sign can be considered to be an expression (though
syntactically restricted to just a function), and whatever that
expression returns is called. See declaration arguments [15]."""

But playing around with it is, IMHO, definitely the best way to figure
it out. =)

STeVe
Getting back to your recipe. Through the explanation of how to get
parameters in there, I see how it is possible to get a logger in
there.

Pretty slick that python can have AOP-like features sort of out of the box.


Thanks again, off to check out the link...
 
S

Steven Bethard

Tom said:
Getting back to your recipe. Through the explanation of how to get
parameters in there, I see how it is possible to get a logger in
there.

Cool. But credit where it's due, the recipe's Scott David Daniels's. =)

STeVe
 
C

Chris Smith

Tom> Pretty slick that python can have AOP-like features sort of
Tom> out of the box.
One wonders if there will not be
py>import AOP
in the pythonic future. These decorators could lead to byzantine
trees of @.
Now, if the decorators start writing code on the fly, shall python
have achieved Lisp macros?
Best,
Chris
 

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
474,221
Messages
2,571,133
Members
47,747
Latest member
swapote

Latest Threads

Top