Обернуть функцию без затирания аргументов по умолчанию

Есть ли способ перенаправить аргументы функции, не скрывая тот факт, что первоначальный вызов предоставил или не предоставил необязательные аргументы?

def func1(a=x):
    # do stuff

def func2(b=y):
    # pass args to func1 without masking func1 defaults
    return func1(?)

Вызов func2() должен привести к вызову func1() без аргументов или, по крайней мере, с аргументами по умолчанию, какими бы они ни были.

Следующее почти работает, но в принципе я не знаю, есть ли у func2 способ определить, были ли вызваны его значения по умолчанию.

def func2(b=y):
    # this comes close but what if func2(y) is called?
    if b == y:
        return func1()
    else:
        return func1(b)

person Praxeolitic    schedule 09.09.2014    source источник
comment
Если значение по умолчанию равно 1, какая разница, если вызывающая функция передала 1 или оставила его в покое?   -  person Ethan Furman    schedule 09.09.2014
comment
Затем func2 необходимо обновлять всякий раз, когда обновляется func1. Если функции написаны разными авторами, это может быть проблематично.   -  person Praxeolitic    schedule 09.09.2014
comment
Почему? Если я использую frobble = func2(a=9), какое мне дело до того, что аргумент по умолчанию func1() изменил значение с 9 на 11?   -  person Ethan Furman    schedule 09.09.2014
comment
Возможно, я неправильно понял ваш первый комментарий. Иногда func2 должен вызывать func1(), независимо от значений по умолчанию для func1, а значения по умолчанию для func2 могут не иметь смысла, поэтому в игру вступают оба значения по умолчанию.   -  person Praxeolitic    schedule 09.09.2014
comment
Каков ваш точный вариант использования? func2 должен быть достаточно умен, чтобы передавать только соответствующие параметры func1, и это должно полагаться на значения по умолчанию любых параметров.   -  person Ethan Furman    schedule 09.09.2014


Ответы (5)


Обычный способ определить, пропущен ли параметр, — использовать None по умолчанию. Маловероятно, что вы будете вызывать функцию с None, так что это полезный маркер.

def func2(b=None):
    if b is None:
        return func1()
    else:
        return func1(b)
person Mark Ransom    schedule 09.09.2014
comment
Это хорошее практическое предложение, но если кто-то хочет немного покопаться во внутренностях, есть ли окончательный способ определить, использовались ли значения по умолчанию? - person Praxeolitic; 09.09.2014
comment
@Praxeolitic: нет простого способа определить разницу между функцией, вызываемой без аргумента (и использующей значение по умолчанию), и функцией, вызываемой с явно переданным аргументом по умолчанию. Вот почему использование контрольного значения (которое пользователь вряд ли пропустит по ошибке) является хорошей идеей, если вам нужно специально обрабатывать случай по умолчанию. - person Blckknght; 09.09.2014
comment
@Blckknght Я думаю, вы можете сделать это, используя *args в качестве списка параметров, но я не так хорошо с этим знаком и не собираюсь предлагать это в качестве ответа. - person Mark Ransom; 09.09.2014
comment
@MarkRansom: Да, вы можете самостоятельно обработать аргумент по умолчанию таким образом, но если ваша подпись def foo(arg="some default"):, изнутри функции невозможно определить, является ли arg значением по умолчанию или действительно ли вызывающий объект передал вам значение по умолчанию явно. Вам, наверное, тоже все равно! - person Blckknght; 09.09.2014

Я подозреваю, что правильный способ сделать это состоит в том, чтобы ваша функция func2 использовала дозорное значение в качестве аргумента по умолчанию, чтобы вы могли легко его распознать. Если вы получите этот часовой, вы можете настроить аргументы, которые вы передадите func1, как хотите (например, не передавая никаких аргументов). Вы можете использовать распаковку аргументов для обработки передачи переменного количества аргументов (например, 0-1).

Обычным дозорным является None, хотя, если это может быть значимым значением для передачи вызывающей стороны, вы можете использовать что-то еще (экземпляр object является распространенным выбором). Вот пример:

