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/ -- http://mail.python.org/mailman/listinfo/python-list