Я использую пакет Python argparse, с простой конфигурацией
Когда я пытаюсь добавить в свой парсер formatter_class=argparse.MetavarTypeHelpFormatter
, получаю ошибку:
AttributeError: 'NoneType' object has no attribute '__name__'
кто-нибудь еще сталкивался с этим? и может быть знаете почему?
это мой код:
import argparse
argParser = argparse.ArgumentParser(
formatter_class=argparse.MetavarTypeHelpFormatter
)
argParser.add_argument("models", default='', nargs='+',
help = "Which model(s) to execute")
args = argParser.parse_args()
print(args)
Когда я пробую без добавления formatter_class
, все работает.
Он также работает с другими классами форматирования, например formatter_class=argparse.RawDescriptionHelpFormatter
Я также пробовал это с атрибутом prog
и без него.
Краткий ответ: аргумент type
для add_argument
по умолчанию равен None
, а не str
.
Менее короткий ответ: тип None
ведет себя как тип str
, но не работает с MetaVarTypeHelpFormatter
.
MetaVarTypeHelpFormatter
требует явного типа для каждого аргумента.
argParser.add_argument("models", default='', nargs='+', type=str,
help = "Which model(s) to execute")
Его документированная цель — использовать имя атрибута type
аргумента в качестве метапеременной, и по умолчанию этот атрибут имеет значение None
.
Хотя тип аргумента по умолчанию — str
, это просто означает, что значение str
, взятое из списка аргументов, сохраняется, если атрибут type
аргумента равен None
, или преобразуется в заданный тип, если это не None
.
>>> p = argparse.ArgumentParser()
>>> print(p.add_argument("foo").type)
None
>>> print(p.add_argument("foo", type=int).type)
<class 'int'>
Каждый синтаксический анализатор поддерживает реестр типов, что означает, что вы можете добавлять свои собственные произвольные сопоставления значений к вызываемым объектам для создания типов. Например,
>>> import argparse
>>> p = argparse.ArgumentParser()
>>> p.register('type', 'lowercase', lambda x: x.lower())
>>> p.add_argument("foo", type='lowercase')
_StoreAction(option_strings=[], dest='foo', nargs=None, const=None, default=None, type='lowercase', choices=None, required=True, help=None, metavar=None)
>>> p.parse_args(["HELLO WORLD"])
Namespace(foo='hello world')
Первоначально запись в реестре типов сопоставляется None
с
def identity(string):
return string
поэтому аргумент с «типом» None
в противном случае ведет себя так, как если бы вы указали str
явно.
Реестр типов не документирован и, за исключением этой единственной записи, больше не используется, поэтому неясно, почему он существует. Возможно, у автора были планы использовать его чаще, но они так и не осуществились. (Обратите внимание, что каждый парсер также имеет реестр действий, который используется чаще, поэтому вы можете указать действия, такие как 'append'
и 'store_const'
, вместо прямого указания закрытых классов, таких как _AppendAction
и _StoreConstAction
.)
Вам нужно передать type=
в add_argument
:
MetavarTypeHelpFormatter использует имя аргумента
type
для каждого аргумент как отображаемое имя для его значений
Вы не уточнили, поэтому получается None
.
Поскольку в вашем примере это тоже по умолчанию ''
, вы можете сделать:
argParser.add_argument(
"models",
default = "",
nargs = "+",
help = "Which model(s) to execute",
type=str,
)
Это похоже на ошибку. Предполагается, что type
по умолчанию имеет значение str
для action
по умолчанию, поэтому он должен вести себя так, даже если type
опущен. Чрезвычайно глупо, если type=str
можно и нужно опускать при обычных обстоятельствах (точно так же, как мы опускаем action='store'
, required=True
и т. д.), но для MetavarTypeHelpFormatter
это необходимо.
argparse
действительно гибко; Аргумент type
может быть любым значением, а не только типом, а вызываемый объект, используемый для преобразования аргумента, ищется в реестре. По какой-то причине реестр сопоставляет None
с функцией идентификации, а не просто сопоставляет str
с str
.
(Непонятно, почему он такой гибкий; похоже, ничто другое не использует реестр типов, и он не задокументирован как что-то, что пользователи могли бы дополнять. Он похож на реестр действий, который сопоставляет, например, строку типа 'append'
с классом _AppendAction
, но гораздо реже используется.)
Я предполагаю, что type
был добавлен в реестр с самого начала, чтобы можно было добавить больше строк типов. Но, за исключением нескольких общих функций, таких как int
и float
, пользователи не используют множество функций специального типа. Позже кто-то добавил этот форматтер Metavar, но не протестировал его тщательно. Поскольку указать метавар так легко, я не уверен, что этот подкласс необходим (или часто используется). В любом случае, если мы вносим изменения, это должно быть связано с подклассом форматтера, а не с использованием реестра. parser._getvalue
отлично справляется с реестром.
Из документации Python:
MetavarTypeHelpFormatter использует имя аргумента типа для каждого аргумента в качестве отображаемого имени для его значений (вместо использования dest, как это делает обычный форматтер)
Итак, вам нужно добавить аргумент, например, как
p.add_argument('models', default='', nargs='+', help='no help', type=str)
Но на самом деле это ошибка в argparse, которая позже выдает исключение: оно должно было вызвать это исключение сразу же, когда вы добавили аргумент без указания типа.
type
по умолчанию None
он же str
отлично работает для синтаксического анализа. Просто этот подкласс Formatter использует слишком упрощенный подход к получению метавара по умолчанию. Я не думаю, что этот подкласс Formatter широко используется.
С использованием nargs='+'
проблем нет. Это с вашим тестовым примером. parse_args
должен получить список строк, например. p.parse_args(['abc xyz'])
или p.parse_args('abc xyz'.split())
Вы правы - я забыл об этом.
Я удалил комментарий о nargs='+' из своего предыдущего поста.
p._get_value
использует
type_func = self._registry_get('type', action.type, action.type)
который вернет str
по умолчанию type
.
Вместо этого этот подкласс Formatter использует
return action.type.__name__
чтобы получить тип, игнорируя _registry
.
Я согласен, что использование _registry
вместо type
, вероятно, является недостаточно используемой функцией. реестр в основном используется для action
. Если пользователям нужна настройка type
, они могут определить необходимую функцию и использовать ее напрямую. Нет реальной необходимости добавлять его в реестр.
За все годы, что я следил за проблемами argparse
, я не припомню, чтобы видел эту проблему раньше. Это говорит о том, что мало кто использовал MetavarTypeHelpFormatter
, а если и использовал, то имел привычку использовать type=str
. Установка metavar
напрямую также является хорошей альтернативой.
Обратите внимание, что в формате по умолчанию default_metavar
является производным от action.dest
либо как есть, либо в верхнем регистре.
MetavarTypeHelpFormatter
класс не получил широкого распространения. Это простая модификация базового форматтера. Как показывают ответы, проблема заключается в том, как реализовано значение по умолчаниюtype
. Это прекрасно работает для других целей,