Checking function calls

James Stroud jstroud at ucla.edu
Wed Mar 8 23:40:27 CET 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