Python:argparse.Namespace对象的Typehints

Bil*_*lly 27 python type-hinting pycharm argparse python-3.x

有没有办法让Python静态分析器(例如在PyCharm,其他IDE中)接受argparse.Namespace对象上的Typehints ?例:

parser = argparse.ArgumentParser()
parser.add_argument('--somearg')
parsed = parser.parse_args(['--somearg','someval'])  # type: argparse.Namespace
the_arg = parsed.somearg  # <- Pycharm complains that parsed object has no attribute 'somearg'
Run Code Online (Sandbox Code Playgroud)

如果我删除内联注释中的类型声明,PyCharm不会抱怨,但它也不会选择无效的属性.例如:

parser = argparse.ArgumentParser()
parser.add_argument('--somearg')
parsed = parser.parse_args(['--somearg','someval'])  # no typehint
the_arg = parsed.somaerg   # <- typo in attribute, but no complaint in PyCharm.  Raises AttributeError when executed.
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?


更新

奥斯汀在下面的回答的启发,我能找到的最简单的解决方案是namedtuples:

from collections import namedtuple
ArgNamespace = namedtuple('ArgNamespace', ['some_arg', 'another_arg'])

parser = argparse.ArgumentParser()
parser.add_argument('--some-arg')
parser.add_argument('--another-arg')
parsed = parser.parse_args(['--some-arg', 'val1', '--another-arg', 'val2'])  # type: ArgNamespace

x = parsed.some_arg  # good...
y = parsed.another_arg  # still good...
z = parsed.aint_no_arg  # Flagged by PyCharm!
Run Code Online (Sandbox Code Playgroud)

虽然这是令人满意的,但我仍然不喜欢重复参数名称.如果参数列表显着增长,那么更新两个位置将是乏味的.什么是理想的是以某种方式从parser对象中提取参数,如下所示:

parser = argparse.ArgumentParser()
parser.add_argument('--some-arg')
parser.add_argument('--another-arg')
MagicNamespace = parser.magically_extract_namespace()
parsed = parser.parse_args(['--some-arg', 'val1', '--another-arg', 'val2'])  # type: MagicNamespace
Run Code Online (Sandbox Code Playgroud)

我无法在argparse模块中找到可以实现这一点的任何内容,而且我仍然不确定是否有任何静态分析工具能够足够聪明地获取这些值而不会使IDE停止运行.

仍在搜索中...


更新2

根据hpaulj的评论,我能找到的最接近上述方法的方法是"神奇地"提取解析对象的dest属性,这将从每个解析器的_actions中提取属性:

parser = argparse.ArgumentParser()
parser.add_argument('--some-arg')
parser.add_argument('--another-arg')
MagicNamespace = namedtuple('MagicNamespace', [act.dest for act in parser._actions])
parsed = parser.parse_args(['--some-arg', 'val1', '--another-arg', 'val2'])  # type: MagicNamespace
Run Code Online (Sandbox Code Playgroud)

但是这仍然不会导致属性错误在静态分析中被标记.如果我通过这是事实也是如此namespace=MagicNamespaceparser.parse_args通话.

Abr*_*don 20

大多数答案都涉及使用另一个包来处理打字。仅当没有像我即将提出的那样简单的解决方案时,这才是一个好主意。

步骤 1. 类型声明

首先,定义数据类中每个参数的类型,如下所示:

from dataclasses import dataclass

@dataclass
class MyProgramArgs:
    first_var: str
    second_var: int
Run Code Online (Sandbox Code Playgroud)

步骤 2. 参数声明

然后,您可以根据需要使用匹配的参数来设置解析器。例如:

import argparse

parser = argparse.ArgumentParser("This CLI program uses type hints!")
parser.add_argument("-a", "--first-var")
parser.add_argument("-b", "--another-var", type=int, dest="second_var")
Run Code Online (Sandbox Code Playgroud)

步骤 3. 解析参数

最后,我们以静态类型检查器了解每个参数类型的方式解析参数:

