如何对这个参数处理代码进行去重?

4

我有以下代码,可以在脚本中从命令行设置y和x轴的限制条件,最终调用matplotlib(这里,ax是一个matplotlib axes对象,但这并不重要,p是一个ArgumentParser实例):

p.add_argument('--ylim', help='Set the y axis limits explicitly (e.g., to cross at zero)', type=float, nargs='+')
p.add_argument('--xlim', help='Set the x axis limits explicitly', type=float, nargs='+')

# more stuff


if args.ylim:
    if (len(args.ylim) == 1):
        ax.set_ylim(args.ylim[0])
    elif (len(args.ylim) == 2):
        ax.set_ylim(args.ylim[0], args.ylim[1])
    else:
        sys.exit('provide one or two args to --ylim')

if args.xlim:
    if (len(args.xlim) == 1):
        ax.set_xlim(args.xlim[0])
    elif (len(args.xlim) == 2):
        ax.set_xlim(args.xlim[0], args.xlim[1])
    else:
        sys.exit('provide one or two args to --xlim')

如果你相信DRY,那么这两个块几乎是一样的,除了在第二个块中到处都用xlim代替ylim
如何重构以消除重复?调用两次某种限制设置函数似乎很明显,但我如何传递我想在一个情况下调用set_ylim,在另一个情况下调用set_xlim的事实?
请注意,在没有指定--*lim参数的情况下调用脚本是完全有效的,并且应该像上面的代码一样行为(也就是说,如果函数被调用,但具有与示例相同的效果,那也可以)。
2个回答

3

函数是一等对象。如果您编写ax.set_ylim而不调用它,则会获得对set_ylim()函数的引用,绑定到ax

def check(name, lim, setter) 
    if not lim:
        return

    if (len(lim) == 1):
        setter(lim[0])
    elif (len(lim) == 2):
        setter(lim[0], lim[1])
    else:
        sys.exit(f'provide one or two args to {name}')

check('--ylim', args.ylim, ax.set_ylim)
check('--xlim', args.xlim, ax.set_xlim)

1

使用argparse有几个选项:

定义一个自定义操作,限制值的数量到指定范围,例如 Python argparse: Is there a way to specify a range in nargs?

这样做的好处是在处理传入参数的上下文中处理错误;由于您的输入应该是可以接受的,因此您只需解压缩args即可。

# From: https://dev59.com/22855IYBdhLWcg3wsWlq#4195302
def required_length(nmin,nmax):
    class RequiredLength(argparse.Action):
        def __call__(self, parser, args, values, option_string=None):
            if not nmin<=len(values)<=nmax:
                msg='argument "{f}" requires between {nmin} and {nmax} arguments'.format(
                    f=self.dest,nmin=nmin,nmax=nmax)
                raise argparse.ArgumentTypeError(msg)
            setattr(args, self.dest, values)
    return RequiredLength

# Make parser
parser=argparse.ArgumentParser(prog='PROG')
parser.add_argument('--xlim', nargs='+', type=int, action=required_length(1,2))
parser.add_argument('--ylim', nargs='+', type=int, action=required_length(1,2))

# Use args in code
ax.set_xlim(*args.xlim)
ax.set_ylim(*args.ylim)

或者,您可以允许长度为一或更多个输入(nargs=“+”),然后手动检查您的输入长度,并在需要时引发解析器错误:

# Make parser
parser=argparse.ArgumentParser(prog='PROG')
parser.add_argument('--xlim', nargs='+', type=int)
parser.add_argument('--ylim', nargs='+', type=int)

# Check args
if not 1 <= len(args.xlim) <= 2:
    parser.error("xlim must have length 1 or 2")
if not 1 <= len(args.ylim) <= 2:
    parser.error("ylim must have length 1 or 2")

# Use args in code
ax.set_xlim(*args.xlim)
ax.set_ylim(*args.ylim)

我原本认为这对于一些--xlim--ylim未设置的情况不起作用,但是当传递0个参数时,set_xlim似乎是无操作的。 - BeeOnRope
您还可以指定默认参数为[None,None],这将不会对它们产生影响。 - Alex
事实上,我的体会是,在调用“mlp”API函数时,即使不指定参数,也有一种方法可以让它们保持未定义。然而,默认参数的方法,例如调用“set_xlim([None, None])”方法,并没有比你在答案中展示的方法更有优势,对吧?也许我漏掉了什么。 - BeeOnRope
在我的两种方法中,我认为由于nargs='+',需要同时使用xlimylim。None列表可以解决这个问题。但是,你也可以使用nargs='*'和稍微修改的检查方式。 - Alex
啊,我错过了。是的,一个(未记录的)关键要求是支持离开 --[x|y]lim,实际上这是调用此脚本最常见的方式。离开它们应该是“好像”没有调用 set_* 方法一样。我正在更新问题,包括 argparse 行以使其更清晰。 - BeeOnRope

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接