How to wrap a class's methods?

G

Grant Edwards

I want to subclass an IMAP connection so that most of the
methods raise an exception if the returned status isn't 'OK'.
This works, but there's got to be a way to do it that doesn't
involve so much duplication:

class MyImap4_ssl(imaplib.IMAP4_SSL):

def login(*args):
s,r = imaplib.IMAP4_SSL.login(*args)
if s!='OK':
raise NotOK((s,r))
return r

def list(*args):
s,r = imaplib.IMAP4_SSL.list(*args)
if s!='OK':
raise NotOK((s,r))
return r

def search(*args):
s,r = imaplib.IMAP4_SSL.search(*args)
if s!='OK':
raise NotOK((s,r))
return r

[and so on for another dozen methods]
 
J

John Lenton

I want to subclass an IMAP connection so that most of the
methods raise an exception if the returned status isn't 'OK'.
This works, but there's got to be a way to do it that doesn't
involve so much duplication:

class MyImap4_ssl(imaplib.IMAP4_SSL):

def login(*args):
s,r = imaplib.IMAP4_SSL.login(*args)
if s!='OK':
raise NotOK((s,r))
return r

def list(*args):
s,r = imaplib.IMAP4_SSL.list(*args)
if s!='OK':
raise NotOK((s,r))
return r

def search(*args):
s,r = imaplib.IMAP4_SSL.search(*args)
if s!='OK':
raise NotOK((s,r))
return r

[and so on for another dozen methods]

something like this:

def NotOKVerified(orig):
def f(*args):
s, r = orig(*args)
if s != 'OK':
raise NotOK((s,r))
return r
return f

class MyImap4_ssl(IMAP4_SSL):
pass

for method_name in ('login', 'list', 'search'):
setattr(MyImap4_ssl, method_name, getattr(IMAP4_SSL, method_name))

?

I'd usually put big fat warnings around this code, and explain exaclty
why I need to do things this way...

--
John Lenton ([email protected]) -- Random fortune:
"To vacillate or not to vacillate, that is the question ... or is it?"

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.0 (GNU/Linux)

iD8DBQFCFPU3gPqu395ykGsRAmi/AJ9ETKZ6EZ5pgHfpibPOdPHbV24F0QCgltf4
kDEAtn7POsO/tXW0s2uuMZM=
=BjlO
-----END PGP SIGNATURE-----
 
S

Steven Bethard

Grant said:
I want to subclass an IMAP connection so that most of the
methods raise an exception if the returned status isn't 'OK'.
This works, but there's got to be a way to do it that doesn't
involve so much duplication:

class MyImap4_ssl(imaplib.IMAP4_SSL):

def login(*args):
s,r = imaplib.IMAP4_SSL.login(*args)
if s!='OK':
raise NotOK((s,r))
return r

def list(*args):
s,r = imaplib.IMAP4_SSL.list(*args)
if s!='OK':
raise NotOK((s,r))
return r

def search(*args):
s,r = imaplib.IMAP4_SSL.search(*args)
if s!='OK':
raise NotOK((s,r))
return r

[and so on for another dozen methods]

You could try something like (Untested!):

class Wrapper(object):
def __init__(self, func):
self.func = func
def __call__(*args, **kwargs):
self, args = args[0], args[1:]
s, r = self.func(*args)
if s != 'OK':
raise NotOK((s, r))
return r

for func_name in ['login', 'list', 'search']:
func = Wrapper(getattr(imaplib.IMAP4_SSL, func_name))
setattr(imaplib.IMAP4_SSL, func_name, func)

STeVe
 
S

Steven Bethard

Grant said:
I want to subclass an IMAP connection so that most of the
methods raise an exception if the returned status isn't 'OK'.
This works, but there's got to be a way to do it that doesn't
involve so much duplication:

class MyImap4_ssl(imaplib.IMAP4_SSL):

def login(*args):
s,r = imaplib.IMAP4_SSL.login(*args)
if s!='OK':
raise NotOK((s,r))
return r

def list(*args):
s,r = imaplib.IMAP4_SSL.list(*args)
if s!='OK':
raise NotOK((s,r))
return r

def search(*args):
s,r = imaplib.IMAP4_SSL.search(*args)
if s!='OK':
raise NotOK((s,r))
return r

