11

我正在查看实现动作的内置argparse._AppendAction的源代码,并对它的实现方式"append"感到困惑:

    def __call__(self, parser, namespace, values, option_string=None):
        items = _copy.copy(_ensure_value(namespace, self.dest, []))
        items.append(values)
        setattr(namespace, self.dest, items)

分解它:

  • _ensure_value就像dict.setdefault属性一样。也就是说,如果namespace有一个带有名称的属性,self.dest则返回它,如果没有,则将其设置为[]并返回。
  • _copy.copy(x)只返回一个浅拷贝。什么时候x是一个列表,它完全一样list(x)(但速度较慢)。
  • 然后将该项目附加到从 获取的列表的副本namespace中。
  • 最后, 的self.dest属性namespace被副本覆盖,这应该会导致旧列表被垃圾收集。

为什么要以如此迂回和低效的方式进行,为每个附加项扔掉一个完整的列表?为什么这还不够?

    def __call__(self, parser, namespace, values, option_string=None):
        items = _ensure_value(namespace, self.dest, [])
        items.append(values)
4

1 回答 1

6

我不是实施方面的专家,所以(免责声明)这只是一个猜测。default=...使用此实现,用户可以在调用中将列表作为 a 传递,add_argument而不会在 argparse 中发生突变。也许出于某种原因,开发人员需要这种类型的安全性。

你提到的效率低下真的没什么大不了的。它用于解析命令行参数,因此在大量使用的情况下,每个程序可能只调用此函数 10 次。


我已经对此进行了测试,实际上,如果我使用以下脚本(argparse_temp只需将其中的位置argparse.py复制到当前目录,以便我可以使用它):

import argparse_temp as argparse

lst = [1,2,3]
parser = argparse.ArgumentParser()
parser.add_argument('-l',default=lst,action='append')
print parser.parse_args()
print lst

这打印(当称为:) python test1.py -l 4

Namespace(l=[1, 2, 3, '4'])
[1, 2, 3]

argparse原样,但是:

Namespace(l=[1, 2, 3, '4'])
[1, 2, 3, '4']

与您提议的更改。

如果您打印由 返回的操作add_argument,您将获得:

_AppendAction(option_strings=['-l'], dest='l', nargs=None, const=None, default=[1, 2, 3, '4'], type=None, choices=None, help=None, metavar=None)

可以想象 argparse 依赖于实现中其他地方的操作。(注意default这里也发生了变异)。

于 2013-01-15T14:04:55.443 回答