2015-12-10 2 views
12

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

def funA(x, a, b=1): 
    return a+b*x 

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

Я думал, что-то вроде этого:

def funB(x, a, b=None): 
    if b: 
    return funA(x, a, b) 
    else: 
    return funA(x, a) 

есть более вещий способ сделать это?

+2

Почему бы не установить значение по умолчанию для 'b' в' funB', а затем всегда отправлять с параметром 'b' в' funA() '? –

ответ

16

Я хотел бы заменить if b с if b is not None, так что если вы передаете b=0 (или любой другой «falsy» значение) в качестве аргумента до funB будет отправлено на номер funA.

Кроме того, для меня это кажется довольно pythonic: ясным и явным. (Хотя, возможно, немного бесполезный, в зависимости от того, что вы пытаетесь сделать!)

Чуть более загадочным способом, который зависит от вызова funB с правильными аргументами ключевого слова (например funB(3, 2, b=4):

def funB(x, a, **kwargs): 
    return funA(x, a, **kwargs) 
+5

Загадочный путь - шаг назад. Сложнее читать без каких-либо дополнительных функций. – chepner

+3

Да, вы правы («загадочный» редко указывает на что-то положительное), и это также на самом деле не одно и то же функционально, но все же что-то, что может быть полезно и избегать большого количества кода в некоторых ситуациях, возможно, не в этом. Но это становится удобным, если вместо 1 у нас есть 2,3,4 ... необязательные аргументы по умолчанию – LeartS

+1

@falsetru, поэтому я указал «что полагается на вызов« funB »с правильными аргументами ключевого слова» – LeartS

8
def funA(x, a, b=1): 
    return a+b*x 

def funB(x, a, b=1):  
    return funA(x, a, b) 

Сделать значение по умолчанию b=1 в funB(), а затем передать его всегда funA()

+1

Я не могу изменить 'funA'. Возьмите его как данность. – itzy

+0

Не нужно изменять 'funA()', если вы этого не хотите. – k4ppa

+0

@itzy, если значение по умолчанию funB совпадает с значением по умолчанию funA, это хорошее решение – gkusner

3

Путь вы сделали это хорошо. Еще один способ для funB иметь то же значение по умолчанию, как funA, так что вы можете передать те же параметры, навылет. например, если вы делаете def funB(x, a, b=1), то вы всегда можете позвонить return funA(x, a, b) просто так.

Для простых случаев выше работа хорошо. Для более сложных случаев вы можете использовать *args и **kwargs (пояснил here и here). В частности, вы можете передать все свои аргументы ключевых слов в качестве словаря (обычно называемого kwargs). В этом случае каждая функция будет устанавливать свои собственные независимые значения по умолчанию, и вы бы просто пройти весь словарь через:

def funA(x, a, **kwargs): 
    b = kwargs.get("b", 1) 
    return a+b*x 

def funB(x, a, **kwargs): 
    return funA(x, a, **kwargs) 

Если kwargs пусто, когда передаются funB (b не указан), то он будет установлен по умолчанию в funA утверждением b = kwargs.get("b", 1). Если задано значение b, оно будет передано через as-is. Обратите внимание, что в funB вы можете получить доступ к b со своим собственным независимым значением по умолчанию и по-прежнему получать поведение, которое вы ищете.

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

1

Используя inspect.getargspec, вы можете получить значения по умолчанию (четвертый пункт возвращаемого кортежа = defaults):

import inspect 

def funA(x, a, b=1): 
    return a + b * x 

# inspect.getargspec(funA) => 
#  ArgSpec(args=['x', 'a', 'b'], varargs=None, keywords=None, defaults=(1,)) 
def funcB(x, a, b=inspect.getargspec(funA)[3][0]): 
    return funA(x, a, b) 

ИЛИ (в Python 2.7+)

def funcB(x, a, b=inspect.getargspec(funA).defaults[0]): 
    return funA(x, a, b) 

В Python 3.5+ это рекомендуют использовать вместо inspect.signature:

def funcB(x, a, b=inspect.signature(funA).parameters['b'].default): 
    return funA(x, a, b) 
0

Вы также можете использовать:

def funA(x, a, b=1): 
    return a+b*x 

def funB(x, a, b=None): 
    return funA(*filter(lambda o: o is not None, [x, a, b])) 

версии, которая не подведет, если х или не указаны:

def funB(x, a, b=None): 
    return funA(*([x, a]+filter(lambda o: o is not None, [b]))) 
+0

Что, почему ?! Я предполагаю, что x, a и b всегда должны быть переданы в x, a и b, а не «принимать значения не None и передавать их в базовую функцию, полностью игнорируя то, что/где каждый параметр« – LeartS

+0

. Вы правы, это не удастся, если x или a None –

+1

см. обновленный ответ –

2

Используя FunctionType от types, вы можете просто взять функцию и создать новую, определяющую значения по умолчанию во время выполнения. Вы можете поместить все это в декоратор, чтобы в точке, где вы пишете свой код, он будет держать вещи в порядке, в то же время давая читателю понять, что вы пытаетесь выполнить. Он также позволяет точно такую ​​же сигнатуру вызова для funB, как и funA. Все аргументы могут быть позиционными, или все аргументы могут быть ключевыми словами или любым допустимым их сочетанием, а любые аргументы со значениями по умолчанию являются необязательными. Должен играть неплохо с позиционными аргументами (*args) и аргументами ключевого слова (**kwargs).

import inspect 
from types import FunctionType 

def copy_defaults(source_function): 
    def decorator(destination_function): 
     """Creates a wrapper for the destination function with the exact same 
     signature as source_function (including defaults).""" 

     # check signature matches 
     src_sig = inspect.signature(source_function) 
     dst_sig = inspect.signature(destination_function) 
     if list(src_sig.parameters) != list(dst_sig.parameters): 
      raise ValueError("src func and dst func do not having matching " \ 
       "parameter names/order") 

     return FunctionType(
      destination_function.__code__, 
      destination_function.__globals__, 
      destination_function.__name__, 
      source_function.__defaults__, # use defaults from src 
      destination_function.__closure__ 
     ) 
    return decorator 

def funA(x, a, b=1): 
    return a+b*x 

@copy_defaults(funA) 
def funB(x, a, b): 
    """this is fun B""" 
    return funA(x, a, b) 

assert funA(1, 2) == funB(1, 2) 
assert funB.__name__ == "funB" 
assert funB.__doc__ == "this is fun B" 
Смежные вопросы