views:

593

answers:

5

I'd like to find out the arity of a method in Python (the number of parameters that it receives). Right now I'm doing this:

def arity(obj, method):
  return getattr(obj.__class__, method).func_code.co_argcount - 1 # remove self

class Foo:
  def bar(self, bla):
    pass

arity(Foo(), "bar")   # => 1

I'd like to be able to achieve this:

Foo().bar.arity()   # => 1

Update: Right now the above function fails with built-in types, any help on this would also be appreciated:

 # Traceback (most recent call last):
 #   File "bla.py", line 10, in <module>
 #     print arity('foo', 'split')  # =>
 #   File "bla.py", line 3, in arity
 #     return getattr(obj.__class__, method).func_code.co_argcount - 1 # remove self
 # AttributeError: 'method_descriptor' object has no attribute 'func_co
+16  A: 

Module inspect from Python's standard library is your friend -- see the online docs! inspect.getargspec(func) returns a tuple with four items, args, varargs, varkw, defaults: len(args) is the "primary arity", but arity can be anything from that to infinity if you have varargs and/or varkw not None, and some arguments may be omitted (and defaulted) if defaults is not None. How you turn that into a single number, beats me, but presumably you have your ideas in the matter!-)

This applies to Python-coded functions, but not to C-coded ones. Nothing in the Python C API lets C-coded functions (including built-ins) expose their signature for introspection, except via their docstring (or optionally via annotations in Python 3); so, you will need to fall back to docstring parsing as a last ditch if other approaches fail (of course, the docstring might be missing too, in which case the function will remain a mystery).

Alex Martelli
This works for my methods I define but not for built-ins, see inspect.getargspec(str.split)
Federico Builes
@Federico, you're correct: nothing in the Python C API lets C-coded functions (which built-ins are) expose their signature for introspection, except via their docstring (or optionally via __annotations__ in Python 3); so, "feel like it" or not, you will need to fall back to docstring parsing as a last ditch if other approaches fail (of course the docstring might be missing too).
Alex Martelli
editing my answer to add this clarification.
Alex Martelli
+3  A: 

you can use a decorator to decorate methods e.g.

def arity(method):

  def _arity():
    return method.func_code.co_argcount - 1 # remove self

  method.arity = _arity

  return method

class Foo:
  @arity
  def bar(self, bla):
    pass

print Foo().bar.arity()

As alex said now its upto you how you code _arity function to calculate arg count based on your needs

Anurag Uniyal
actually you can use metaclass to decorate all methods of a class instead of manually doing it
Anurag Uniyal
+1  A: 

Ideally, you'd want to monkey-patch the arity function as a method on Python functors. Here's how:

def arity(self, method):
    return getattr(self.__class__, method).func_code.co_argcount - 1

functor = arity.__class__
functor.arity = arity
arity.__class__.arity = arity

But, CPython implements functors in C, you can't actually modify them. This may work in PyPy, though.

That's all assuming your arity() function is correct. What about variadic functions? Do you even want an answer then?

Nikhil Chelliah
A: 

here is another attempt using metaclass, as i use python 2.5, but with 2.6 you could easily decorate the class

metaclass can also be defined at module level, so it works for all classes

from types import FunctionType

def arity(unboundmethod):
    def _arity():
        return unboundmethod.func_code.co_argcount - 1 # remove self
    unboundmethod.arity = _arity

    return unboundmethod

class AirtyMetaclass(type):
    def __new__(meta, name, bases, attrs):
        newAttrs = {}
        for attributeName, attribute in attrs.items():
            if type(attribute) == FunctionType:
                attribute = arity(attribute)

            newAttrs[attributeName] = attribute

        klass = type.__new__(meta, name, bases, newAttrs)

        return klass

class Foo:
    __metaclass__ = AirtyMetaclass
    def bar(self, bla):
        pass

print Foo().bar.arity()
Anurag Uniyal
+2  A: 

This is the only way that I can think of that should be 100% effective (at least with regard to whether the function is user-defined or written in C) at determining a function's (minimum) arity. However, you should be sure that this function won't cause any side-effects and that it won't throw a TypeError:

from functools import partial

def arity(func):
    pfunc = func
    i = 0
    while True:
     try:
      pfunc()
     except TypeError:
      pfunc = partial(pfunc, '')
      i += 1
     else:
      return i

def foo(x, y, z):
    pass

def varfoo(*args):
    pass

class klass(object):
    def klassfoo(self):
     pass

print arity(foo)
print arity(varfoo)

x = klass()
print arity(x.klassfoo)

# output
# 3
# 0
# 0

As you can see, this will determine the minimum arity if a function takes a variable amount of arguments. It also won't take into account the self or cls argument of a class or instance method.

To be totally honest though, I wouldn't use this function in a production environment unless I knew exactly which functions would be called though as there is a lot of room for stupid errors. This may defeat the purpose.

Jason Baker