Advertisement
mwchase

Extremely normal Python code

Feb 26th, 2019
118
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 3.42 KB | None | 0 0
  1. """Support for tail calls in Python, including context managers.
  2.  
  3. Do not use this code in production.
  4. """
  5.  
  6. import functools
  7. import inspect
  8. import sys
  9.  
  10. # pylint: disable=too-few-public-methods
  11.  
  12.  
  13. class _ReifiedCall:
  14.     def __init__(self, function, args, kwargs):
  15.         self.function = function
  16.         self.args = args
  17.         self.kwargs = kwargs
  18.  
  19.  
  20. class _Tail(BaseException):
  21.     def __init__(self, reified_call, *args, **kwargs):
  22.         super().__init__(reified_call, *args, **kwargs)
  23.         self.reified_call = reified_call
  24.         self.exits = []
  25.  
  26.  
  27. class _DeferExits:
  28.     def __init__(self):
  29.         self.exits = []
  30.  
  31.     def __enter__(self):
  32.         return self.exits
  33.  
  34.     def __exit__(self, exc_type, exc_value, traceback):
  35.         for exit_group in reversed(self.exits):
  36.             for exit_ in exit_group:
  37.                 if exit_(exc_type, exc_value, traceback):
  38.                     exc_type = exc_value = traceback = None
  39.         return exc_type is None
  40.  
  41.  
  42. class tail_call:  # pylint: disable=invalid-name  # noqa
  43.     """Wrap a function or context manager to support tail calls."""
  44.  
  45.     __wrapped__ = None
  46.  
  47.     def __new__(cls, thing, *args, **kwargs):
  48.         """Return a tail-call proxy."""
  49.         if isinstance(thing, cls):
  50.             return thing
  51.         return functools.wraps(thing)(super().__new__(cls, *args, **kwargs))
  52.  
  53.     def __get__(self, instance, owner):
  54.         """Return tail-call proxy if wrapped func is descriptor, else self."""
  55.         getter = inspect.getattr_static(self.__wrapped__, "__get__", None)
  56.         if getter is None:
  57.             return self
  58.         return tail_call(getter(self.__wrapped__, instance, owner))
  59.  
  60.     def __call__(self, *args, **kwargs):
  61.         """Return the result of calling the wrapped function.
  62.  
  63.        Tail calls of functions wrapped with tail_call will be eliminated.
  64.        Non-tail calls of such functions should be handled with the "call"
  65.        method.
  66.        """
  67.         frame = sys._getframe()  # pylint: disable=protected-access
  68.         reified_call = _ReifiedCall(self.__wrapped__, args, kwargs)
  69.         grandparent_code = None
  70.         try:
  71.             grandparent_code = frame.f_back.f_back.f_code
  72.         except AttributeError:
  73.             pass
  74.         if grandparent_code == frame.f_code:
  75.             raise _Tail(reified_call)
  76.         with _DeferExits() as exits:
  77.             while True:
  78.                 try:
  79.                     return reified_call.function(
  80.                         *reified_call.args, **reified_call.kwargs
  81.                     )
  82.                 except _Tail as tail:
  83.                     reified_call = tail.reified_call
  84.                     exits.append(tail.exits)
  85.  
  86.     @property
  87.     def call(self):
  88.         """Return the result of calling the wrapped function directly.
  89.  
  90.        This avoids the tail-call machinery.
  91.        """
  92.         return self.__wrapped__
  93.  
  94.     def __enter__(self):
  95.         """Enter the wrapped context."""
  96.         return self.__wrapped__.__enter__()
  97.  
  98.     def __exit__(self, exc_type, exc_value, traceback):
  99.         """Exit the wrapped context, unless this is a tail call.
  100.  
  101.        In that case, defer exiting the context until the tail calls complete.
  102.        """
  103.         exit_func = self.__wrapped__.__exit__
  104.         if exc_type is not _Tail:
  105.             return exit_func(exc_type, exc_value, traceback)
  106.         exc_value.exits.append(exit_func)
  107.         return None
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement