× К оглавлению На главную Об авторе

Дата и время публикации:    

Проблемы и решение

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]

3.3 Python Dict and File

3.4 argparse's source code on the GitHub

3.5 Использование словарей в python