Подтвердить что ты не робот

Установить подпись функции в Python

Предположим, что у меня есть общая функция f. Я хочу программно создать функцию f2, которая ведет себя так же, как f, но имеет настраиваемую подпись.

Подробнее

Учитывая список l и и словарь d, я хочу иметь возможность:

  • Задайте аргументы без ключевого слова f2 для строк в l
  • Задайте аргументы ключевого слова f2 ключам в d и значениям по умолчанию значениям d

т. Предположим, что

l=["x", "y"]
d={"opt":None}

def f(*args, **kwargs):
    #My code

Тогда мне понадобится функция с сигнатурой:

def f2(x, y, opt=None):
    #My code

Конкретный вариант использования

Это просто упрощенная версия моего конкретного варианта использования. Я приводил это только как пример.

Мой фактический прецедент (упрощенный) выглядит следующим образом. У нас есть общая инициативная функция:

def generic_init(self,*args,**kwargs):
    """Function to initiate a generic object"""
    for name, arg in zip(self.__init_args__,args):
        setattr(self, name, arg)
    for name, default in self.__init_kw_args__.items():
        if name in kwargs:
            setattr(self, name, kwargs[name])
        else:
            setattr(self, name, default)

Мы хотим использовать эту функцию в нескольких классах. В частности, мы хотим создать функцию init, которая ведет себя как generic_init, но имеет подпись, определенную некоторыми переменными класса, в время создания:

class my_class:
    __init_args__=["x", "y"]
    __kw_init_args__={"my_opt": None}

__init__=create_initiation_function(my_class, generic_init)
setattr(myclass, "__init__", __init__)

Мы хотим, чтобы create_initiation_function создала новую функцию с сигнатурой, определяемой с помощью init_args и kw_init_args. Можно ли написать create_initiation_function?

Обратите внимание:

  • Если бы я просто хотел улучшить помощь, я мог бы установить doc.
  • Мы хотим установить подпись функции при создании. После этого его не нужно менять.
  • Вместо создания такой функции, как generic_init, но с другой подписью, мы могли бы создать новую функцию с нужной сигнатурой, которая просто вызывает generic_init
  • Мы хотим определить create_initiation_function. Мы не хотим вручную указывать новую функцию!

Связанные

4b9b3361

Ответ 1

Для вашего использования, когда должна работать docstring в классе/функции, которая будет отображаться в help() в порядке и может быть установлена ​​программно (func.__ doc__ = "stuff" ).

Я не вижу способа установить действительную подпись. Я бы подумал, что functools module выполнил бы это, если бы это выполнимо, но это не так, по крайней мере, в py2.5 и py2. 6.

Вы также можете создать исключение TypeError, если вы получаете плохой ввод.

Хм, если вы не против быть по-настоящему мерзким, вы можете использовать compile()/eval() для этого. Если ваша желаемая подпись указана arglist = [ "foo", "bar", "baz" ], а ваша фактическая функция - f (* args, ** kwargs), вы можете управлять:

argstr = ", ".join(arglist)
fakefunc = "def func(%s):\n    return real_func(%s)\n" % (argstr, argstr)
fakefunc_code = compile(fakefunc, "fakesource", "exec")
fakeglobals = {}
eval(fakefunc_code, {"real_func": f}, fakeglobals)
f_with_good_sig = fakeglobals["func"]

help(f)               # f(*args, **kwargs)
help(f_with_good_sig) # func(foo, bar, baz)

Изменение docstring и func_name должно дать вам полное решение. Но, э-э...

Ответ 2

Из PEP-0362 действительно существует способ установить подпись в py3.3 +, используя fn.__signature__ атрибут:

def shared_vars(*shared_args):
    """Decorator factory that defines shared variables that are
       passed to every invocation of the function"""

    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            full_args = shared_args + args
            return f(*full_args, **kwargs)

        # Override signature
        sig = signature(f)
        sig = sig.replace(parameters=tuple(sig.parameters.values())[1:])
        wrapper.__signature__ = sig

        return wrapper
    return decorator

Тогда:

>>> @shared_vars({"myvar": "myval"})
>>> def example(_state, a, b, c):
>>>     return _state, a, b, c
>>> example(1,2,3)
({'myvar': 'myval'}, 1, 2, 3)
>>> str(signature(example))
'(a, b, c)'

Примечание: PEP не совсем прав; Signature.replace переместил params из позиционного arg в kw-only arg.

Ответ 3

Мы хотим, чтобы create_initiation_function менял подпись

Пожалуйста, не делайте этого.

Мы хотим использовать эту функцию в нескольких классах

Используйте обычное наследование.

Нет значения, когда подпись "изменена" во время выполнения.

Вы создаете кошмар для обслуживания. Никто больше никогда не захочет понять, что вы делаете. Они просто уничтожат его и заменят его наследованием.

Сделайте это вместо этого. Это просто и понятно и делает ваш общий init доступным во всех подклассах очевидным, простым, питоническим способом.

class Super( object ):
    def __init__( self, *args, **kwargs ):
        # the generic __init__ that we want every subclass to use

class SomeSubClass( Super ):
    def __init__( self, this, that, **kwdefaults ):
        super( SomeSubClass, self ).__init__( this, that, **kwdefaults )

class AnotherSubClass( Super ):
    def __init__( self, x, y, **kwdefaults ):
        super( AnotherSubClass, self ).__init__( x, y, **kwdefaults )

Ответ 4

Вы не можете сделать это с помощью живого кода.

То есть вам кажется, что вы хотите принять реальную, живую функцию, которая выглядит так:

def f(*args, **kwargs):
    print args[0]

и измените его на один так:

def f(a):
    print a

Причина этого не может быть выполнена - по крайней мере, без изменения фактического байт-кода Python - это потому, что они компилируются по-разному.

Первая приводит к функции, которая получает два параметра: список и dict, а код, который вы пишете, работает в этом списке и dict. Второй результат - в функции, которая получает один параметр и к которому напрямую обращаются как локальная переменная. Если вы изменили функцию "подпись", так сказать, это привело бы к следующей функции:

def f(a):
    print a[0]

который, очевидно, не сработает.

Если вам нужна более подробная информация (хотя это вам действительно не помогает), функция, которая принимает * args или * kwargs, имеет один или два бита, установленные в f.func_code.co_flags; вы можете изучить это сами. Функция, которая принимает регулярный параметр, имеет f.func_code.co_argcount, установленную в 1; версия * args равна 0. Это то, что использует Python, чтобы выяснить, как настроить фрейм стека функций при его вызове, проверить параметры и т.д.

Если вы хотите поиграть с изменением функции напрямую - только бы убедиться, что она не сработает - см. этот ответ о том, как создать объект кода и текущую функцию из существующего, чтобы изменить его биты. (Этот материал документирован где-то, но я не могу его найти, он нигде в документах модулей типов...)

Тем не менее, вы можете динамически изменять docstring функции. Просто назначьте func.__doc__. Обязательно выполняйте это только во время загрузки (из глобального контекста или, скорее всего, - декоратора); если вы это сделаете позже, инструменты, которые загружают модуль для проверки docstrings, никогда его не увидят.

Ответ 5

Возможно, я плохо разбирался в этой проблеме, но если это касается сохранения такого же поведения при изменении сигнатуры функции, вы можете сделать что-то вроде:

# define a function
def my_func(name, age) :
    print "I am %s and I am %s" % (name, age)

# label the function with a backup name
save_func = my_func

# rewrite the function with a different signature
def my_func(age, name) :
    # use the backup name to use the old function and keep the old behavior
    save_func(name, age)

# you can use the new signature
my_func(35, "Bob")

Выводится:

I am Bob and I am 35

Ответ 6

Изменить 1: ответ на новый вопрос:

Вы спрашиваете, как вы можете создать функцию с этой сигнатурой:

def fun(a, b, opt=None):
    pass

Правильный способ сделать это в Python таким образом:

def fun(a, b, opt=None):
    pass

Изменить 2: Ответ на вопрос:

"Предположим, что у меня есть общая функция F. Я хочу программно создать функцию f2, которая ведет себя так же, как f, но имеет настраиваемую подпись".

def f(*args, **kw):
    pass

ОК, тогда f2 выглядит так:

def f2(a, b, opt=None):
    f(a, b, opt=opt)

Опять же, ответ на ваш вопрос настолько тривиален, что вы, очевидно, хотите узнать что-то другое, что вы просите. Вам действительно нужно прекратить задавать абстрактные вопросы и объяснить вашу конкретную проблему.