def func1(a="default value"): # lets assume we don't know what this default is
    # do stuff with a

# later, perhaps in a different module

_sentinel = object()    # our sentinel object

def func2(b=_sentinel):
    if b is _sentinel:  # test for the sentinel
        b = "some useful value"
        a_args = ()     # arguments to func1 is an empty tuple
    else:
        a_args = (b,)   # pack b into a 1-tuple

    # do stuff with b perhaps

    func1(*a_args)      # call func1 with appropriate arguments (either b or nothing)

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

person Blckknght    schedule 09.09.2014

Смотрите этот ответ:

https://stackoverflow.com/a/2088101/933416

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

def func2(*args, **kwargs):
    if len(args) == 0 and "b" not in kwargs:
        b = y
        return func1()
    else:
        return func1(b)

Теперь с первой проверки мы гарантируем, что был вызван func2(), а не func2(y) или func2(b=y). Почти в каждом случае метка уникального объекта достаточно хороша, чтобы избежать необходимости действительно гарантировать, как она была вызвана, но это можно сделать.

Но, судя по тому, что вы немедленно возвращаете результат func1, я не вижу причин, по которым func2 вообще имеет аргументы по умолчанию. В вызове по умолчанию (func2()) этот y никогда не используется. Так почему же это там? Почему бы вам просто не использовать define func2(*a, **k) и не передать их напрямую func1?

person nmclean    schedule 09.09.2014
comment
Предположительно b использовался в какой-то не показанной части кода перед вызовом func1. Кроме того, в рамках этой внутренней обработки аргументов по умолчанию вы должны убедиться, что args не слишком длинное, а kwargs не содержит недопустимый аргумент. Это просто становится слишком шаблонным для такой простой задачи. - person Mark Ransom; 09.09.2014

Пересылка аргументов должна выполняться с вариативными аргументами:

def func2(*args, **kwargs):
    func1(*args, **kwargs)

Все просто будет работать, хотя самоанализ может немного пострадать.


Если вам нужно иногда не передавать аргумент, вы можете удалить аргумент всякий раз, когда:

del kwargs["name"]

Пример:

def print_wrapper(*args, extrabig=False, **kwargs):
    if extrabig:
        args = [arg*2 for arg in args]
        kwargs["sep"] = kwargs.get("sep", " ") * 2

    print(*args, **kwargs)

print_wrapper(2, 4, 8, end="!!!\n")
#>>> 2 4 8!!!

print_wrapper(2, 4, 8, sep=", ", end="!!!\n")
#>>> 2, 4, 8!!!

print_wrapper(2, 4, 8, extrabig=True, end="!!!\n")
#>>> 4  8  16!!!

Если вы действительно не хотите этого делать (хотя вы ошибаетесь), вы можете использовать object для создания уникального часового.

# Bad! Won't let you print None
def optionally_print_one_thing(thing=None):
    if thing is not None:
        print(thing)

# Better
_no_argument = object()
def optionally_print_one_thing(thing=_no_argument):
    if thing is not _no_argument:
        print(thing)
person Veedrac    schedule 09.09.2014
comment
Я думаю, что я читаю вопрос по-другому для вас. Я все равно расширю его. - person Veedrac; 09.09.2014
comment
Также может случиться так, что автор func2 не имеет контроля над func1. - person Praxeolitic; 09.09.2014
comment
@Praxeolitic Я не понимаю, почему это важно. - person Veedrac; 09.09.2014

Каков ваш точный вариант использования? func2 должен быть достаточно умен, чтобы передавать только соответствующие параметры func1, и это должно полагаться на значения по умолчанию любых параметров.

Единственный раз, когда я счел необходимым изменить то, как func2 вызывает func1, это когда func1 является функцией c со странной подписью:

def func2(this, that, those=None):
    if those is None:
        return func1(this, that)
    else:
        return func1(this, that, those)
person Ethan Furman    schedule 09.09.2014