2

Я хочу создать декоратор, который объединяет две функции и объединяет параметры из их подписи.Создание декоратора, который объединяет две функции без указания вызывающей сигнатуры исходной функции

Интерфейс Я хочу:

def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n): 
    # I am using many parameters to explain the need of not 
    # needing to type the arguments again. 
    return a * b * c * d * e * f * g * h * i * j * k * l * m * n 

@combines(f) 
def g(o, p, *args, **kwargs): 
    return (o + p) * f(*args, **kwargs) 

Это должно существенно привести:

def g(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p): 
    return (o + p) * (a * b * c * d * e * f * g 
         * h * i * j * k * l * m * n) 

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

Я не уверен, что мне нужно позвонить g с *args и **kwargs, но я думаю, что это будет необходимо.

Это как далеко я получил:

import functools 
import inspect 

def combines(old_func): 
    old_sig = inspect.signature(old_func) 
    old_parameters = old_sig.parameters 
    def insert_in_signature(new_func): 
     new_parameters = inspect.signature(new_func).parameters 
     for new_parameter in new_parameters: 
      if new_parameter in old_parameters.keys(): 
       raise TypeError('`{}` argument already defined'.format(new_parameter)) 

     @functools.wraps(new_func) 
     def wrapper(*args, **kwargs): 
      return old_func(*args, **kwargs) * new_func(*args, **kwargs) 

     parms = list(old_parameters.values()) 
     for arg, par in new_parameters.items(): 
      if par.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD: 
       parms.append(inspect.Parameter(arg, par.kind)) 

     wrapper.__signature__ = old_sig.replace(parameters=parms) 
     return wrapper 
    return insert_in_signature 

def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n): 
    return a * b * c * d * e * f * g * h * i * j * k * l * m * n 

@combines(f) 
def g(o, p, *args, **kwargs): 
    return (o + p) * f(*args, **kwargs) 

Это приводит к желаемому вызывающей подписи g, но он не работает.

EDIT, поскольку сообщение об ошибке было предложено

Например: g(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)

--------------------------------------------------------------------------- 
TypeError         Traceback (most recent call last) 
<ipython-input-23-2775f64e1b3e> in <module>() 
----> 1 g(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1) 

<ipython-input-18-3a843320e4e3> in wrapper(*args, **kwargs) 
    13   @functools.wraps(new_func) 
    14   def wrapper(*args, **kwargs): 
---> 15    return old_func(*args, **kwargs) * new_func(*args, **kwargs) 
    16 
    17   parms = list(old_parameters.values()) 

TypeError: f() takes 14 positional arguments but 16 were given 

если я затем следуют сообщение об ошибке и дать 14 аргументы g(1,1,1,1,1,1,1,1,1,1,1,1,1,1):

--------------------------------------------------------------------------- 
TypeError         Traceback (most recent call last) 
<ipython-input-24-052802b037a4> in <module>() 
----> 1 g(1,1,1,1,1,1,1,1,1,1,1,1,1,1) 

<ipython-input-18-3a843320e4e3> in wrapper(*args, **kwargs) 
    13   @functools.wraps(new_func) 
    14   def wrapper(*args, **kwargs): 
---> 15    return old_func(*args, **kwargs) * new_func(*args, **kwargs) 
    16 
    17   parms = list(old_parameters.values()) 

<ipython-input-18-3a843320e4e3> in g(o, p, *args, **kwargs) 
    29 @combines(f) 
    30 def g(o, p, *args, **kwargs): 
---> 31  return (o + p) * f(*args, **kwargs) 

TypeError: f() missing 2 required positional arguments: 'm' and 'n' 

So ясно, что моя реализация на самом деле не работает.

+0

В какой конкретный способ она не работает? У вас есть пример _complete_, который могут попробовать другие люди? Покажи это. Есть ли ошибка? Покажи это_. – Useless

+0

Спасибо за ваш комментарий, я добавил сообщение об ошибке. – johnbaltis

ответ

2

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

Вы должны выбрать один, мой совет, чтобы удалить его вызов из исходного g

Я немного изменил свой код, но по крайней мере моя версия работает в Python 3.5:

  • сигнатура обернутая функция перечисляет все параметры для f и g
  • обернутой функция принимает позиционные и ключевые параметры
  • обернутой функция вызывает ошибку, когда он получает неправильное количество параметров

Вот код:

def combine(ext): 
    ext_params = inspect.signature(ext).parameters 
    def wrapper(inn): 
     inn_params = inspect.signature(inn).parameters 
     for k in inn_params.keys(): 
      if k in ext_params.keys(): 
       raise TypeError('`{}` argument already defined'.format(
         k)) 
     all_params = list(ext_params.values()) + \ 
       list(inn_params.values()) 
     # computes the signature for the wrapped function 
     sig = inspect.signature(inn).replace(parameters = all_params) 
     def wrapped(*args, **kwargs): 
      # signature bind magically processes positional and keyword arguments 
      act_args = sig.bind(*args, **kwargs).args 
      ext_args = act_args[:len(ext_params.keys())] # for external function 
      inn_args = act_args[len(ext_params.keys()):] # for inner function 
      return ext(*ext_args) * inn(*inn_args) 
     w = functools.update_wrapper(wrapped, inn) # configure the wrapper function 
     w.__signature__ = sig # and set its signature 
     return w 
    return wrapper 

теперь я могу написать:

>>> @combine(f) 
def g(o,p): 
    return o+p 

>>> help(g) 
Help on function g in module __main__: 

g(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) 

>>> g(1,1,1,1,1,1,1,1,1,1,1,1,1,p=1, o=1, n=1) 
2 
0

Думаю, я понимаю, чего вы хотите. Для этого, прежде всего, я рекомендую использовать ООП, чтобы сделать все более ясным. Я не думаю, что вам нужен весь этот код для выполнения параметров или модуля inspect. Я думаю, вы должны держать его простым:

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

Вот одно решение:

def combines(func): 
    class Decorator: 
     def __init__(self, f): 
      self.outer_func = func 
      self.inner_func = f 

     def __call__(self, *args, **kwargs): 
      res, a, p = self.inner_func(*args, **kwargs) 
      return res * self.outer_func(*a, **p) 
    return Decorator 


def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n): 
    return a * b * c * d * e * f * g * h * i * j * k * l * m * n 


@combines(f) 
def g(o, p, *args, **kwargs): 
    return o+p, args, kwargs 

print g(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1) # output: 2 

Вот еще одно решение, которое использует третью функцию, чтобы объединить два:

def combines(func1, func2): 
    class Decorator: 
     def __init__(self, f): 
      self.outer_func = func1 
      self.inner_func = func2 
      self.func = f 

     def __call__(self, *args, **kwargs): 
      res, a, p = self.inner_func(*args, **kwargs) 
      return self.func(res, self.outer_func(*a, **p)) 
    return Decorator 


def f(a, b, c, d, e, f, g, h, i, j, k, l, m, n): 
    return a * b * c * d * e * f * g * h * i * j * k * l * m * n 


def g(o, p, *args, **kwargs): 
    return o+p, args, kwargs 


@combines(f, g) 
def h(a, b):  # a is g's result 
    return a * b # b is f's result 

print h(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)  # output: 2 

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