title: Class as a decorator for regular functions and coroutines in Python
body: <p>I am trying to create a class as a decorator that will apply a try-except block to the decorated function and retain some log of the exceptions. I want to apply the decorator to both regular functions as well as to coroutines.</p>
  
  <p>I have done the class-as-decorator and it works as designed for regular functions, but something is going wrong with coroutines. Below is some reduced code for a reduced version of the class-as-decorator and a couple of use cases:</p>
  
  <pre><code>import traceback
  import asyncio
  import functools
  
  class Try:
  
      def __init__(self, func):
          functools.update_wrapper(self, func)
          self.func = func
  
      def __call__(self, *args, **kwargs):
          print(f"applying __call__ to {self.func.__name__}")
          try:
              return self.func(*args, **kwargs)
          except:
              print(f"{self.func.__name__} failed")
              print(traceback.format_exc())
  
      def __await__(self, *args, **kwargs):
          print(f"applying __await__ to {self.func.__name__}")
          try:
              yield self.func(*args, **kwargs)
          except:
              print(f"{self.func.__name__} failed")
              print(traceback.format_exc())
  
  # Case 1
  @Try
  def times2(x):
      return x*2/0
  
  # Case 2
  @Try
  async def times3(x):
      await asyncio.sleep(0.0001)
      return x*3/0
  
  async def test_try():
      return await times3(10)
  
  def main():
      times2(10)
      asyncio.run(test_try())
      print("All done")
  
  if __name__ == "__main__":
      main()
  </code></pre>
  
  <p>Here's the output of the above code (with minor edits):</p>
  
  <pre><code>applying __call__ to times2
  times2 failed
  Traceback (most recent call last):
    File "&lt;ipython-input-3-37071526b2e6&gt;", line 14, in __call__
      return self.func(*args, **kwargs)
    File "&lt;ipython-input-3-37071526b2e6&gt;", line 30, in times2
      return x*2/0
  ZeroDivisionError: division by zero
  
  applying __call__ to times3
  Traceback (most recent call last):
    File "[...]/lib/python3.7/site-packages/IPython/core/interactiveshell.py", line 3296, in run_code
      exec(code_obj, self.user_global_ns, self.user_ns)
    File "&lt;ipython-input-3-37071526b2e6&gt;", line 46, in &lt;module&gt;
      main()
    File "&lt;ipython-input-3-37071526b2e6&gt;", line 43, in main
      asyncio.run(test_try())
    File "[...]/lib/python3.7/asyncio/runners.py", line 43, in run
      return loop.run_until_complete(main)
    File "[...]/lib/python3.7/asyncio/base_events.py", line 579, in run_until_complete
      return future.result()
    File "&lt;ipython-input-3-37071526b2e6&gt;", line 39, in test_try
      return await times3(10)
    File "&lt;ipython-input-3-37071526b2e6&gt;", line 36, in times3
      return x*3/0
  ZeroDivisionError: division by zero
  </code></pre>
  
  <p>Case 1 behaves normally: as expected <code>__call__</code> is called, then the decorated function which fails and the exception is caught. But I can't explain the behaviour of Case 2. Notice the missing "times3 failed" and "All done" print at the end. I can't reproduce the color coded output here but Case 1's traceback is regular print while Case 2's traceback is exception red (on PyCharm). The surprising part is that the <code>__call__</code> method was called instead of <code>__await__</code>.</p>
  
  <p>I have tried another class-as-decorator, one that keeps a tally of the number of times a function was called. That works just fine with <code>__call__</code> with either regular functions or coroutines.</p>
  
  <p>So what is actually going on? Do I need to somehow force the function to use <code>__await__</code>? How?</p>
  
  <p>I tried the following:</p>
  
  <pre><code>async def test_try2():
      func = await times3
  </code></pre>
  
  <p>with output</p>
  
  <pre><code>applying __await__ to times3
  times3 failed
  Traceback (most recent call last):
    File "&lt;ipython-input-5-5a85f988097e&gt;", line 22, in __await__
      yield self.func(*args, **kwargs)
  TypeError: times3() missing 1 required positional argument: 'x'
  </code></pre>
  
  <p>which does force using <code>__await__</code> but then what?</p>