Как узнать арность метода в Python

Я хотел бы узнать арность метода в Python (количество параметров, которое он получает). Сейчас я делаю это:

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

Я бы хотел добиться этого:

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

Обновить: прямо сейчас эта функция не работает со встроенными типами, любая помощь по этому поводу также будет оценена:

 # 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

Ответ 1

Модуль inspect из стандартной библиотеки Python - ваш друг - см. онлайн-документы! inspect.getargspec(func) возвращает кортеж с четырьмя элементами, args, varargs, varkw, defaults: len(args) - это "первичная арность", но может быть что-то от этого до бесконечности, если у вас есть varargs и/или varkw not None, и некоторые аргументы могут быть опущены (и по умолчанию), если defaults не None. Как вы превращаете это в одно число, бьется меня, но, по-видимому, у вас есть свои идеи в этом вопросе! -)

Это относится к Python-закодированным функциям, но не к C-кодированным. Ничто в API Python C не позволяет C-кодированным функциям (включая встроенные) подвергать свою подпись для интроспекции, кроме как через их docstring (или необязательно через аннотации в Python 3); поэтому вам нужно будет вернуться к синтаксическому анализу docstring как последнему канаву, если другие подходы не сработают (конечно, докстрина также может отсутствовать, и в этом случае функция останется загадкой).

Ответ 2

Используйте декоратор для украшения методов, например.

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()

Теперь реализуем функцию _arity для вычисления количества аргументов на основе ваших потребностей.

Ответ 3

В идеале вы хотите обезвредить функцию arity как метод на функторах Python. Вот как:

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

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

Но CPython реализует функторы в C, вы не можете их модифицировать. Однако это может работать в PyPy.

То, что все ваши функции arity() верны. Как насчет вариационных функций? Вы даже хотите получить ответ?

Ответ 4

Это единственный способ, которым я могу думать, что он должен быть на 100% эффективным (по крайней мере, в зависимости от того, определена ли функция пользователем или записана на C) при определении функции (минимальной). Однако вы должны быть уверены, что эта функция не вызовет каких-либо побочных эффектов и что она не будет бросать 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

Как вы можете видеть, это определит минимальную arity, если функция принимает переменное количество аргументов. Он также не будет учитывать аргумент self или cls метода класса или экземпляра.

Чтобы быть абсолютно честным, я бы не использовал эту функцию в производственной среде, если бы не знал точно, какие функции будут называться, хотя есть много места для глупых ошибок. Это может победить цель.

Ответ 5

вот еще одна попытка использования метакласса, поскольку я использую python 2.5, но с 2.6 вы можете легко украсить класс

metaclass также может быть определен на уровне модуля, поэтому он работает для всех классов

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()