[and so on for another dozen methods]

A more tested version of my other email:

py> class C(object):
.... def f(self, *args):
.... print "f:", args
.... def g(self, *args):
.... print "g:", args
....
py> class D(C):
.... pass
....
py> class Wrapper(object):
.... def __init__(self, func):
.... self.func = func
.... def __call__(self, *args):
.... print "wrapped"
.... return self.func(*args)
....
py> for name in ['f', 'g']:
.... wrapper = Wrapper(getattr(C, name))
.... setattr(D, name, new.instancemethod(wrapper, None, D))
....
py> C().f()
f: ()
py> C().g(1)
g: (1,)
py> D().f()
wrapped
f: ()
py> D().g(1)
wrapped
g: (1,)
 
G

Grant Edwards

py> class C(object):
... def f(self, *args):
... print "f:", args
... def g(self, *args):
... print "g:", args
...
py> class D(C):
... pass
...
py> class Wrapper(object):
... def __init__(self, func):
... self.func = func
... def __call__(self, *args):
... print "wrapped"
... return self.func(*args)
...
py> for name in ['f', 'g']:
... wrapper = Wrapper(getattr(C, name))
... setattr(D, name, new.instancemethod(wrapper, None, D))

Thanks. The stuff provided by the "new" module is what I was
missing.
 
M

Mathias Waack

Grant said:
I want to subclass an IMAP connection so that most of the
methods raise an exception if the returned status isn't 'OK'.
This works, but there's got to be a way to do it that doesn't
involve so much duplication:

class MyImap4_ssl(imaplib.IMAP4_SSL):

def login(*args):
s,r = imaplib.IMAP4_SSL.login(*args)
if s!='OK':
raise NotOK((s,r))
return r

def list(*args):
s,r = imaplib.IMAP4_SSL.list(*args)
if s!='OK':
raise NotOK((s,r))
return r

def search(*args):
s,r = imaplib.IMAP4_SSL.search(*args)
if s!='OK':
raise NotOK((s,r))
return r

[and so on for another dozen methods]

How about using a delegator:

class Wrapper:
funcs = ("login", "list", "search")
def __init__(self, classobj):
self.__wrapped = classobj()
def __getattr__(self, attr):
if attr in Wrapper.funcs:
def f(*args):
f1 = getattr(self.__wrapped, attr)
s,r = f1(args)
if s != 'OK': raise NotOk((s,r))
return r
return f
# raise some exception here
imap = Wrapper(imaplib.IMAP4_SSL)

If you wrap all methods you can ignore the if-test. Instead of the
class object you can pass instances to the wrapper if you need
special arguments for initialization.

I don't like subclassing;)

Mathias

PS: note that we're wrapping the instance's methods, not the class's
methods!
 
J

Jeff Shannon

Steven said:
Grant said:
I want to subclass an IMAP connection so that most of the
methods raise an exception if the returned status isn't 'OK'.
This works, but there's got to be a way to do it that doesn't
involve so much duplication:

class MyImap4_ssl(imaplib.IMAP4_SSL):

def login(*args):
s,r = imaplib.IMAP4_SSL.login(*args)
if s!='OK':
raise NotOK((s,r))
return r
def list(*args):
s,r = imaplib.IMAP4_SSL.list(*args)
if s!='OK':
raise NotOK((s,r))
return r
def search(*args):
s,r = imaplib.IMAP4_SSL.search(*args)
if s!='OK':
raise NotOK((s,r))
return r
[and so on for another dozen methods]


You could try something like (Untested!):

class Wrapper(object):
def __init__(self, func):
self.func = func
def __call__(*args, **kwargs):
self, args = args[0], args[1:]
s, r = self.func(*args)
if s != 'OK':
raise NotOK((s, r))
return r

for func_name in ['login', 'list', 'search']:
func = Wrapper(getattr(imaplib.IMAP4_SSL, func_name))
setattr(imaplib.IMAP4_SSL, func_name, func)

You could probably also do this as a factory function, rather than as
a class (also untested!):

def Wrapper(func):
def wrapped(self, *args, **kwargs):
s, r = func(self, *args, **kwargs)
if s != 'OK':
raise NotOK((s,r))
return r
return wrapped

