Как узнать арность метода в 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()