On Mon, Aug 8, 2016 at 11:59 PM, Chris Angelico <ros...@gmail.com> wrote:
> On Tue, Aug 9, 2016 at 7:14 AM, Wolfgang Maier > <wolfgang.ma...@biologie.uni-freiburg.de> wrote: > > Right, I think a fairer comparison would be to: > > > > class ctx2: > > def __enter__(self): > > self.it = iter(self) > > return next(self.it) > > > > def __exit__(self, *args): > > try: > > next(self.it) > > except StopIteration: > > pass > > > > def __iter__(self): > > yield > > > > With this change alone the slowdown diminishes to ~ 1.7x for me. The > rest is > > probably the extra overhead for being able to pass exceptions raised > inside > > the with block back into the generator and such. > > I played around with a few other variants to see where the slowdown > is. They all work out pretty much the same as the above; my two > examples are both used the same way as contextlib.contextmanager is, > but are restrictive on what you can do. > > import timeit > import contextlib > import functools > > class ctx1: > def __enter__(self): > pass > def __exit__(self, *args): > pass > > @contextlib.contextmanager > def ctx2(): > yield > > class SimplerContextManager: > """Like contextlib._GeneratorContextManager but way simpler. > > * Doesn't reinstantiate itself - just reinvokes the generator > * Doesn't allow yielded objects (returns self) > * Lacks a lot of error checking. USE ONLY AS DIRECTED. > """ > def __init__(self, func): > self.func = func > functools.update_wrapper(self, func) > def __call__(self, *a, **kw): > self.gen = self.func(*a, **kw) > return self > def __enter__(self): > next(self.gen) > return self > def __exit__(self, type, value, traceback): > if type is None: > try: next(self.gen) > except StopIteration: return > else: raise RuntimeError("generator didn't stop") > try: self.gen.throw(type, value, traceback) > except StopIteration: return True > # Assume any instance of the same exception type is a proper > reraise > # This is way simpler than contextmanager normally does, and costs > us > # the ability to detect exception handlers that coincidentally > raise > # the same type of error (eg "except ZeroDivisionError: > print(1/0)"). > except type: return False > > # Check that it actually behaves correctly > @SimplerContextManager > def ctxdemo(): > print("Before yield") > try: > yield 123 > except ZeroDivisionError: > print("Absorbing 1/0") > return > finally: > print("Finalizing") > print("After yield (no exception)") > > with ctxdemo() as val: > print("1/0 =", 1/0) > with ctxdemo() as val: > print("1/1 =", 1/1) > #with ctxdemo() as val: > # print("1/q =", 1/q) > > @SimplerContextManager > def ctx3(): > yield > > class TooSimpleContextManager: > """Now this time you've gone too far.""" > def __init__(self, func): > self.func = func > def __call__(self): > self.gen = self.func() > return self > def __enter__(self): > next(self.gen) > def __exit__(self, type, value, traceback): > try: next(self.gen) > except StopIteration: pass > > @TooSimpleContextManager > def ctx4(): > yield > > class ctx5: > def __enter__(self): > self.it = iter(self) > return next(self.it) > > def __exit__(self, *args): > try: > next(self.it) > except StopIteration: > pass > > def __iter__(self): > yield > > t1 = timeit.timeit("with ctx1(): pass", setup="from __main__ import ctx1") > print("%.3f secs" % t1) > > for i in range(2, 6): > t2 = timeit.timeit("with ctx2(): pass", setup="from __main__ > import ctx%d as ctx2"%i) > print("%.3f secs" % t2) > print("slowdown: -%.2fx" % (t2 / t1)) > > > My numbers are: > > 0.320 secs > 1.354 secs > slowdown: -4.23x > 0.899 secs > slowdown: -2.81x > 0.831 secs > slowdown: -2.60x > 0.868 secs > slowdown: -2.71x > > So compared to the tight custom-written context manager class, all the > "pass it a generator function" varieties look pretty much the same. > The existing contextmanager factory has several levels of indirection, > and that's probably where the rest of the performance difference comes > from, but there is some cost to the simplicity of the gen-func > approach. > > My guess is that a C-implemented version could replace piles of > error-handling code with simple pointer comparisons (hence my > elimination of it), and may or may not be able to remove some of the > indirection. I'd say it'd land about in the same range as the other > examples here. Is that worth it? > > ChrisA > _______________________________________________ > Python-Dev mailing list > Python-Dev@python.org > https://mail.python.org/mailman/listinfo/python-dev > Unsubscribe: https://mail.python.org/mailman/options/python-dev/g.rodola% > 40gmail.com > Thanks for all this useful info. And I agree, some sort of slowdown should be expected because it's the price you pay for the additional flexibility that @contextmanager offers over a "raw" ctx manager class, which will always be faster as "it does less". -- Giampaolo - http://grodola.blogspot.com
_______________________________________________ Python-Dev mailing list Python-Dev@python.org https://mail.python.org/mailman/listinfo/python-dev Unsubscribe: https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com