2015-06-01 2 views
2

Как написать декоратор, чтобы декорированные функции могли принимать (и игнорировать) произвольные аргументы?Декоратор, позволяющий функции принимать произвольные аргументы

У меня есть некоторые функции, как это:

def foo(x): 
    return x 

def foo2(x, y): 
    if bar(y): 
     return x 
    else: 
     return x + 1 

def foo3(x, y, z): 
    ... 

foo() можно вычислить возвращаемое значение для данного x только на основе x, но foo2() нужен еще один параметр, и foo3() нужен третий параметр. У меня есть метод в другом месте, который, между прочим, вызывает либо foo(), foo2(), и т. Д. В зависимости от заданного пользователем аргумента.

Сейчас метод просто захватывает соответствующую функцию с getattr(user_arg) и называет его со всеми x, y, z. Чтобы избежать TypeError за неверное число аргументов, то foo функции все определены с *args, как это:

def foo(x, *args): 
    return x 

Но я хотел бы просто декоратора, чтобы избежать включения *args в каждом определении foo функции. Есть ли способ сделать это? Или вы можете предложить лучший способ организовать этот код?

Одним из способов было бы написать метод, как это:

if user_arg == 'foo': 
    foo(x) 
elif user_arg == 'foo2': 
    foo2(x, y) 
elif user_arg == 'foo3': 
    foo3(x, y, z) 

Но я бы очень хотел, чтобы избежать этого, потому что есть много foo функций, и потому что я хотел бы сделать это можно добавить новые функции foo, не добавляя еще одну ветвь к методу.

Редактировать: Для того, чтобы уточнить, что это не тот случай, когда мне нужно вызвать другую функцию fooоснованную на число аргументов. Это произвольная (заданная пользователем) функция из foo функций.

def foo3(x, y, z): 
    return x + y + z 

def foo4(x, y, z): 
    return x + y - z 
+0

Образец, который вы описываете (отправка по количеству аргументов), не имеет ничего общего с декораторами. – alfasin

+0

Есть ли у вас предложения? – user2034412

+0

Как '@somedecorator def foo (x)' быть проще, чем 'def foo (x, * args)'? – chepner

ответ

4

Вы можете использовать inspect.getargspec, чтобы определить, какие аргументы украшают функцию t AKES:

import functools 
import inspect 

def ignore_extra_arguments(func): 
    args, varargs, kwvarargs, defaults = inspect.getargspec(func) 
    @functools.wraps(func) 
    def wrapper_func(*wrapper_args, **wrapper_kwargs): 
     if varargs is None: 
      # remove extra positional arguments 
      wrapper_args = wrapper_args[:len(args)] 
     if kwvarargs is None: 
      # remove extra keyword arguments 
      wrapper_kwargs = {k: v for k, v in wrapper_kwargs.iteritems() if k in args} 
     return func(*wrapper_args, **wrapper_kwargs) 
    return wrapper_func 

Это может быть оптимизированы немного, подняв if извлекающих из wrapper_func, за счет написания больше коды.

1

Вы можете использовать named arguments и всегда вызывать foo() (первая функция), для того чтобы послать к функции "правой", следующим образом:

def foo(a, b=None, c=None, d=None): 
    if b: 
     return foo2(a, b, c, d) 
    else: 
     # do your thing 

def foo2(a, b, c=None, d=None):   
    if c: 
     return foo3(a, b, c, d) 
    else: 
     # do your thing 

def foo3(a, b, c, d=None):     
    ... 

Другой вариант:

def foo(a, *b): 
    if b: 
     return foo2(a, *b) 
    else: 
     print "in foo" 
     # do your thing 

def foo2(a, b, *c):  
    if c: 
     return foo3(a, b, *c) 
    else: 
     print "in foo2" 
     # do your thing 

def foo3(a, b, c, *d): 
    print "in foo3" 


foo(1, 2, 3) 
+0

Спасибо, это хорошая мысль, но это не сработает для меня, потому что некоторые из функций 'foo' принимают один и тот же набор аргументов, но делают с ними разные вещи. Я не указал это в вопросе. – user2034412

+0

@ user2034412 Установив значение по умолчанию 'None', вы можете установить' foo() ', чтобы принять столько аргументов, сколько хотите, - поэтому кажется, что он должен работать. Тем не менее, пожалуйста, ознакомьтесь с 2-м подходом - это немного более кратким и гораздо более «пифоническим», – alfasin

Смежные вопросы