I believe that this will be semantically almost equivalent, but
conceptually slightly simpler.

Jeff Shannon
 
M

Michael Spencer

Grant said:
py> class C(object):
... def f(self, *args):
... print "f:", args
... def g(self, *args):
... print "g:", args
...
py> class D(C):
... pass
...
py> class Wrapper(object):
... def __init__(self, func):
... self.func = func
... def __call__(self, *args):
... print "wrapped"
... return self.func(*args)
...
py> for name in ['f', 'g']:
... wrapper = Wrapper(getattr(C, name))
... setattr(D, name, new.instancemethod(wrapper, None, D))


Thanks. The stuff provided by the "new" module is what I was
missing.
No magic in the 'new' module - new.instancemethod is just a synonym for the
method type:
True

Michael
 
G

Grant Edwards

How about using a delegator:

class Wrapper:
funcs = ("login", "list", "search")
def __init__(self, classobj):
self.__wrapped = classobj()
def __getattr__(self, attr):
if attr in Wrapper.funcs:
def f(*args):
f1 = getattr(self.__wrapped, attr)
s,r = f1(args)
if s != 'OK': raise NotOk((s,r))
return r
return f
# raise some exception here
imap = Wrapper(imaplib.IMAP4_SSL)

If you wrap all methods you can ignore the if-test.

I'm not, and the other methods should behave "normally":

class Wrapper:
funcs = ("login", "list", "search")
def __init__(self, classobj):
self.__wrapped = classobj()
def __getattr__(self, attr):
if attr in Wrapper.funcs:
def f(*args):
f1 = getattr(self.__wrapped, attr)
s,r = f1(args)
if s != 'OK': raise NotOk((s,r))
return r
else:
f = getattr(self.__wrapped, attr)
return f
PS: note that we're wrapping the instance's methods, not the class's
methods!

You're right.
 
M

Michael Spencer

John said:
On Thu, Feb 17, 2005 at 07:32:55PM +0000, Grant Edwards wrote:


I'd usually put big fat warnings around this code, and explain exaclty
why I need to do things this way...

As a low-tech alternative, what about sourcecode generation, since you are
targetting a python module? This gives two advantages vs the wrapping function:
1) the magic all occurs at coding time 2) the method signatures are documented.

Michael

import imaplib
import inspect
import types
instancemethod = types.MethodType

# The function template
funcwrapper = \
"""
def %(name)s%(argspec)s:
s,r = imaplib.IMAP4_SSL.%(name)s%(callspec)s
if s!='OK':
raise NotOK((s,r))
return r"""

# A helper function to get the template parameters
def getargs(method):
argspec = inspect.getargspec(method)
callspec = tuple(argspec[:3] + (None,))# No default

return {"name": method.__name__,
"argspec": inspect.formatargspec(*argspec),
"callspec": inspect.formatargspec(*callspec)}

# Do the stuff manually:
>>> obj = imaplib.IMAP4_SSL
>>> attrnames = [meth for meth in dir(imaplib.IMAP4_SSL) if not meth.startswith("_")]
>>> attributes = [getattr(obj, attrname) for attrname in attrnames]
>>> methods = [attribute for attribute in attributes if inspect.ismethod(attribute)]
>>> print "\n".join(funcwrapper % getargs(method) for method in methods)

def append(self, mailbox, flags, date_time, message):
s,r = imaplib.IMAP4_SSL.append(self, mailbox, flags, date_time, message)
if s!='OK':
raise NotOK((s,r))
return r

def authenticate(self, mechanism, authobject):
s,r = imaplib.IMAP4_SSL.authenticate(self, mechanism, authobject)
if s!='OK':
raise NotOK((s,r))
return r

def check(self):
s,r = imaplib.IMAP4_SSL.check(self)
if s!='OK':
raise NotOK((s,r))
return r

def close(self):
s,r = imaplib.IMAP4_SSL.close(self)
if s!='OK':
raise NotOK((s,r))
return r

def copy(self, message_set, new_mailbox):
s,r = imaplib.IMAP4_SSL.copy(self, message_set, new_mailbox)
if s!='OK':
raise NotOK((s,r))
return r

