5

I'm looking for something that works like Lisp's arg-supplied-p variables, to help differentiate a default value from the same value specified by a user.

Example:

def foo(a=10):
    pass

I'd like to know in foo if it was called like this:

foo()

or like this:

foo(10)

or even like this:

foo(a=10)

The last 2 are synonymous enough to me; I don't need to know that detail. I've looked through the inspect module a bit, but getargspec returns exactly the same results for all 3 calls.

Gary Fixler
  • 5,632
  • 2
  • 23
  • 39
  • Also see https://stackoverflow.com/questions/33073652/check-the-number-of-parameters-passed-in-python-function – illiterate Nov 30 '19 at 19:16

4 Answers4

12

Except for inspecting the source code, there is no way to tell the three function calls apart – they are meant to have exactly the same meaning. If you need to differentiate between them, use a different default value, e.g. None.

def foo(a=None):
    if a is None:
        a = 10
        # no value for a provided
martineau
  • 119,623
  • 25
  • 170
  • 301
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • 2
    This also gets around the problems encountered when the default is a mutable object. Definitely the Pythonic way to go. – Mark Ransom Jun 28 '12 at 19:32
  • I thought as much, Sven, but wanted to double check. My use case at the moment is json.dumps. I'm passing indent through to it from another call, but if you pass indent=False, you get newlines in the output. If you don't pass indent at all, you don't get them. I guess I'll work off of None vs. False. Thanks. – Gary Fixler Jun 28 '12 at 19:55
6

As Sven points out, it's typical to use None for a default argument. If you even need to tell the difference between no argument, and an explicit None being provided, you can use a sentinel object:

sentinel = object()

def foo(a=sentinel):
    if a is sentinel:
        # No argument was provided
        ...
Ned Batchelder
  • 364,293
  • 75
  • 561
  • 662
  • But then again, you couldn't tell if the user passed in `sentinel`… – Sven Marnach Jun 28 '12 at 19:05
  • True, but the user doesn't have `sentinel` to play around with anyway. It's a value that is different than any value the user might try to pass you. – Ned Batchelder Jun 28 '12 at 19:16
  • Thanks, Ned. This is along the lines of what I wanted to see if there wasn't a built-in or standard method. In the end, I'm going to default to None and require passing False, which makes more sense anyway, but using an object the user couldn't pass as the default is pretty smart. Thanks. – Gary Fixler Jun 28 '12 at 20:30
  • @SvenMarnach Funny, but that does make a good point we should probably call it `_sentinel` – jamylak Dec 27 '14 at 22:59
  • @SvenMarnach In some cases, that is an advantage. Consider for example if you're creating a function that needs to pass through. It can use the same default. – jpmc26 Sep 20 '16 at 16:09
2

Not really. You can use foo.func_defaults to get a list of the function's default arguments, so you could use that to check for object identity with the passed arguments. But this will only work reliably for mutable objects. There's no way to tell, as in your example, whether someone passed 10 or used the default 10, because both 10s are likely to be the same object.

This is only a rough approximation anyway, because it doesn't tell you how the function was called: it tells you what the default arguments are, and you look at the actual arguments, and try to guess whether they are the same. There is no way to get access to the syntactic form used to call the function.

Here's an example that shows how it sometimes works and sometimes doesn't.

>>> def f(x="this is crazy"):
...     print x is f.func_defaults[0]
>>> f()
True
>>> f("this is crazy")
False
>>> def f(x=10):
...     print x is f.func_defaults[0]
>>> f()
True
>>> f(10)
True
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • Oops, I see I had a typo. I meant it *won't* work reliably for immutable objects. This is just an example of its unpredictability. – BrenBarn Jun 28 '12 at 19:12
0

The called function only gets the final argument value, whether that's the default value or something that's passed in. So what you need to do to be able to tell whether the caller passed in something is to make the default value something they can't pass in.

They can pass in None, so you can't use that. What can you use?

The simplest solution is to create a Python object of some sort, use it as the default value in the function, and then del it so users have no way to refer to it. Technically, users can dig this out of your function objects, but rarely will they go to such lengths, and if they do, you can argue that they deserve what they get.

default = []

def foo(a=default):
    if a is default:
        a = 10
    print a

del default

The "default value" I'm using here is an empty list. Since lists are mutable, they're never reused, meaning that each empty list is a unique object. (Python actually uses the same empty tuple object for all empty tuples, so don't use an empty tuple! String literals and smallish integers are similarly reused, so don't use those either.) An object instance would be fine. Anything as long as it's mutable and thus not to be shared.

Now the problem is the code above won't actually run since you've done del default. How do you get a reference to this object in your function at run-time so you can test against it? One way to do it is with a default argument value which isn't used for an actual argument.

default = []

def foo(a=default, default=default):
    if a is default:
          a = 10
     print a

del default

This binds the object to the argument's default value when the function is defined, so it doesn't matter that the global name is deleted a second later. But the problem there is if someone does help() on your function, or otherwise introspects it, it will look like there is an extra argument that they can pass in. A better solution is a closure:

default = []

def foo(default=default):   # binds default to outer func's local var default
    def foo(a=default):     # refers to outer func's local var default
        if a is default:
            a = 10
        print a
    return foo
foo = foo()

del default

This is all rather a lot of work to go to, so really, don't bother.

kindall
  • 178,883
  • 35
  • 278
  • 309