Is there any way to know if the value of an argument is the default vs. user-specified?

与世无争的帅哥 提交于 2020-01-19 14:23:26

问题


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.


回答1:


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



回答2:


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



回答3:


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



回答4:


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.



来源:https://stackoverflow.com/questions/11251119/is-there-any-way-to-know-if-the-value-of-an-argument-is-the-default-vs-user-spe

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!