nodes: 0, module; 1, function_definition; 2, function_name:SynchronizedClassMethod; 3, parameters; 4, comment:# pylint: disable=C1801; 5, block; 6, list_splat_pattern; 7, dictionary_splat_pattern; 8, expression_statement; 9, comment:# Filter the names (remove empty ones); 10, expression_statement; 11, if_statement; 12, if_statement; 13, function_definition; 14, comment:# Return the wrapped method; 15, return_statement; 16, identifier:locks_attr_names; 17, identifier:kwargs; 18, comment:"""
      A synchronizer decorator for class methods. An AttributeError can be raised
      at runtime if the given lock attribute doesn't exist or if it is None.
  
      If a parameter ``sorted`` is found in ``kwargs`` and its value is True,
      then the list of locks names will be sorted before locking.
  
      :param locks_attr_names: A list of the lock(s) attribute(s) name(s) to be
                               used for synchronization
      :return: The decorator method, surrounded with the lock
      """; 19, assignment; 20, not_operator; 21, block; 22, boolean_operator; 23, comment:# Sort the lock names if requested; 24, comment:# (locking always in the same order reduces the risk of dead lock); 25, block; 26, function_name:wrapped; 27, parameters; 28, block; 29, identifier:wrapped; 30, identifier:locks_attr_names; 31, list_comprehension; 32, identifier:locks_attr_names; 33, raise_statement; 34, comparison_operator:"sorted" not in kwargs; 35, subscript; 36, expression_statement; 37, expression_statement; 38, identifier:method; 39, expression_statement; 40, decorated_definition; 41, return_statement; 42, identifier:lock_name; 43, for_in_clause; 44, if_clause; 45, call; 46, string:"sorted"; 47, identifier:kwargs; 48, identifier:kwargs; 49, string:"sorted"; 50, assignment; 51, call; 52, comment:"""
          The wrapping method
  
          :param method: The wrapped method
          :return: The wrapped method
          :raise AttributeError: The given attribute name doesn't exist
          """; 53, decorator; 54, function_definition; 55, identifier:synchronized; 56, identifier:lock_name; 57, identifier:locks_attr_names; 58, identifier:lock_name; 59, identifier:ValueError; 60, argument_list; 61, identifier:locks_attr_names; 62, call; 63, attribute; 64, argument_list; 65, call; 66, function_name:synchronized; 67, parameters; 68, block; 69, string:"The lock names list can't be empty"; 70, identifier:list; 71, argument_list; 72, identifier:locks_attr_names; 73, identifier:sort; 74, attribute; 75, argument_list; 76, identifier:self; 77, list_splat_pattern; 78, dictionary_splat_pattern; 79, expression_statement; 80, comment:# Raises an AttributeError if needed; 81, expression_statement; 82, expression_statement; 83, expression_statement; 84, try_statement; 85, identifier:locks_attr_names; 86, identifier:functools; 87, identifier:wraps; 88, identifier:method; 89, identifier:args; 90, identifier:kwargs; 91, comment:"""
              Calls the wrapped method with a lock
              """; 92, assignment; 93, assignment; 94, assignment; 95, comment:# Lock; 96, block; 97, finally_clause; 98, identifier:locks; 99, list_comprehension; 100, identifier:locked; 101, call; 102, identifier:i; 103, integer:0; 104, for_statement; 105, comment:# Use the method; 106, return_statement; 107, comment:# Unlock what has been locked in all cases; 108, block; 109, call; 110, for_in_clause; 111, attribute; 112, argument_list; 113, identifier:lock; 114, identifier:locks; 115, block; 116, call; 117, for_statement; 118, expression_statement; 119, delete_statement; 120, identifier:getattr; 121, argument_list; 122, identifier:attr_name; 123, identifier:locks_attr_names; 124, identifier:collections; 125, identifier:deque; 126, if_statement; 127, comment:# Get the lock; 128, expression_statement; 129, expression_statement; 130, expression_statement; 131, identifier:method; 132, argument_list; 133, identifier:lock; 134, identifier:locked; 135, block; 136, call; 137, subscript; 138, identifier:self; 139, identifier:attr_name; 140, comparison_operator:lock is None; 141, comment:# No lock...; 142, block; 143, augmented_assignment; 144, call; 145, call; 146, identifier:self; 147, list_splat; 148, dictionary_splat; 149, expression_statement; 150, attribute; 151, argument_list; 152, identifier:locks; 153, slice; 154, identifier:lock; 155, None; 156, raise_statement; 157, identifier:i; 158, integer:1; 159, attribute; 160, argument_list; 161, attribute; 162, argument_list; 163, identifier:args; 164, identifier:kwargs; 165, call; 166, identifier:locked; 167, identifier:clear; 168, call; 169, identifier:lock; 170, identifier:acquire; 171, identifier:locked; 172, identifier:appendleft; 173, identifier:lock; 174, attribute; 175, argument_list; 176, identifier:AttributeError; 177, argument_list; 178, identifier:lock; 179, identifier:release; 180, call; 181, attribute; 182, argument_list; 183, string:"Lock '{0}' can't be None in class {1}"; 184, identifier:format; 185, subscript; 186, attribute; 187, identifier:locks_attr_names; 188, identifier:i; 189, call; 190, identifier:__name__; 191, identifier:type; 192, argument_list; 193, identifier:self
code: def SynchronizedClassMethod(*locks_attr_names, **kwargs):
      # pylint: disable=C1801
      """
      A synchronizer decorator for class methods. An AttributeError can be raised
      at runtime if the given lock attribute doesn't exist or if it is None.
  
      If a parameter ``sorted`` is found in ``kwargs`` and its value is True,
      then the list of locks names will be sorted before locking.
  
      :param locks_attr_names: A list of the lock(s) attribute(s) name(s) to be
                               used for synchronization
      :return: The decorator method, surrounded with the lock
      """
      # Filter the names (remove empty ones)
      locks_attr_names = [
          lock_name for lock_name in locks_attr_names if lock_name
      ]
  
      if not locks_attr_names:
          raise ValueError("The lock names list can't be empty")
  
      if "sorted" not in kwargs or kwargs["sorted"]:
          # Sort the lock names if requested
          # (locking always in the same order reduces the risk of dead lock)
          locks_attr_names = list(locks_attr_names)
          locks_attr_names.sort()
  
      def wrapped(method):
          """
          The wrapping method
  
          :param method: The wrapped method
          :return: The wrapped method
          :raise AttributeError: The given attribute name doesn't exist
          """
  
          @functools.wraps(method)
          def synchronized(self, *args, **kwargs):
              """
              Calls the wrapped method with a lock
              """
              # Raises an AttributeError if needed
              locks = [getattr(self, attr_name) for attr_name in locks_attr_names]
              locked = collections.deque()
              i = 0
  
              try:
                  # Lock
                  for lock in locks:
                      if lock is None:
                          # No lock...
                          raise AttributeError(
                              "Lock '{0}' can't be None in class {1}".format(
                                  locks_attr_names[i], type(self).__name__
                              )
                          )
  
                      # Get the lock
                      i += 1
                      lock.acquire()
                      locked.appendleft(lock)
  
                  # Use the method
                  return method(self, *args, **kwargs)
  
              finally:
                  # Unlock what has been locked in all cases
                  for lock in locked:
                      lock.release()
  
                  locked.clear()
                  del locks[:]
  
          return synchronized
  
      # Return the wrapped method
      return wrapped