简体   繁体   中英

Why can you assign functions to type, const and default?

I want to understand why this code works. Why can you give type , const and default a built-in function?

def printList(i):
    print("Integer:", i)
    return(i)

def getParser():
    from argparse import ArgumentParser

    parser = ArgumentParser()
    parser.add_argument("integers", type=printList, nargs="+")
    parser.add_argument("--sum", dest="accumulate", const=sum, default=max, nargs="?")
    return(parser)


args = getParser().parse_args(["2", "3", "9", "5"])
print(args.integers)
print(args.accumulate(args.integers))

Output:

>>> Integer: 2
>>> Integer: 3
>>> Integer: 9
>>> Integer: 5
>>> ['2', '3', '9', '5'] 
>>> 9

I want to understand why it is working.

Edit:

You misunderstood me.

For example "type" I would expect to see "type=int", because you want only to allow Integers. So you give "type" something. But in my example "type" gives something to the "printList" functions, so that it can print it. Or for example "default". I would expect to give some value, liken an int or str. I give something to "default". But in my example I give the build-in function "max". Why does "max" get a list? Why is that working? That is what I want to know.

parser.add_argument("--sum", dest="accumulate", const=sum, default=max, nargs="?")

is, assuming you don't supply an argument with '--sum' , a little bit like:

args.accumulate = sum if '--sum' in arg_list else max
                               # ^ it also needs to be in the right place!

Per the docs for nargs='?' :

One argument will be consumed from the command line if possible, and produced as a single item. If no command-line argument is present, the value from default will be produced. Note that for optional arguments, there is an additional case - the option string is present but not followed by a command-line argument. In this case the value from const will be produced.

Functions are first-class objects in Python, and can be passed around like any other object, so there's no reason they can't be used for const and default .


Note that if you actually did supply an argument with '--sum' , args.accumulate would be that value instead:

>>> args = getParser().parse_args(["1", "--sum", "this one"])
('Integer:', '1')
>>> args.accumulate
'this one'

Short answer:

argparse expects type to be a callable (with limited provision for a string).

argparse allows const and default to be almost anything. It doesn't do anything with them except assign them to an attribute (or pass them to your type callable).

Long answer:

From the internal documentation, for class Action :

  • type -- A callable that accepts a single string argument, and returns the converted value. The standard Python types str, int, float, and complex are useful examples of such callables. If None, str is used.

  • default -- The value to be produced if the option is not specified.

  • const -- The value to be produced if the option is specified and the option uses an action that takes no values.

parse.add_argument creates an Action , using the action parameter to specify the subclass. You can look at this object interactively or with:

a = parser.add_argument(....)
print(repr(a))

Parameters like type , nargs , default are stored as attributes of this object.

At some depth in the parse_args action, parser._get_values() ends up calling parser._get_value on each string that is allocated to a particular action.

def _get_value(self, action, arg_string):
    type_func = self._registry_get('type', action.type, action.type)
    if not callable(type_func):
        msg = _('%r is not callable')
        raise ArgumentError(action, msg % type_func)

    # convert the value to the appropriate type
    try:
        result = type_func(arg_string)

This looks in a dictionary ( parser._registries ) to see if action.type has been defined (as a string). If not, it assumes action.type is itself a function. The type is used in the result = type_func(arg_string) line.

As a default None is the only registered type

def identity(string):
   return string

argparse.FileType is a factory that creates a type function, one that can open a file (ie take a file name and return an open file).

int , float are builtin Python functions that take a string and return a value, or raise an error if there is a problem. These specify the function that is called to convert the string. They do not, directly, specify that the string must represent an integer or float. Users sometimes mistakenly use boolean in the same way.

The action parameter is looked up in the _registries . All of the default action classes have an entry, hence we use values like store , store_true , and append . But we can also specify a custom Action class.

As for default , early in the parse_args stack, this line is executed for each action (defined argument):

setattr(namespace, action.dest, action.default)

This makes not assumptions about the nature of action.default . It could be None , a string, a number, a list, a function, or any Python object.

Near the end of parse_args , it may pass action.default through the type function. It does this only if default is a string.

 setattr(namespace, action.dest, self._get_value(action, action.default))

Look at your args . It probably looks like:

Namespace(integers=['2', '3', '9', '5'], accumulate=max)

Since you did not specify a --sum option, the default value is placed in the namespace without change or checking. If you had specified --sum , but without an argument, then accumulate=sum (the const parameter) would be in the name space. But --sum some_other_function would have created accumulate="some_other_function" .

Your chosen type function has the side effect of printing its input, but it returns the string unchanged, hence you see strings in the integers list.

When you execute

args.accumulate(args.integers)

It is the same as

max(["2", "3", "9", "5"])

which returns "9" - the string, not the number.

sum(["2", "3", "9", "5"])

raise an error. The misnamed printList only prints a string, not a list, and does not convert its values to integers.

Sorry to be long winded, but you did ask what is going on. In short, argparse is written to give the user a lot of power, but it also tries to handle many common cases in a simple default manner.

Because functions are first-class objects in Python. They can be bound, accessed, and moved around just like any other type.

>>> foo = max
>>> foo(2, 3, 1)
3

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM