
On 2021-09-29 10:09 p.m., Chris Angelico wrote:
On Thu, Sep 30, 2021 at 11:03 AM Soni L. <fakedme+py@gmail.com> wrote:
So uh, this is a hardly at all fleshed out idea, but one thing we really don't like about python is having to do stuff like this so as to not swallow exceptions:
def a_potentially_recursive_function(some, args): """ Does stuff and things. Raises ExceptionWeCareAbout under so and so conditions. """ try: some.user_code() except ExceptionWeCareAbout as exc: raise RuntimeError from exc code_we_assume_is_safe() if args.something and some_condition: raise ExceptionWeCareAbout
It'd be nice if there was a way to... make this easier to deal with. Perhaps something where, something like this:
def a_potentially_recursive_function(some, args) with ExceptionWeCareAbout: """ Does stuff and things. Raises ExceptionWeCareAbout under so and so conditions. """ some.user_code() code_we_assume_is_safe() if args.something and some_condition: raise ExceptionWeCareAbout
becomes:
def a_potentially_recursive_function(some, args): """ Does stuff and things. Raises ExceptionWeCareAbout under so and so conditions. """ try: some.user_code() code_we_assume_is_safe() if args.something and some_condition: let_exception_through = True raise ExceptionWeCareAbout except ExceptionWeCareAbout as exc: if let_exception_through: raise else: raise RuntimeError from exc
(or equivalent)
and something like:
def foo() with Bar: try: baz() except Bar: raise
becomes:
def foo(): try: try: baz() except Bar: allow_exception = True raise except Bar as exc: if allow_exception: raise else: raise RuntimeError from exc
(thus allowing explicit exception propagation)
Additionally, note that it would only apply to the raise itself - something like `raise may_raise_the_same_error()` would desugar as if:
exception = may_raise_the_same_error() allow_exception = True raise exception
Obviously this doesn't solve exception handling, doesn't require the caller to catch the exceptions, etc etc. It does, however, encourage better exception hygiene. Chances are something like this would significantly reduce the amount of swallowed exceptions, if it gets widely adopted. We know something like this would've saved us a lot of trouble, so we're sharing the idea in the hopes it can, in the future, help others.
Can you give a realistic example of how this works, and how you could accidentally leak the exact same exception that you'd be intentionally raising? It looks to me like you possibly should be splitting the function into the recursive part and the setup part, where only the setup is capable of raising that exception.
I've seen a LOT of bad Python code caused by an assumption that "recursion" must always mean "calling the external API". A much better model, in a lot of cases, is:
def _somefunc_recursive(x, y, z): ... _somefunc_recursive(x, y + 1, z - 1)
def somefunc(x, y, z=4): ... _somefunc_recursive(x, y, z) return someresult
def get_property_value(self, prop): """Returns the value associated with the given property. If duplicated, an earlier value should override a later value. Args: prop (DataProperty): The property. Returns: The value associated with the given property. Raises: PropertyError: If the property is not supported by this data source. LookupError: If the property is supported, but isn't available. ValueError: If the property doesn't have exactly one value. """ iterator = self.get_property_values(prop) try: # note: unpacking ret, = iterator except LookupError as exc: raise RuntimeError from exc # don't accidentally swallow bugs in the iterator return ret (real code. spot all the exception-related time-bombs! the class this is in... well, one of its subclasses wraps other instances of this class. it gets Fun when you add up all the ways downstream code can be wrong and interact badly with this. in this case, the main concern is about swallowing ValueError, altho no harm would be caused by also wrapping unexpected PropertyErrors into RuntimeErrors. anyway, this thing is a bit of a maintenance nightmare, but this idea would help a lot.) we believe this is how we'd write it with this idea: def get_property_value(self, prop) with ValueError, LookupError, PropertyError: """Returns the value associated with the given property. If duplicated, an earlier value should override a later value. Args: prop (DataProperty): The property. Returns: The value associated with the given property. Raises: PropertyError: If the property is not supported by this data source. LookupError: If the property is supported, but isn't available. ValueError: If the property doesn't have exactly one value. """ try: iterator = self.get_property_values(prop) except PropertyError, LookupError: raise # note: unpacking try: ret = next(iterator) except StopIteration as exc: raise ValueError from exc try: next(iterator) raise ValueError except StopIteration: pass return ret we strongly believe this would fix the relevant time-bombs :)
ChrisA _______________________________________________ Python-ideas mailing list -- python-ideas@python.org To unsubscribe send an email to python-ideas-leave@python.org https://mail.python.org/mailman3/lists/python-ideas.python.org/ Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/XDLYGF... Code of Conduct: http://python.org/psf/codeofconduct/