code: def factory(**default_opts):
      
      def wrapper(decorator):
          @functools.wraps(decorator)
          def wrapper(fn=None, **opts):
              _opts = default_opts.copy()
              _opts.update(opts)
  
              def wrapper(fn):
                  @functools.wraps(fn)
                  def wrapper(*args, **kwargs):
                      return decorator(fn, _opts, *args, **kwargs)
                  return wrapper
  
              return wrapper if fn is None else wrapper(fn)
          return wrapper
      return wrapper
docs: Factory function to create decorators for tasks' run methods. Default options for the decorator
      function can be given in *default_opts*. The returned decorator can be used with or without
      function invocation. Example:
  
      .. code-block:: python
  
          @factory(digits=2)
          def runtime(fn, opts, task, *args, **kwargs):
              t0 = time.time()
              try:
                  return fn(task, *args, **kwargs)
              finally:
                  t1 = time.time()
                  diff = round(t1 - t0, opts["digits"])
                  print("runtime:")
                  print(diff)
  
          ...
  
          class MyTask(law.Task):
  
              @runtime
              def run(self):
                  ...
  
              # or
              @runtime(digits=3):
              def run(self):
                  ...
  
      .. note::
  
          Decorators might not have the expected behavior when used to decorate generator functions
          such as ``Task.run()`` methods that yield dynamic dependencies.