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

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

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

1. Суть проблемы

В первые задумался об использовании декораторов, когда попробавал использовать их для назначения собственных действий по обработки аргументы командной строки в проекте python-shellenv и потребовалось производить тиражирование argparse.Action в целях соблюдения принципа DRY(Don't Repeated You), которому принято придерживаться в Python

Как показано в листинге 2.1.2 и 2.2.1 мне пришлось воспользоваться некоторыми декораторами, такими как @classmethod и @staticmethod встроенных в Python. О создании своих собственных и использования @property думаю данный материал разбавить в будущем. А пока...

2. Решение

2.1 Декоратор @classmethod

Используется для неявного обращения к встроенной в Python функции classmethod(func), которая осуществляет определение метода класса (classmethod)[3.3], как показано в листинге 2.1.1

Дамп 2.1.1

class C(object):
    
   @classmethod
    def function(cls, arg1, arg2, ...):
       ...

В котором показано, что function() будет представлена в метода класса с неявным classmethod(func), которая вернет его.

При реализации обработке аргументов командной строки на базе класса argparse.Action декоратор classmethod используется для наделения действий к вызываемой опции и передаваемой вместе с ней аргумента, как показано в листинге 2.1.2

Листинг 2.1.2

class _CommonAction(argparse.Action):
     ...
     @classmethod
     def catcher(cls, **kwargs ) :
         raise argparse.ArgumentTypeError( 'direct call CommonAction.catcher() is illegal!' )         
     ... 
class _GetenvAllRunAction(_CommonAction):
      """
          Defining Action for option --getenv
      """
      def __init__(self, option_strings, dest, nargs=None, **kwargs ):
          super(_OutputAllRunAction, self).__init__( catcher=self.catcher, option_strings=option_strings, dest=dest, nargs=nargs ,**kwargs )

      @classmethod
      def catcher(cls, **kwargs ): 
          vkey = cls._kwargs_get(kwargs,'values')[0]
          ...

Также в листинге 2.1.2 показан пример того, что переопределение метода _CommonAction: catcher() в производном _CommonAction для метода класса _GetenvAllRunAction: не работает. Для наглядности рассмотрим раскрываюший суть использования декоратора @classmethod в листинге 2.1.3, из которого следует, что метод catcher() в родительском классе _CommonAction() и _GetenvAllRunAction() — два разных объекта, потому что встроенная в Python функция classmethod() возвратит новый объект с другим именем чем передаваемый ей.[3.8]

Листинг 2.1.3

		
     def catcher_func( **kwargs ) :
         ...
     catcher = classmethod(func)
     ...

Как показано в листинге 2.1.1 и 2.1.2, первый аргумент в объявляемой функцией методом класса должен быть CLS, конечно же в нижнем регистре.

Что такое CLS

Для понимания этого, нужно произвести сравнение cls c (it)self, чтобы осознать, что применение обоих оправдано, но использование одного из них подобным образом, как показано в дампе 2.2.1, по отношению к другому приводит к ошибке.

Листинг 2.2.1

>>> class MyClass:
...       def __init__(self):
...           print ('call the constructor to create a class instance')
...       def method(self):
...           return 'instance method called', self
...       @classmethod
...       def classmethod(cls):
...           return 'class method called', cls
... 
>>> MyClass.classmethod()
('class method called', )
>>> MyClass.method()
Traceback (most recent call last):
  File "", line 1, in 
TypeError: method() missing 1 required positional argument: 'self'

Попытка использования method() класса MyClass без конструирования, как это нормально прошло с classmethod(), вызвало ошибку, что было затем исправлено, как показано в дампе 2.2.2

Дамп 2.2.2

>>> instance=MyClass()
call the constructor to create a class instance
>>> instance.method()
('instance method called', <__main__.MyClass object at 0x7fc7c8dc7a60>)
>>> instance.classmethod()
('class method called', )

В тоже время, создание экземпляра объекта не повлило на поведение classmethod(), который является частью класса MyClass и от которого он образован.

Поэтому в разнице между cls и self кроется ответ на вопрос: "Что это такое CSL?" – указатель на метод класса объекта, который может быть использован отдельно от него, т.е. без создания экземпляра. В то время как антоним self, наоброт, может быть использован только с экземпляром объекта [3.8], порожденного от класса, в данном случае, MyClass, как и было показано в дампе 2.2.2

Таким образом, при определение методов класса через декоратор, существует риск использования их вне экземпляра объекта, что может отразится на некоторых переменных, которые инициируются, например, во время создание экземпляра объекта в конструкторе класса и попытка использования в них неменуемо приведет к ошибки

Поэтому декоратор classmethod и далее рассматриваемый staticmethod нужно использовать с осторожностью, не забывая об их "самостоятельном образе жизни".

2.2 Декоратор @staticmethod

Этот тип метода в отличии от предыдущего никогда не имеет self (признак отношения к типу экземпляра класса объекта) , а тем более cls (признак типа класса объекта), что выражается в невозможности модификации состояния объекта или класса. При этом нужно помнить, что статические методы ограничены в том, что доступ к данным ограничивается пространством имен (областью видимости) самого метода, как показано в листинге 2.2.1

Листинг 2.2.1

class _CommonAction(argparse.Action):
... 
@staticmethod
      def _kwargs_get(kws,key): 
          for k in kws.keys():
               if k == key :
                  return kws[k]
          return None
...
class _GetenvAllRunAction(_CommonAction):
      ...
      @classmethod
      def catcher(cls, **kwargs ): 
          vkey = cls._kwargs_get(kwargs,'values')[0]
...

Где методе _GetenvAllRunAction:_kwargs_get() оперирует только переменными внутри своего пространства имен (namespace), но может быть вызван внутри класса методом _GetenvAllRunAction: catcher(), который, в свою очередь, имеет декоратор @classmethod

3. Библиография

3.1 GeeksforGeeks — classmethod() in Python

3.2 Python: Frome None to ML — 6.3. Decorator Function with Cls

3.3 StackOverflow — Handle cls and self in method decorator

3.4 The Python Wiki — PythonDecorators

3.5 Python_syntax_and_semantics — Decorators

3.6 Python's @classmethod and @staticmethod Explained

3.7 Python's Instance, Class, and Static Methods Demystified

3.8 StackOverflow — How do I override a decorated method in Python?