tl;dr: I show how the goal of Soni L's exception spaces can be addressed today, via less intrusive means. (Assuming I understand their proposal, that is.)

On 4/11/2020 7:49 AM, Soni L. wrote:


On 2020-04-11 8:38 a.m., Chris Angelico wrote:
On Sat, Apr 11, 2020 at 9:30 PM Soni L. <fakedme...@gmail.com> wrote:
>
> alternatively, they can let their caller
> handle it by having the espace come from the caller.
>
>      def bar(espace=None):
>          x = foo(espace=espace)
>
> and then the caller is expected to handle it, *if they so choose*.

If a function just replicates down the espace parameter, how is that
even slightly different from the current situation? You STILL won't be
able to know where the exception came from. I am at a complete loss to
understand how all of this overhead (and there is a LOT of overhead)
even solves the problem.

And if a function doesn't replicate it down, what exception space
should it be using? How would it know?

That just means you completely misunderstood the proposal. None would still work (and be the default). raising things in None would be the same as using python today.

the special things happen when you don't raise in None. yeah sure your function has an espace but if you don't use it... the caller won't catch those errors.

    def bar(espace=None):
        x = foo(espace=espace)
        y = x['bug']

if called with an espace other than None, the explicitly declared errors from foo will be raised in the given espace. but the indexing in x['bug']? that'll just give you a KeyError in None, indicating there's a bug in bar.
In your proposal, you're requiring all library code that wants to use exception spaces to be modified to accept your exception space parameter, and then to change where they raise exception space exceptions. Is that correct? I'm assuming so for this discussion. Further, client code that wants to participate in these exception spaces also need to change how they call the library code and how they catch exceptions.

As others have suggested, this can be achieved today with less disruption just by subclassing exceptions in the library. Consider a library that exports a single function that might raise a KeyError, and that's defined as part of its interface. In my example below, it will always raise that error. Rather than change the function to take a new parameter and change how it raises exceptions, it could work as follows:

#####################
# library code
class LibExceptionBase(Exception): pass

class LibKeyError(LibExceptionBase, KeyError): pass

# Other library API exceptions.
class LibValueError(LibExceptionBase, ValueError): pass

def lib_fn(a):
    raise LibKeyError(f'{a!r} not found')

#####################
# User code

mymap = {'x': 4}

# Case 0: I have a "local" problem in calling the library, which raises
# KeyError.  This would be similar to your code which passes in an espace.
try:
    lib_fn(mymap['bad key'])
except LibExceptionBase as ex:
    print('0 library exception', ex)
except KeyError as ex:
    print('0 normal exception', ex)

# Case 1: I call the library, which raises LibKeyError.  This would be similar
# to your code which passes in an espace.
try:
    lib_fn(mymap['x'])
except LibExceptionBase as ex:
    print('1 library exception', ex)
except KeyError as ex:
    print('1 normal exception', ex)

# Case 2: Existing code that doesn't know about exception spaces, and has a
# "local" problem that raises KeyError.  This would be code that passes in
# espace=None (via the default) in your examples.
try:
    lib_fn(mymap['bad key'])
except KeyError as ex:
    print('2 Some KeyError', ex)

# Case 3: Existing code that doesn't know about exception spaces, and the
# library raises its a KeyErorr.  This would be code that passes in espace=None
# (via the default) in your examples.
try:
    lib_fn(4)
except KeyError as ex:
    print('3 Some KeyError', ex)

#####################

This produces:

0 normal exception 'bad key'
1 library exception '4 not found'
2 Some KeyError 'bad key'
3 Some KeyError '4 not found'

In cases 2 and 3 there likely wouldn't be an exception handler, and the error would just propagate. I'm catching it here just to show that it's a KeyError that's propagating, although in case 3 it's really a LibKeyError (a subclass of KeyError).

In my example, the library code that wants to use exception spaces needs to change (same as your proposal), the client code that wants to use exception spaces needs to change (same as your proposal), and the client code that doesn't want to exception spaces doesn't need to change (same as your proposal). In my case, the library changes involve defining exception classes and require the library to catch and re-throw various exceptions. In your proposal, the library changes involve new function parameters and require the library to catch and re-throw exceptions in a new way.

In my example, notice that the user code that cares about exceptions spaces just needs to catch the library-specific exception base class. Or it could go further by catching a specific library exception. And the user code that doesn't care about exception spaces requires no changes. In your proposal, the user code that does care would need to change how the functions are called, and would need to change how they catch exceptions. The code that doesn't care about exception spaces requires no changes.

I think my example and your proposal achieve the same effect, with a similar amount of required changes on the library side and on client code that wants to participate. Both my example and your proposal require no changes to client code that doesn't want to participate.

The difference is that my example requires no changes to Python and so works today. It's also probably more efficient because it doesn't require the extra espace parameter.


you can do this. you can do all sorts of weird things. you don't have to immediately handle them. you can let them bubble up. but espaces/channels (I've been calling them both) let you be explicit about which raisers and which handlers link up to eachother. you aren't supposed to mindlessly pass espaces forward. if your function isn't supposed to raise, *don't use espaces* and don't use the new operators. that's perfectly valid, and is in fact intended. if you decide that a future version needs to be able to raise, you can add espaces then. but you wouldn't litter your codebase with espaces: you'd have an espace=None argument, and perhaps one or two statements where you pass in an espace (either with espace=espace or espace-aware operators). or you might not even accept an espace and instead just want to handle errors in the function itself but don't wanna mask bugs. all this is valid, and intended.
The problem with "if you decide that a future version needs to be able to raise" is that you now need to pass an espace parameter all through your code, and into the library. That might be many layers of functions that now need to be modified. If A calls B calls C calls D which calls the library, and A decides it wants use an exception space, then in your proposal B, C, and D also need modifying to pass through the espace argument. With normal exceptions (my example) that's not the case: only A needs modifying. And imagine that C and D aren't in your code, but are in second library. With your proposal, you'd need to modify that library too.


> we'll also need operator variants. I was thinking of the following:
>
>      x.in foo bar # for attribute access
>      x[in foo bar] # for item access
>      x +in foo bar # for math
>      # etc
>
> (yes, these are very ugly. this is one of the unfortunate things of
> trying to retrofit this into an existing language. but they're more
> likely to catch bugs so I'm not worried.)

There have been various proposals like this in the past, revolving around "just raise exceptions in my code, not in any code I call". I can't say if this is better than any of the prior ones. Usually the advice is to just limit the scope of your exception handlers. Although that can't catch everything, I think your operator proposal would have the same issues. But that would of course need to be worked out.

Eric
_______________________________________________
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/MIDAE6V2JUPDLZJRQZXRUTTDE5YJQ5BR/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to