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

У меня есть функция с одним необязательным аргументом, например:

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)

Есть ли более питонический способ сделать это?

Ответ 1

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

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

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

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

Ответ 2

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()

Ответ 3

То, как вы это сделали, прекрасно. Другой способ: для funB иметь те же значения по умолчанию, что и funA, поэтому вы можете передать те же параметры прямо. Например, если вы выполняете def funB(x, a, b=1), вы всегда можете вызвать return funA(x, a, b) именно так.

В простых случаях вышеуказанное будет работать нормально. Для более сложных случаев вы можете использовать *args и **kwargs (пояснил здесь и здесь). В частности, вы можете передать все свои аргументы ключевого слова в виде словаря (условно называемого 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).

Ответ 4

Используя 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"

Ответ 5

Используя 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)

Ответ 6

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

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]))

Версия, которая не будет терпеть неудачу, если x или - None:

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