my_args = MyProgramArgs(**vars(parser.parse_args())
Run Code Online (Sandbox Code Playgroud)

现在类型检查器知道它my_args的类型MyProgramArgs,因此它确切地知道哪些字段可用以及它们的类型是什么。


Jes*_*sse 18

类型化参数解析器正是为此目的而制作的。它包装argparse。您的示例实现为:

from tap import Tap


class ArgumentParser(Tap):
    somearg: str


parsed = ArgumentParser().parse_args(['--somearg', 'someval'])
the_arg = parsed.somearg
Run Code Online (Sandbox Code Playgroud)

这是它的运行图片。 在此处输入图片说明

它在 PyPI 上,可以通过以下方式安装: pip install typed-argument-parser

完全披露:我是这个库的创建者之一。


agh*_*ast 11

考虑定义一个扩展类,argparse.Namespace它提供了你想要的类型提示:

class MyProgramArgs(argparse.Namespace):
    def __init__():
        self.somearg = 'defaultval' # type: str
Run Code Online (Sandbox Code Playgroud)

然后使用namespace=将其传递给parse_args:

def process_argv():
    parser = argparse.ArgumentParser()
    parser.add_argument('--somearg')
    nsp = MyProgramArgs()
    parsed = parser.parse_args(['--somearg','someval'], namespace=nsp)  # type: MyProgramArgs
    the_arg = parsed.somearg  # <- Pycharm should not complain
Run Code Online (Sandbox Code Playgroud)

  • Python 3.6 及更高版本不需要 `__init__`,而是添加带有类型提示的属性(无值)。```somearg: str``` (3认同)

hpa*_*ulj 5

我对 PyCharm 如何处理这些类型提示一无所知,但了解Namespace代码。

argparse.Namespace是一个简单的类;本质上是一个带有一些方法的对象,可以更容易地查看属性。为了便于单元测试,它有一个__eq__方法。您可以阅读argparse.py文件中的定义。

parser在可能的最普遍的方式命名空间相互作用-有getattrsetattrhasattr。因此,您几乎可以使用任何dest字符串,即使是无法使用.dest语法访问的字符串。

确保不要混淆add_argument type=参数;那是一个函数。

namespace按照另一个答案中的建议使用您自己的类(从头开始或子类化)可能是最好的选择。这在文档中简要描述。 命名空间对象。虽然我已经多次建议使用它来处理特殊的存储需求,但我还没有看到这样做太多。所以你必须进行实验。

如果使用子解析器,使用自定义命名空间类可能会中断,http: //bugs.python.org/issue27859

注意处理默认值。大多数argparse操作的默认默认值是None。如果用户没有提供这个选项,在解析后使用它来做一些特殊的事情是很方便的。

 if args.foo is None:
     # user did not use this optional
     args.foo = 'some post parsing default'
 else:
     # user provided value
     pass
Run Code Online (Sandbox Code Playgroud)

这可能会妨碍键入提示。无论您尝试何种解决方案,请注意默认设置。


Anamedtuple不能作为Namespace.

首先,正确使用自定义 Namespace 类是:

nm = MyClass(<default values>)
args = parser.parse_args(namespace=nm)
Run Code Online (Sandbox Code Playgroud)

也就是说,您初始化该类的一个实例,并将其作为参数传递。返回的args将是相同的实例,通过解析设置新的属性。

其次,namedtuple 只能创建,不能更改。

In [72]: MagicSpace=namedtuple('MagicSpace',['foo','bar'])
In [73]: nm = MagicSpace(1,2)
In [74]: nm
Out[74]: MagicSpace(foo=1, bar=2)
In [75]: nm.foo='one'
...
AttributeError: can't set attribute
In [76]: getattr(nm, 'foo')
Out[76]: 1
In [77]: setattr(nm, 'foo', 'one')    # not even with setattr
...
AttributeError: can't set attribute
Run Code Online (Sandbox Code Playgroud)

命名空间必须与getattr和 一起使用setattr

另一个问题namedtuple是它不设置任何类型的type信息。它只是定义字段/属性名称。所以静态类型不需要检查。

虽然很容易从 中获得预期的属性名称,但parser您无法获得任何预期的类型。

对于一个简单的解析器:

In [82]: parser.print_usage()
usage: ipython3 [-h] [-foo FOO] bar
In [83]: [a.dest for a in parser._actions[1:]]
Out[83]: ['foo', 'bar']
In [84]: [a.type for a in parser._actions[1:]]
Out[84]: [None, None]
Run Code Online (Sandbox Code Playgroud)

Actionsdest是正常的属性名称。但type不是该属性的预期静态类型。它是一个可以转换或不转换输入字符串的函数。这里的None意思是输入字符串按原样保存。

因为静态类型argparse需要不同的信息,所以没有一种简单的方法可以从另一个生成一个。

我认为你能做的最好的事情是创建你自己的参数数据库,可能在字典中,然后用你自己的实用程序函数创建 Namespace 类和 parsesr。

假设dd是带有必要键的字典。然后我们可以创建一个参数:

parser.add_argument(dd['short'],dd['long'], dest=dd['dest'], type=dd['typefun'], default=dd['default'], help=dd['help'])
Run Code Online (Sandbox Code Playgroud)

您或其他人将不得不提出一个 Namespace 类定义,用于default从这样的字典中设置(简单)和静态类型(困难?)。