Checking function calls

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Fredrik Tolf

    #1

    Checking function calls

    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


  • Ravi Teja

    #2
    Re: Checking function calls

    You can match if the list contains the legal number of arguments with.
    func_variable.f unc_code.co_arg count

    Type checking arguments has to be done manually since Python is a
    dynamic language. Perhaps, you could try some typechecking decorators.
    http://www.ilowe.net/software/typecheck/[color=blue]
    >From the docs, this one raises TypeCheckError (not TypeError), so you[/color]
    should do fine.

    Comment

    • James Stroud

      #3
      Re: Checking function calls

      Fredrik Tolf wrote:[color=blue]
      > 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
      >
      >[/color]

      Since python is "weakly typed", you will not be able to check what
      "type" of arguments a function expects. TypeErrors relating to the type
      of argemtns you pass will be raised inside the function only and not
      when it is called. I.e. there is no type checking when a function is
      called, only in its body.

      Types erros can be raised when calling a function like this:

      func(*avar)

      or

      func(**avar)

      In the former case, if avar is not a sequence, you will get a TypeError.
      In the latter case, if avar is not a dictionary, you will get a TypeError.

      In this latter case, if your dictionary has keys that are not in the
      parameter list, you will get a TypeError.

      This would be similar to explicitly specifying a keyword argument that
      is not in the parameter list (e.g. func(aparam=som eval)).

      TypeError will be raised also if you give an improper number of
      arguments to the 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

      To check the parameter names, use

      py> func.func_code. co_names
      ('a', 'b', 'c')


      James

      --
      James Stroud
      UCLA-DOE Institute for Genomics and Proteomics
      Box 951570
      Los Angeles, CA 90095


      Comment

      • bruno at modulix

        #4
        Re: Checking function calls

        James Stroud wrote:
        (snip)
        [color=blue]
        > Since python is "weakly typed",[/color]

        s/weakly/dynamically/

        (snip)

        --
        bruno desthuilliers
        python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
        p in 'onurb@xiludom. gro'.split('@')])"

        Comment

        • Roy Smith

          #5
          Re: Checking function calls

          Fredrik Tolf <python-list@python.org > wrote:[color=blue]
          >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?[/color]

          The only way is to read the documentation for the function (or, the
          source code).

          Can you be a little more specific about what you're trying to do? Can
          you post your code?

          Comment

          • gene tani

            #6
            Re: Checking function calls


            Roy Smith wrote:[color=blue]
            > Fredrik Tolf <python-list@python.org > wrote:[color=green]
            > >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?[/color]
            >
            > The only way is to read the documentation for the function (or, the
            > source code).
            >
            > Can you be a little more specific about what you're trying to do? Can
            > you post your code?[/color]

            if you know ahead of runtime which methods are at issue and what method
            signatures you want, multimethod decorators, maybe?

            (read comments: PJE, ian bicking

            or you can arg-test inside the method, which isn't much fun


            Comment

            • Joel Hedlund

              #7
              Re: Checking function calls

              If you have access to the source of the function you want to call (and know what kind of data types it wants in its args) you can raise something else for bad parameters, eg:

              ----------------------------------------------------------------
              class ArgTypeError(Ty peError):
              def __init__(self, arg):
              self.arg = arg

              def __str__(self):
              return "bad type for argument %r" % self.arg

              def moo(cow):
              if not isinstance(cow, str):
              raise ArgTypeError('c ow')
              print "%s says moo!" % cow
              # this error is not caused by wrong arg type:
              var = 1 + 's'

              function = moo

              for arg in [1, 'rose']:
              try:
              function(arg)
              except ArgTypeError, e:
              print e
              ----------------------------------------------------------------

              Output:
              ----------------------------------------------------------------
              bad type for argument 'cow'
              rose says moo!

              Traceback (most recent call last):
              File "/merlot1/yohell/eraseme/test.py", line 23, in -toplevel-
              make_noise('ros e')
              File "/merlot1/yohell/eraseme/test.py", line 13, in moo
              raise TypeError
              TypeError
              ----------------------------------------------------------------

              Hope it helps
              /Joel Hedlund

              Comment

              • Fredrik Tolf

                #8
                Re: Checking function calls

                On Mon, 2006-03-06 at 20:25 -0800, James Stroud wrote:[color=blue]
                > Fredrik Tolf wrote:[color=green]
                > > 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
                > >
                > >[/color]
                >
                > Since python is "weakly typed", you will not be able to check what
                > "type" of arguments a function expects. [...][/color]

                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.
                [color=blue]
                > 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[/color]

                Not very well, though:[color=blue][color=green][color=darkred]
                >>> def a(b, c, *d): pass[/color][/color][/color]
                ....[color=blue][color=green][color=darkred]
                >>> print a.func_code.co_ argcount[/color][/color][/color]
                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(65 536)
                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")
                # ...

                However, this results in bugs in the server code that would cause
                TypeError to reply to the client that it had the wrong number of args,
                and I can't see what the real cause is without turning off that check
                while debugging. Therefore, I'd like to check in advance whether cv[1:]
                would raise a TypeError upon calling f with it or not. And of course,
                I'd like to be able to use cmd_* functions with variable arglists as
                well.

                Surely, there ought to be some way in python to see whether a certain
                argument list would be valid for a function call, without checking magic
                values of f.func_code?

                Fredrik Tolf


                Comment

                • Peter Otten

                  #9
                  Re: Checking function calls

                  Fredrik Tolf wrote:
                  [color=blue]
                  > # ...
                  > if callable(f):
                  >     try:
                  >         f(*cv[1:])
                  >     except TypeError:
                  >         self.reply("err ", "argno")
                  > # ...
                  >
                  > However, this results in bugs in the server code that would cause
                  > TypeError to reply to the client that it had the wrong number of args,
                  > and I can't see what the real cause is without turning off that check
                  > while debugging. Therefore, I'd like to check in advance whether cv[1:]
                  > would raise a TypeError upon calling f with it or not. And of course,
                  > I'd like to be able to use cmd_* functions with variable arglists as
                  > well.[/color]

                  If you must, just check the error message, not just the type of exception.

                  I'd just drop the cryptical error codes like 'argno' and 'unk' and pass on
                  the actual error message:

                  try:
                  f(*cv[1:])
                  except:
                  tp, exc, traceback = sys.exc_info()
                  self.reply("err ", str(exc))

                  Note that the check whether f is callable is also gone.

                  Peter

                  Comment

                  • James Stroud

                    #10
                    Re: Checking function calls

                    Fredrik Tolf wrote:[color=blue]
                    > On Mon, 2006-03-06 at 20:25 -0800, James Stroud wrote:
                    >[color=green]
                    >>Fredrik Tolf wrote:
                    >>[color=darkred]
                    >>>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
                    >>>
                    >>>[/color]
                    >>
                    >>Since python is "weakly typed", you will not be able to check what
                    >>"type" of arguments a function expects. [...][/color]
                    >
                    >
                    > 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.
                    >
                    >[color=green]
                    >>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[/color]
                    >
                    >
                    > Not very well, though:
                    >[color=green][color=darkred]
                    >>>>def a(b, c, *d): pass[/color][/color]
                    >
                    > ...
                    >[color=green][color=darkred]
                    >>>>print a.func_code.co_ argcount[/color][/color]
                    >
                    > 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(65 536)
                    > 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")
                    > # ...[/color]


                    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(exc eption): 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


                    Comment

                    • James Stroud

                      #11
                      Re: Checking function calls

                      Fredrik Tolf wrote:[color=blue]
                      > On Mon, 2006-03-06 at 20:25 -0800, James Stroud wrote:
                      >[color=green]
                      >>Fredrik Tolf wrote:
                      >>[color=darkred]
                      >>>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
                      >>>
                      >>>[/color]
                      >>
                      >>Since python is "weakly typed", you will not be able to check what
                      >>"type" of arguments a function expects. [...][/color]
                      >
                      >
                      > 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.
                      >
                      >[color=green]
                      >>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[/color]
                      >
                      >
                      > Not very well, though:
                      >[color=green][color=darkred]
                      >>>>def a(b, c, *d): pass[/color][/color]
                      >
                      > ...
                      >[color=green][color=darkred]
                      >>>>print a.func_code.co_ argcount[/color][/color]
                      >
                      > 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.
                      >[/color]

                      What is your goal? To avoid TypeError? In that case the minimum for no
                      error is 2, as *d could be empty. It would then be safe to check against
                      co_argcount to avoid errors:

                      py> def a(a,b,*c):
                      .... print a,b
                      ....
                      py> a(*range(a.func _code.co_argcou nt))
                      0 1
                      py> a(*range(a.func _code.co_argcou nt + 5))
                      0 1
                      py> a(*range(a.func _code.co_argcou nt - 1))
                      Traceback (most recent call last):
                      File "<stdin>", line 1, in ?
                      TypeError: a() takes at least 2 arguments (1 given)

                      If the function requires 3 arguments to work, you may want to change the
                      definition to reflect that fact as the *args are implicitly optional.
                      This will help with using co_argcount as a test.


                      James


                      --
                      James Stroud
                      UCLA-DOE Institute for Genomics and Proteomics
                      Box 951570
                      Los Angeles, CA 90095


                      Comment

                      Working...