Sometimes, you have an API:

    @abc.abstractmethod
    def get_property_value(self, prop):
        """Returns the value associated with the given property.

        Args:
            prop (DataProperty): The property.

        Returns:
            The value associated with the given property.

        Raises:
            PropertyError: If the property is not supported by this config
            source.
            LookupError: If the property is supported, but isn't available.
            ValueError: If the property doesn't have exactly one value.
        """
        raise PropertyError

and you don't want your API to mask bugs. How would it mask bugs? For example, if API consumers do an:

    try:
        x = foo.get_property_value(prop)
    except ValueError:
        # handle it

then the following implementation:

    def get_property_value(self, prop):
        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
    def get_property_values(self, prop):
        try:
            factory = self.get_supported_properties()[prop]
        except KeyError as exc: raise PropertyError from exc
        iterator = factory(self._obj)
        try:
            first = next(iterator)
        except StopIteration: return (x for x in ())
        except abdl.exceptions.ValidationError as exc: raise LookupError from exc         except LookupError as exc: raise RuntimeError from exc  # don't accidentally swallow bugs in the iterator
        return itertools.chain([first], iterator)

could cause you to accidentally swallow unrelated ValueErrors.

so instead this needs to be rewritten. you can't just unpack things from the iterator, you need to wrap the iterator into another iterator, that then converts ValueError into RuntimeError.

but if that ValueError is part of *your* API requirements... it's impossible to use!

so my proposal is that we get "exception spaces". they'd be used through the 'in' keyword, as in "except in" and "raise in".

for example:

    X = object()
    Y = object()

    def foo():
      raise LookupError in X

    def bar():
      try:
        foo()
      except LookupError in Y:
        print("bar: caught LookupError in Y, ignoring")

    def baz():
      try:
        bar()
      except LookupError in X:
        print("baz: caught LookupError in X, re-raising in Y")
        raise in Y

    def qux():
      try:
        baz()
      except LookupError in Y:
        print("qux: caught LookupError in Y, ignoring")

    qux()

    # would print:
    # ---
    # baz: caught LookupError in X, re-raising in Y
    # qux: caught LookupError in Y, ignoring
    # ---
    # note the lack of "bar"

(or perhaps "raise in X LookupError" and "except in Y LookupError" etc)

and then you can adjust the above implementations accordingly:
(btw, anyone knows how to tell apart a ValueError from a specific unpacking and a ValueError from an iterator being used in that unpacking?)

    def get_property_value(self, prop, espace=None):
        iterator = self.get_property_values(prop, espace=espace)
        try:
            # note: unpacking
            ret, = iterator
        except ValueError: raise in espace
        # except LookupError as exc: raise RuntimeError from exc  # no longer needed
        return ret
    def get_property_values(self, prop, espace=None):
        try:
            factory = self.get_supported_properties()[prop]
        except KeyError as exc: raise PropertyError in espace from exc
        iterator = factory(self._obj)
        try:
            first = next(iterator)
        except StopIteration: return (x for x in ())
        except abdl.exceptions.ValidationError as exc: raise LookupError in espace from exc         # except LookupError as exc: raise RuntimeError from exc  # no longer needed
        return itertools.chain([first], iterator)

as well as the caller:

    espace = object()
    try:
        x = foo.get_property_value(prop, espace=espace)
    except ValueError in espace:
        # handle it

I feel like this feature would significantly reduce bugs in python code, as well as significantly improve the error messages related to bugs. This would be even better than what we did with StopIteration! This would be comparable to Rust's Result type, where you can have Result<Result<YourValue, YourError>, TheirError> and the like (except slightly/a lot more powerful).
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@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/J76S2NBEVKX2MHZULELM22IHT5H7KLP3/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to