有没有办法让Python静态分析器(例如在PyCharm和其他IDE中)在argparse.Namespace
对象上拾取类型提示?例如:
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'
如果我删除内联注解中的类型声明,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.
有什么想法吗?
更新
受下面Austin's answer的启发,我能找到的最简单的解决方案是使用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!
虽然这是令人满意的,但我仍然不喜欢重复参数名称。如果参数列表显著增长,更新两个位置将是乏味的。理想的做法是以某种方式从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
我还没有在argparse
模块中找到任何可以实现这一点的东西,而且我仍然不确定 * 任何 * 静态分析工具是否足够聪明,可以获得这些值,而不会使IDE陷入停顿。
仍在搜寻中...
更新2
根据hpaulj的注解,我所能找到的与上述方法最接近的东西是从解析器的每个_action
中提取dest
属性,该方法将“神奇地”提取解析对象的属性。
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
但是这仍然不会导致在静态分析中标记属性错误。如果我在parser.parse_args
调用中传递namespace=MagicNamespace
,也是如此。
7条答案
按热度按时间rkkpypqq1#
Typed argument parser正是为此目的而创建的。它 Package 了
argparse
。您的示例实现为:这是一张它工作时的图片。
它位于PyPI上,可以通过以下方式安装:
pip install typed-argument-parser
个充分披露:我是这个图书馆的创建者之一。
5t7ly7z52#
请考虑定义
argparse.Namespace
的扩充类别,以提供您想要的型别提示:然后使用
namespace=
将其传递给parse_args
:368yc8dk3#
我不知道PyCharm是如何处理这些类型提示的,但我理解
Namespace
代码。argparse.Namespace
是一个简单类;本质上是一个对象,它有几个方法,使它更容易查看属性。为了便于单元测试,它有一个__eq__
方法。你可以在argparse.py
文件中阅读定义。parser
以最通用的方式与命名空间交互--getattr
、setattr
、hasattr
。因此,几乎可以使用任何dest
字符串,即使是那些不能用.dest
语法访问的字符串。请确保您没有混淆
add_argument
type=
参数;这是一个函数使用你自己的
namespace
类(从头开始或子类化)可能是最好的选择。这在文档中有简要的描述。命名空间对象。我还没有看到它做了很多,尽管我已经建议过几次它来处理特殊的存储需求。所以你必须进行实验。如果使用子解析器,则使用自定义Namespace类可能会中断,http://bugs.python.org/issue27859
注意默认值的处理。大多数
argparse
操作的默认值是None
。如果用户没有提供此选项,那么在解析后使用此选项来做一些特殊的事情是很方便的。这可能会妨碍类型提示。无论你尝试什么解决方案,都要注意默认值。
namedtuple
不能像Namespace
那样工作。首先,自定义Namespace类的正确用法是:
也就是说,初始化该类的一个示例,并将其作为参数传递。返回的
args
将是同一个示例,并通过解析设置了新的属性。第二,命名元组只能创建,不能更改。
命名空间必须与
getattr
和setattr
一起使用。namedtuple
的另一个问题是,它没有设置任何type
信息,它只定义了字段/属性名,所以静态类型没有什么需要检查的。虽然很容易从
parser
中获得预期的属性名称,但无法获得任何预期的类型。对于简单的解析器:
动作
dest
是一般属性名称。但type
不是该属性的预期静态类型。它是一个可能会也可能不会转换输入字串的函数。这里的None
表示输入字串会储存成原来的样子。因为静态类型和
argparse
需要不同的信息,所以没有一种简单的方法可以从另一个生成一个。我认为最好的办法是创建自己的参数数据库,可能是在字典中,然后使用自己的实用函数从数据库中创建Namespace类和parsesr。
假设
dd
是一个字典,其中包含了所需的键,那么我们可以创建一个参数:您或其他人将必须从这样的字典中提供一个Namespace类定义来设置
default
(easy)和静态类型(hard?)。pnwntuvh4#
如果你在一个情况下,你可以从头开始有有趣的解决方案,如
然而,在我的案例中,它们并不是理想的解决方案,因为:
1.我有许多现有的基于
argparse
的CLI,我无法使用这种args-inferred-from-types方法全部重写它们。1.当从类型推断参数时,支持普通
argparse
支持的所有高级CLI功能可能会很棘手。1.在多个CLI中重用公共参数定义通常比在普通命令式参数解析中更容易。
因此,我开发了一个小型库typed_argparse,它允许引入类型化参数,而不需要太多的重构,其思想是添加一个从特殊的
TypedArg
类派生的类型,然后简单地 Package 普通的argparse.Namespace
对象:这种方法稍微违反了单一真实源原则,但是库会执行完整的运行时验证,以确保类型注解与argparse类型匹配,并且它只是一个非常简单的选项,可以迁移到类型化的CLI。
ev7lccsx5#
大多数的答案都涉及到使用另一个包来处理输入。只有在没有像我将要提出的这样简单的解决方案时,这才是一个好主意。
步骤1.类型声明
首先,定义数据类中每个参数的类型,如下所示:
步骤2.参数声明
然后,您可以使用匹配的参数任意设置解析器。例如:
步骤3.解析参数
最后,我们以静态类型检查器知道每个参数的类型的方式解析参数:
现在,类型检查器知道
my_args
是MyProgramArgs
类型,因此它确切地知道哪些字段可用以及它们的类型是什么。zzwlnbp86#
另一种方法,如果你有很少的参数,这可能是理想的,如下所示。
首先创建一个设置解析器并返回名称空间的函数。例如:
然后定义一个main函数,它接受上面单独声明的参数;就像这样。
当你调用你的main时,你可以这样做:
从main开始,静态类型检查器将正确识别变量
a
和b
,尽管不再有包含所有参数的对象,这可能是好事也可能是坏事,具体取决于您的用例。mf98qq947#
一个
super
的解决方案只提示parse_args
方法的NameSpace
返回值。