def create(self, mailbox):
s,r = imaplib.IMAP4_SSL.create(self, mailbox)
if s!='OK':
raise NotOK((s,r))
return r

def delete(self, mailbox):
s,r = imaplib.IMAP4_SSL.delete(self, mailbox)
if s!='OK':
raise NotOK((s,r))
return r

def deleteacl(self, mailbox, who):
s,r = imaplib.IMAP4_SSL.deleteacl(self, mailbox, who)
if s!='OK':
raise NotOK((s,r))
return r

def expunge(self):
s,r = imaplib.IMAP4_SSL.expunge(self)
if s!='OK':
raise NotOK((s,r))
return r

def fetch(self, message_set, message_parts):
s,r = imaplib.IMAP4_SSL.fetch(self, message_set, message_parts)
if s!='OK':
raise NotOK((s,r))
return r

def getacl(self, mailbox):
s,r = imaplib.IMAP4_SSL.getacl(self, mailbox)
if s!='OK':
raise NotOK((s,r))
return r

def getquota(self, root):
s,r = imaplib.IMAP4_SSL.getquota(self, root)
if s!='OK':
raise NotOK((s,r))
return r

def getquotaroot(self, mailbox):
s,r = imaplib.IMAP4_SSL.getquotaroot(self, mailbox)
if s!='OK':
raise NotOK((s,r))
return r

def list(self, directory='""', pattern='*'):
s,r = imaplib.IMAP4_SSL.list(self, directory, pattern)
if s!='OK':
raise NotOK((s,r))
return r

def login(self, user, password):
s,r = imaplib.IMAP4_SSL.login(self, user, password)
if s!='OK':
raise NotOK((s,r))
return r

def login_cram_md5(self, user, password):
s,r = imaplib.IMAP4_SSL.login_cram_md5(self, user, password)
if s!='OK':
raise NotOK((s,r))
return r

def logout(self):
s,r = imaplib.IMAP4_SSL.logout(self)
if s!='OK':
raise NotOK((s,r))
return r

def lsub(self, directory='""', pattern='*'):
s,r = imaplib.IMAP4_SSL.lsub(self, directory, pattern)
if s!='OK':
raise NotOK((s,r))
return r

def myrights(self, mailbox):
s,r = imaplib.IMAP4_SSL.myrights(self, mailbox)
if s!='OK':
raise NotOK((s,r))
return r

def namespace(self):
s,r = imaplib.IMAP4_SSL.namespace(self)
if s!='OK':
raise NotOK((s,r))
return r

def noop(self):
s,r = imaplib.IMAP4_SSL.noop(self)
if s!='OK':
raise NotOK((s,r))
return r

def open(self, host='', port=993):
s,r = imaplib.IMAP4_SSL.open(self, host, port)
if s!='OK':
raise NotOK((s,r))
return r

def partial(self, message_num, message_part, start, length):
s,r = imaplib.IMAP4_SSL.partial(self, message_num, message_part, start,
length)
if s!='OK':
raise NotOK((s,r))
return r

def print_log(self):
s,r = imaplib.IMAP4_SSL.print_log(self)
if s!='OK':
raise NotOK((s,r))
return r

def proxyauth(self, user):
s,r = imaplib.IMAP4_SSL.proxyauth(self, user)
if s!='OK':
raise NotOK((s,r))
return r

def read(self, size):
s,r = imaplib.IMAP4_SSL.read(self, size)
if s!='OK':
raise NotOK((s,r))
return r

def readline(self):
s,r = imaplib.IMAP4_SSL.readline(self)
if s!='OK':
raise NotOK((s,r))
return r

def recent(self):
s,r = imaplib.IMAP4_SSL.recent(self)
if s!='OK':
raise NotOK((s,r))
return r

def rename(self, oldmailbox, newmailbox):
s,r = imaplib.IMAP4_SSL.rename(self, oldmailbox, newmailbox)
if s!='OK':
raise NotOK((s,r))
return r

def response(self, code):
s,r = imaplib.IMAP4_SSL.response(self, code)
if s!='OK':
raise NotOK((s,r))
return r

def search(self, charset, *criteria):
s,r = imaplib.IMAP4_SSL.search(self, charset, *criteria)
if s!='OK':
raise NotOK((s,r))
return r

