Checking function calls
James Stroud
jstroud at ucla.edu
Wed Mar 8 17:40:27 EST 2006
Fredrik Tolf wrote:
> On Mon, 2006-03-06 at 20:25 -0800, James Stroud wrote:
>
>>Fredrik Tolf wrote:
>>
>>>If I have a variable which points to a function, can I check if certain
>>>argument list matches what the function wants before or when calling it?
>>>
>>>Currently, I'm trying to catch a TypeError when calling the function
>>>(since that is what is raised when trying to call it with an illegal
>>>list), but that has the rather undesirable side effect of also catching
>>>any TypeErrors raised inside the function. Is there a way to avoid that?
>>>
>>>Fredrik Tolf
>>>
>>>
>>
>>Since python is "weakly typed", you will not be able to check what
>>"type" of arguments a function expects. [...]
>
>
> Sorry, it seems I made myself misunderstood. I don't intend to check the
> type of the values that I pass to a function (I'm well aware that Python
> is dynamically typed). The reason I refer to TypeError is because that
> seems to be the exception raised by Python when I attempt to call a
> function with an argument list that wouldn't be valid for it (for
> example due to the number of parameters being wrong). I just want to
> check in advance whether a certain arglist would be valid to call a
> certain function.
>
>
>>So, if you want to know the number of
>>arguments expected, I've found this works:
>>
>>py> def func(a,b,c):
>>... print a,b,c
>>...
>>py> func.func_code.co_argcount
>>3
>
>
> Not very well, though:
>
>>>>def a(b, c, *d): pass
>
> ...
>
>>>>print a.func_code.co_argcount
>
> 2
>
> Here, that would indicate that I could only call `a' with two arguments,
> while in fact I could call it with two or more arguments.
>
> More exactly, what I'm trying to do is this; I writing a small protocol
> server class as part of a program, and I thought it would be convenient
> to be able to define new commands in the protocol by just adding a
> function for them, like this:
>
> class client:
> # ...
> # Read is called from the event driver when data is available on the
> # socket.
> def read(self):
> self.buf += self.sk.recv(65536)
> for c in self.buf.split("\n")[:-1]:
> cv = tokenize(c)
> if len(cv) > 0:
> f = getattr(self, "cmd_" + cv[0], None)
> if callable(f):
> f(*cv[1:])
> else:
> self.reply("err", "unk")
> # Example:
> def cmd_echo(self, arg1):
> self.reply("ok", arg1)
>
> So basically, I want to be able to handle to client giving the wrong
> number of arguments and reply with an error rather than the server
> process crashing and dying. Therefore, I'm currently catching TypeErrors
> when calling the function:
>
> # ...
> if callable(f):
> try:
> f(*cv[1:])
> except TypeError:
> self.reply("err", "argno")
> # ...
It might be more reasonable to program your cmd_* functions to be more
flexible themselves. This would be more pythonic. E.g.
def cmd_echo(self, *args):
if not len(args):
answer = 'OMITTED' # or raise your own exception
else:
answer = args[0]
self.reply("ok", answer)
Now, you never have to worry about length of the argument list.
You could, of course factor this behavior. Here's how I might do it:
# first make a custom exception class, called whatever you want
class ServerError(exception): pass
#... somewhere in your client class
def preprocess_args(self, args, numexpected):
"""
This is the more flexible way.
A less flexible way would to check for the exact number of
arguments. But that goes against the "lenient in what you accept"
maxim.
"""
if len(args) < numexpected:
raise ServerError, 'Too few args!'
else:
return args[:numexpected]
def cmd_echo(self, *args):
(answer,) = preprocess_args(args, 1) # preprocess returns tuple
self.reply("ok", answer)
#... elsewhere in your client class
if callable(f):
try:
f(*cv[1:])
except ServerError, e:
self.reply("err", "argno") # can also make use of exception e
You could factor even further with a dictionary:
minargs = {'echo' : 1, 'etc' : 5000}
#... somewhere in your client class
def preprocess_args(self, cv):
"""
This is the more flexible way.
A less flexible way would to check for the exact number of
arguments. But that goes against the "lenient in what you accept"
maxim.
"""
args = cv[1:]
expected = minargs[cv[0]]
if len(args) < expected:
raise ServerError, 'Too few args!'
else:
return args[:expected]
# ...
def cmd_echo(self, answer):
self.reply("ok", answer)
# ... elsewhere
if callable(f):
try:
args = preprocess_args(cv)
except ServerError, e:
self.reply("err", "argno") # can also make use of exception e
f(*args)
Now you shouldn't have to rewrite any cmd_* functions.
James
--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095
http://www.jamesstroud.com/
More information about the Python-list
mailing list