Дата и время публикации:
Проблемы и решение
1. Суть проблемы
При использовании конструкторов классов с большим числом пораметров,например, образованных от базового класса argparse.Action модуля Python и который позволяет выполнять заданные действия через командную строку, выяснилось необходимость задействовать в них переменное число параметров или атрибутов с выборкой по ключевому значению.
В СPython так же как и языки программирования Си позволяет оперировать с переменным числом параметров, но без использования выборки по ключевым словам c использованием *args [3.1], пример которого показан в листинге 1.1
Листинге 1.1
>>> def print_sum(*args): ... print ( 'Sum: ', sum([ arg for arg in args ]) ) ... >>> print_sum(1,2,3,5,6) Sum: 17 >>>
Из которого видно, что *args на самом деле является списком, состоящим из однородных по типу элементов и сумму значений которых можно посчитать не особо задумываясь, т.к. членами являются целочисленные значения [3.2].
Поэтому данный вид представления переменных параметров функции, метода и конструктора класса может привести к сложным решениям в отличии от возможности выборки по ключевым словам, которые позволят перечислять неоднородные по типу аргументы и параметры.
2. Решение
В функции, методе или конструкторе для реализации переменного числа параметров или атрибутов с выборкой по ключевому значению, необходимо задать с помощью сдвоенных звездочек перед имени аргумента (параметра) , чтобы обозначить принадлежность к этому типу, как показано в листинге 2.1 (выделено жирным).
Листинге 2.1
... """ Defining the derivative classes from argparse.Action """ class _CommonAction(argparse.Action): """ Defining the Common Action to set an option handler """ def __init__(self, catcher, **kwargs ) : option_strings=self.__kwargs_get(kwargs,'option_strings') dest=self.__kwargs_get(kwargs,'dest') nargs=self.__kwargs_get(kwargs,'nargs') if self.__kwargs_get(kwargs,'help') is None : raise argparse.ArgumentTypeError("help value must always be assigned!") if self.__kwargs_get(kwargs, 'required') is None : raise argparse.ArgumentTypeError("required value must always be define explisity, True or False!") super(_CommonAction, self).__init__( option_strings=option_strings, dest=dest, nargs=0, help=kwargs['help'] ) self.catcher=catcher @staticmethod def __kwargs_get(kws,key): ... @classmethod def catcher(cls, **kwargs ) : raise argparse.ArgumentTypeError( 'direct call CommonAction.catcher() is illegal!' ) def __call__(self, parser, namespace, values, option_string=None): print('Run option: namespace=%r, values=%r, option_string=%r' % (namespace, values, option_string)) setattr(namespace, self.dest, values) if not self.catcher is None : sys.exit(self.catcher ( option_string=option_string, namespace=namespace, values=values ) ) sys.exit(1) class _TestRunAction(_CommonAction): """ Defining Action for option --test """ def __init__(self, option_strings, dest, nargs=None, **kwargs ): super(_TestRunAction, self).__init__( catcher=self.catcher, option_strings=option_strings, dest=dest, nargs=nargs ,**kwargs ) @classmethod def catcher(cls, **kwargs ): ...
В конструкторах класса shellenv.cmdlineargs._CommonAction и shellenv.cmdlineargs.TestRunAction переменное число параметров или атрибутов с выборкой по ключевому значению **kwargs используется для того, чтобы сократить или сделать выборочным передачу параметров конструктору базового класса argparse.Action:__init__(), перечень которых приведен в листинге 2.2, содержащий фрагмент с определением в нем числа параметров.
Листинге 2.2
... class Action(_AttributeHolder): ... def __init__(self, option_strings, dest, nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None): ...
Тоже самое делается в отношение передаваемого метода shellenv.cmdlineargs._TestRunAction:catcher(), который вызывается в переназначенном обработчике класса argparse.Action:__call__() при выполнении команды: ../../bin/py-shellenv --test
Пример извлечения передаваемых разношерстных атрибутов из **kwargs, приведен в методе shellenv.cmdlineargs._CommonAction:__kwargs_get(), который производит извлечение значения по ключевому слову, являющимся атрибутом в имени параметра конструктора shellenv.cmdlineargs._CommonAction:__init__() подмодуля shellenv.cmdlineargs [3.1]. Этот статический метод полностью показан в листинге 2.3
Листинге 2.3
class _CommonAction(argparse.Action): ... def __init__(self, catcher, **kwargs ) : option_strings=self.__kwargs_get(kwargs,'option_strings') ... @staticmethod def __kwargs_get(kws,key): for k in kws.keys(): if k == key : return kws[k] return None ... class _TestRunAction(_CommonAction): ... def __init__(self, option_strings, dest, nargs=None, **kwargs ): super(_TestRunAction, self).__init__( catcher=self.catcher, option_strings=option_strings, dest=dest, nargs=nargs ,**kwargs ) ...
Который иллюстрирует, что переданный атрибут option_strings в hellenv.cmdlineargs._TestRunAction:__init__() будет одним из ключевых значений ассоциотивного массива с выборкой по ключу, типа dict и возвращаемый значение для атрибута option_strings будет верно kwargs['option_strings'].
Если же вернуться от сложного примера, к более простому, который был показан в дампе 1.1, и изменить с точки зрения использования **kwargs, как показано в дампе 2.4
Дамп 2.4
>>> def print_sum(**kwargs): ... nargs=kwargs['nargs'] ... args=kwargs['args'] ... print ( 'Sum: ', sum([ args[i] for i in range(nargs) if i < len(args) ] ) ) ... >>> print_sum(nargs=3,args=[1,2,3,5,6]) Sum: 6 >>> print_sum(nargs=10,args=[1,2,3,5,6]) Sum: 17
В котором показан, пример регулирования количества используемых членов списка атрибута args в зависимости от числа указанных в атрибуте nargs
3. Библиография
3.1 Programiz — Python *args and **kwargs
3.2 Introduction to Programming in Python Lists[pdf]