2

Is there a way to forward function arguments without hiding the fact that the original call did or did not provide optional arguments?

def func1(a=x):
    # do stuff

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

A call to func2() should result in func1() being called without arguments or at least with its default arguments, whatever they may be.

The following almost works but fundamentally I don't know if there is a way for func2 to determine if its defaults were invoked on not.

def func2(b=y):
    # this comes close but what if func2(y) is called?
    if b == y:
        return func1()
    else:
        return func1(b)
Praxeolitic
  • 22,455
  • 16
  • 75
  • 126
  • If the default value is `1`, what difference does in make if the calling function passed in `1` or left it alone? – Ethan Furman Sep 09 '14 at 16:27
  • func2 then needs to be updated whenever func1 is updated. If the functions are written by different authors this could be problematic. – Praxeolitic Sep 09 '14 at 16:30
  • Why? If I'm using `frobble = func2(a=9)` why would I care that `func1()`'s default argument changed value from `9` to `11`? – Ethan Furman Sep 09 '14 at 16:36
  • Maybe I misunderstood your first comment. Sometimes func2 needs to call func1(), agnostic of func1's defaults, and the defaults to func2 might not be meaningful, so both defaults come into play. – Praxeolitic Sep 09 '14 at 16:44
  • What is your exact use case? `func2` should be smart enough to only pass on the appropriate params to `func1`, and that should rely on the default values of any parameters. – Ethan Furman Sep 09 '14 at 16:47

5 Answers5

1

The usual way of determining if a parameter is left off is to use None as the default. It's unlikely that you'll be calling a function with None so it's a useful marker.

def func2(b=None):
    if b is None:
        return func1()
    else:
        return func1(b)
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • This is a good practical suggestion but if one is willing to dig through internals a bit, is there a definitive way to determine if defaults were used? – Praxeolitic Sep 09 '14 at 16:10
  • @Praxeolitic: There's no straightforward way to tell the difference between a function being called without an argument (and using a default) and a function being called with its default argument being passed in explicitly. This is why using a sentinel value (that a user is unlikely to pass you by mistake) is a good idea if you need to handle the default case specially. – Blckknght Sep 09 '14 at 16:25
  • @Blckknght I think you can do it by using `*args` as the parameter list, but I'm not as familiar with that and I'm not going to suggest it as an answer. – Mark Ransom Sep 09 '14 at 16:30
  • @MarkRansom: Yes, you can do the default argument handling yourself that way, but if your signature is `def foo(arg="some default"):` there's no way to tell from inside the function if `arg` is the default or if the caller actually passed you the default value explicitly. You probably shouldn't care, either! – Blckknght Sep 09 '14 at 18:43
1

I suspect the right way to do this is to have your func2 function use a sentinel value as its default argument, so you can recognize it easily. If you get that sentinel, you can set up the arguments you'll pass on to func1 however you want (e.g. not passing any argument). You can use argument unpacking to handle passing a variable number of arguments (such as 0-1).

A common sentinel is None, though if that could be a meaningful value for a caller to pass, you may want to use something else (an instance of object is a common choice). Here's an example:

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)

Note that this design is relatively silly. Most of the time you'll either call func1 with an argument in all cases, or you'll call it without an argument in all cases. You rarely need to conditionally pass an argument like this.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
1

See this answer:

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

There is no way to get the information you want from the internals. To detect whether defaults were used, you would need to re-implement the internal default argument processing within the function, i.e.:

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

Now from the first check we guarantee that func2() was called as opposed to func2(y) or func2(b=y). In almost every case, the unique object sentinel is good enough to avoid having to truly guarantee how it was called, but it can be done.

But judging from the fact that you immediately return the result of func1, I see no reason why func2 even has default arguments. In the default call (func2()), that y is never used. So why is it there? Why don't you just use define func2(*a, **k) and pass them directly to func1?

Community
  • 1
  • 1
nmclean
  • 7,564
  • 2
  • 28
  • 37
  • Presumably `b` was used in some not-shown part of the code before calling `func1`. Also as part of that "internal default argument processing" you should make sure that `args` isn't too long and `kwargs` doesn't contain an invalid argument. It just becomes too much boilerplate for such a simple task. – Mark Ransom Sep 09 '14 at 17:13
0

Argument forwarding should be done with variadic arguments:

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

Everything will just work, although introspection can suffer a bit.


If you need to sometimes not pass on an argument, you can remove an argument whenever:

del kwargs["name"]

An example:

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!!!

If you really don't want to do this (although you'd be wrong), you can use object to generate a unique sentinel.

# 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)
Veedrac
  • 58,273
  • 15
  • 112
  • 169
0

What is your exact use case? func2 should be smart enough to only pass on the appropriate params to func1, and that should rely on the default values of any parameters.

The only time I have ever found it necessary to change how func2 calls func1 is when func1 is a c function with a screwy signature:

def func2(this, that, those=None):
    if those is None:
        return func1(this, that)
    else:
        return func1(this, that, those)
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237