def select(self, mailbox='INBOX', readonly=None):
s,r = imaplib.IMAP4_SSL.select(self, mailbox, readonly)
if s!='OK':
raise NotOK((s,r))
return r

def send(self, data):
s,r = imaplib.IMAP4_SSL.send(self, data)
if s!='OK':
raise NotOK((s,r))
return r

def setacl(self, mailbox, who, what):
s,r = imaplib.IMAP4_SSL.setacl(self, mailbox, who, what)
if s!='OK':
raise NotOK((s,r))
return r

def setquota(self, root, limits):
s,r = imaplib.IMAP4_SSL.setquota(self, root, limits)
if s!='OK':
raise NotOK((s,r))
return r

def shutdown(self):
s,r = imaplib.IMAP4_SSL.shutdown(self)
if s!='OK':
raise NotOK((s,r))
return r

def socket(self):
s,r = imaplib.IMAP4_SSL.socket(self)
if s!='OK':
raise NotOK((s,r))
return r

def sort(self, sort_criteria, charset, *search_criteria):
s,r = imaplib.IMAP4_SSL.sort(self, sort_criteria, charset,
*search_criteria)
if s!='OK':
raise NotOK((s,r))
return r

def ssl(self):
s,r = imaplib.IMAP4_SSL.ssl(self)
if s!='OK':
raise NotOK((s,r))
return r

def status(self, mailbox, names):
s,r = imaplib.IMAP4_SSL.status(self, mailbox, names)
if s!='OK':
raise NotOK((s,r))
return r

def store(self, message_set, command, flags):
s,r = imaplib.IMAP4_SSL.store(self, message_set, command, flags)
if s!='OK':
raise NotOK((s,r))
return r

def subscribe(self, mailbox):
s,r = imaplib.IMAP4_SSL.subscribe(self, mailbox)
if s!='OK':
raise NotOK((s,r))
return r

def thread(self, threading_algorithm, charset, *search_criteria):
s,r = imaplib.IMAP4_SSL.thread(self, threading_algorithm, charset,
*search_criteria)
if s!='OK':
raise NotOK((s,r))
return r

def uid(self, command, *args):
s,r = imaplib.IMAP4_SSL.uid(self, command, *args)
if s!='OK':
raise NotOK((s,r))
return r

def unsubscribe(self, mailbox):
s,r = imaplib.IMAP4_SSL.unsubscribe(self, mailbox)
if s!='OK':
raise NotOK((s,r))
return r

def xatom(self, name, *args):
s,r = imaplib.IMAP4_SSL.xatom(self, name, *args)
if s!='OK':
raise NotOK((s,r))
return r
 
R

rbt

Jeff said:
You could probably also do this as a factory function, rather than as a
class (also untested!):

def Wrapper(func):
def wrapped(self, *args, **kwargs):
s, r = func(self, *args, **kwargs)
if s != 'OK':
raise NotOK((s,r))
return r
return wrapped

I believe that this will be semantically almost equivalent, but
conceptually slightly simpler.

Jeff Shannon

This is a nice example. I have used sub-functions (functions within
functions) recently with some code, but I've wondered how proper it is
to do this. Is this type of thing frowned upon?
 
S

Steven Bethard

rbt said:
This is a nice example. I have used sub-functions (functions within
functions) recently with some code, but I've wondered how proper it is
to do this. Is this type of thing frowned upon?

Nope. If it was frowned upon, Python wouldn't support it. ;) Sometimes
it's even faster:

-------------------- test.py --------------------
def wrapper(func):
def wrapped(*args, **kwargs):
return bool(func(*args, **kwargs))
return wrapped

class Wrapper(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
return bool(self.func(*args, **kwargs))
-------------------------------------------------

$ python -m timeit -s "import test; w = test.wrapper(sum)" "w([]); w([1,2])"
100000 loops, best of 3: 4.77 usec per loop

$ python -m timeit -s "import test; w = test.Wrapper(sum)" "w([]); w([1,2])"
100000 loops, best of 3: 6.52 usec per loop

Personally, I still tend towards the class-based version, but I'm sure
in many cases the nested function version would work just as well.

STeVe
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top