Re: [Python-ideas] Let try-except check the exception instance

2018-06-01 Thread Antoine Pitrou
On Thu, 31 May 2018 14:00:24 -0400
Alexander Belopolsky
 wrote:
> 
> Is this really true?  Consider the following simple code
> 
> class E(Exception):
> def __init__(self):
> print("instantiated")
> 
> try:
> raise E
> except E:
> pass
> 
> Is it truly necessary to instantiate E() in this case?  Yet when I run it,
> I see "instantiated" printed on the console.

I don't think it's truly necessary, but there's enough complication
nowadays in the exception subsystem (e.g. with causes and contexts)
that at some point we (perhaps I) decided it made things less hairy to
always instantiate it in an "except" clause.

Let me stress, though: this happens when catching exceptions in
*Python*.  When in C you call PyErr_ExceptionMatches, the exception
should not get instantiated.

Regards

Antoine.


___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Let try-except check the exception instance

2018-05-31 Thread Alexander Belopolsky
On Thu, May 31, 2018 at 10:37 AM Nick Coghlan  wrote:
>
> The exception machinery deliberately attempts to avoid instantiating
exception objects whenever it can, but that gets significantly more
difficult if we always need to create the instance before we can decide
whether or not the raised exception matches the given exception handler
criteria.


Is this really true?  Consider the following simple code

class E(Exception):
def __init__(self):
print("instantiated")

try:
raise E
except E:
pass

Is it truly necessary to instantiate E() in this case?  Yet when I run it,
I see "instantiated" printed on the console.
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Let try-except check the exception instance

2018-05-31 Thread Eric Snow
On Wed, May 30, 2018 at 10:47 PM, Danilo J. S. Bellini
 wrote:
 try:
> ... # [...]
> ... session.commit() # Here it raises!
> ... # [...]
> ... except DatabaseError as exc:
> ... msg = get_db_error_msg_from_exception(exc)
> ... if msg == "beyond_limit":
> ... # [...]
> ... elif msg == "no_funds":
> ... # [...]
> ... else:
> ... raise

Matching on error messages is a recipe for pain.  My experience with
Go seared that lesson into my brain. (The boilerplate involved there
to make custom error types leads to a lot of error message checking
instead.)

> That works, but I'd like to do something like:
>
 try:
> ... # [...]
> ... except BeyondLimit:
> ... # [...]
> ... except NoFunds:
> ... # [...]

Using subclasses like this is the way to go.  If you control the
originating code then make it happen. :)  If not then work to fix it
upstream.

If that fails then at least use a helper that converts the exception
after the fact.  It could look like following:

# Subclassing DatabaseError means existing try-except block will
still work the same.
class CustomDatabaseError(DatabaseError):


class BeyondLimitError(CustomDatabaseError):
...

class NoFundsError(CustomDatabaseError):
...

def convert_exception(exc):
if isinstance(exc, CustomDatabaseError):
raise
msg = get_db_error_msg_from_exception(exc)
if msg == "beyond_limit":
raise BeyondLimitError(...)
elif msg == "no_funds":
raise NoFundsError(...)
else:
raise

@contextmanager
def converted_exceptions():
try:
yield
except CustomDatabaseError:
raise
except DatabaseError as exc:
convert_exception(exc)

def db_op(...):
with converted_exceptions():
# Some code that does DB ops, e.g. session.commit().
...

try:
db_op(...)
except BeyondLimitError:
...
except NoFundsError:
...
except DatabaseError:  # fallback
...

The big wins there are with re-usability, maintainability, and
separation of concerns.  Notably, it consolidates your
message-handling code to one spot and keeps it focused.

-eric
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Let try-except check the exception instance

2018-05-31 Thread Nick Coghlan
On 31 May 2018 at 14:47, Danilo J. S. Bellini 
wrote:

> The idea is to allow catching exceptions beyond checking their MRO,
> using a class that checks the exception instance by implementing
> a custom __instancecheck__.
>

The exception machinery deliberately attempts to avoid instantiating
exception objects whenever it can, but that gets significantly more
difficult if we always need to create the instance before we can decide
whether or not the raised exception matches the given exception handler
criteria.

So quite aside from any philosophy-of-design questions, we're unlikely to
ever implement this simply for laziness-of-evaluation reasons. (There are
already lots of circumstances that force instantiation - that doesn't mean
we're keen to add more)

Cheers,
Nick.

P.S. There's a somewhat related issue aimed at getting ABCs to work
correctly as exception handlers, although that still sticks to the
principle that exception handler checks solely consider the exception type,
not its value: https://bugs.python.org/issue12029

-- 
Nick Coghlan   |   ncogh...@gmail.com   |   Brisbane, Australia
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Let try-except check the exception instance

2018-05-31 Thread Ken Hilton
In one of my own packages, I use the following to catch dynamic error
messages:

def handle_error(exc):
# do stuff with exc
with catch('error_code', handle_error):
# do stuff that can raise an error

The definition of "catch" is as follows, assuming the root exception
actually raised is called "CodedError" whose "code" attribute is the error
code:

from contextlib import contextmanager
@contextmanager
def catch(code=None, caught=None, always=None):
try:
yield
except CodedError as exc:
if isinstance(code, str):
is_caught = exc.code == code
else:
is_caught = exc.code in code #assume it's a container
if (code is not None) and not is_caught:
raise
if caught is not None:
caught(exc)
finally:
if always is not None:
always()

Perhaps you could make use of this?

Suggesting,
Ken Hilton;
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Let try-except check the exception instance

2018-05-31 Thread Danilo J. S. Bellini
On 31 May 2018 at 02:41, Steven D'Aprano  wrote:

> Since error messages are rarely part of the exception API, testing for
> them is fragile and prone to errors. For example, what if the message
> changes to "no money" or "out of funds" or "insufficient funds" or
> "keine Mittel"? Since the error message is not part of the API, that can
> happen at any time, without warning.
>

The messages are from constraint triggers in the database.
I'm the author of them.



> The obvious way to do that is to create BeyondLimit and NoFunds
> subclasses of DatabaseError. Why can't you do that?
>

Because I'm not the author of DatabaseError.
It's an ORM exception or a database driver class exception,
something I'd need to monkeypatch to change.


That seems like a backwards way to do it to me. If you're the author of
> the exception class, why not just subclass your exceptions the regular
> way and get a real subclass?
>

I agree. Problem is: I'm not the author of the exception class,
and I'm not raising/throwing their exception instances.


On 31 May 2018 at 04:19, Terry Reedy  wrote:

> As Stephen said, messages are intentionally not part of the defined API.
> [...]
>

We could check error numbers instead of meaningful strings.
I'm thinking on technical error keys in either way.

Previously to classes like FileNotFoundError, we would need to check
an OSError errno. There were no alternative:
Python users are not the author of "everywhere that raises OSError".

Another example is urllib.error.HTTPError.
We can have a specific handler code for a given HTTP error code.
Yet we don't change urllib.request.urlopen to raise something else
for that specific error code.


On 31 May 2018 at 06:29, Stephan Houben  wrote:

> "[...] The exception matching machinery ignores the __instancecheck__
> mechanism."
>

The __subclasscheck__ gets bypassed, as well.

-- 
Danilo J. S. Bellini
---
"*It is not our business to set up prohibitions, but to arrive at
conventions.*" (R. Carnap)
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Let try-except check the exception instance

2018-05-31 Thread Stephan Houben
Current documentation says:

"An object is compatible with an exception if it is the class or a base
class of the exception object or a tuple containing an item compatible with
the exception."

https://docs.python.org/3/reference/compound_stmts.html#the-try-statement

It is, in my opinion, not very clear from this that the __instancecheck__
mechanism is bypassed.

Should the documentation perhaps be adapted to explain that the class needs
to actually occur in the MRO
and that virtual base classes are not considered for matching purposes?

"An object is compatible with an exception if it is the class or a
non-virtual base class of the exception object or a tuple containing an
item compatible with the exception.
The exception matching machinery ignores the __instancecheck__ mechanism."


Stephan


2018-05-31 9:19 GMT+02:00 Terry Reedy :

> On 5/31/2018 12:47 AM, Danilo J. S. Bellini wrote:
>
>> Hi!
>> I was working on handling some exceptions from external software
>> (e.g. database constraint triggers)
>> switching the handler based on the messages that had been sent.
>> Today we can do something like (running on Python 3.6.5):
>>
>>
>> try:
>
 ... # [...]
>> ... session.commit() # Here it raises!
>> ... # [...]
>> ... except DatabaseError as exc:
>> ... msg = get_db_error_msg_from_exception(exc)
>> ... if msg == "beyond_limit":
>> ... # [...]
>> ... elif msg == "no_funds":
>> ... # [...]
>> ... else:
>> ... raise
>>
>>
>> That works,
>>
>
> Yes, it works perfectly well, AND it exposes the fact that your code
> depends on the message, which I think is a good thing.
>
> As Stephen said, messages are intentionally not part of the defined API.
> As a matter of curtesy, we usually restrict message changes to new versions
> and do not backport the change. An exception may be made if we decide that
> a message is sufficiently erroneous that is likely misleads people.  In any
> case, message dependent code may be version dependent.
>
> --
> Terry Jan Reedy
>
>
>
> ___
> Python-ideas mailing list
> Python-ideas@python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Let try-except check the exception instance

2018-05-31 Thread Terry Reedy

On 5/31/2018 12:47 AM, Danilo J. S. Bellini wrote:

Hi!
I was working on handling some exceptions from external software
(e.g. database constraint triggers)
switching the handler based on the messages that had been sent.
Today we can do something like (running on Python 3.6.5):



try:

...     # [...]
...     session.commit() # Here it raises!
...     # [...]
... except DatabaseError as exc:
...     msg = get_db_error_msg_from_exception(exc)
...     if msg == "beyond_limit":
...     # [...]
...     elif msg == "no_funds":
...     # [...]
...     else:
...     raise


That works,


Yes, it works perfectly well, AND it exposes the fact that your code 
depends on the message, which I think is a good thing.


As Stephen said, messages are intentionally not part of the defined API. 
 As a matter of curtesy, we usually restrict message changes to new 
versions and do not backport the change. An exception may be made if we 
decide that a message is sufficiently erroneous that is likely misleads 
people.  In any case, message dependent code may be version dependent.


--
Terry Jan Reedy


___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Let try-except check the exception instance

2018-05-30 Thread Franklin? Lee
Would guards (such as from the switch-case discussions) be a good fit?

try:
...
except RuntimeError as e if e.message == "tie":
...


On Thu, May 31, 2018, 00:48 Danilo J. S. Bellini 
wrote:

> Hi!
> I was working on handling some exceptions from external software
> (e.g. database constraint triggers)
> switching the handler based on the messages that had been sent.
> Today we can do something like (running on Python 3.6.5):
>
>
> >>> try:
> ... # [...]
> ... session.commit() # Here it raises!
> ... # [...]
> ... except DatabaseError as exc:
> ... msg = get_db_error_msg_from_exception(exc)
> ... if msg == "beyond_limit":
> ... # [...]
> ... elif msg == "no_funds":
> ... # [...]
> ... else:
> ... raise
>
>
> That works, but I'd like to do something like:
>
>
> >>> try:
> ... # [...]
> ... except BeyondLimit:
> ... # [...]
> ... except NoFunds:
> ... # [...]
>
>
> Creating classes to "match" the exception in their __instancecheck__.
> Well, I tried to do so. A simplified example would be:
>
>
> >>> class MsgCheckerMeta(type):
> ... def __instancecheck__(cls, instance):
> ... return str(instance) == cls.internal
> ...
> >>> class ExceptionHasMessage(Exception, metaclass=MsgCheckerMeta):
> ... internal = "message"
>
>
> Using these new classes, we would get this:
>
>
> >>> try:
> ... raise Exception("message")
> ... except ExceptionHasMessage:
> ... print("Yeah!")
> ...
> Traceback (most recent call last):
>   File "", line 2, in 
> Exception: message
>
>
> Yet,
>
>
> >>> isinstance(Exception("message"), ExceptionHasMessage)
> True
> >>> try:
> ... raise Exception("message")
> ... except Exception as exc:
> ... print(isinstance(exc, ExceptionHasMessage))
> ...
> True
>
>
> The idea is to allow catching exceptions beyond checking their MRO,
> using a class that checks the exception instance by implementing
> a custom __instancecheck__.
>
> --
> Danilo J. S. Bellini
> ---
> "*It is not our business to set up prohibitions, but to arrive at
> conventions.*" (R. Carnap)
> ___
> Python-ideas mailing list
> Python-ideas@python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


Re: [Python-ideas] Let try-except check the exception instance

2018-05-30 Thread Steven D'Aprano
On Thu, May 31, 2018 at 01:47:17AM -0300, Danilo J. S. Bellini wrote:

> >>> try:
> ... # [...]
> ... session.commit() # Here it raises!
> ... # [...]
> ... except DatabaseError as exc:
> ... msg = get_db_error_msg_from_exception(exc)
> ... if msg == "beyond_limit":
> ... # [...]
> ... elif msg == "no_funds":
> ... # [...]
> ... else:
> ... raise

Since error messages are rarely part of the exception API, testing for 
them is fragile and prone to errors. For example, what if the message 
changes to "no money" or "out of funds" or "insufficient funds" or 
"keine Mittel"? Since the error message is not part of the API, that can 
happen at any time, without warning.

If you want to make such a risky check in your own code, of course you 
can do so, but we shouldn't encourage it or add functionality to make it 
easier. You have the right to shoot yourself in the foot, but don't 
expect us to load the gun, hand it to you, point it at your foot, and 
place your finger on the trigger :-)


> That works, but I'd like to do something like:
> 
> 
> >>> try:
> ... # [...]
> ... except BeyondLimit:
> ... # [...]
> ... except NoFunds:
> ... # [...]

The obvious way to do that is to create BeyondLimit and NoFunds 
subclasses of DatabaseError. Why can't you do that?


> Creating classes to "match" the exception in their __instancecheck__.
> Well, I tried to do so. A simplified example would be:
> 
> >>> class MsgCheckerMeta(type):
> ... def __instancecheck__(cls, instance):
> ... return str(instance) == cls.internal
> ...
> >>> class ExceptionHasMessage(Exception, metaclass=MsgCheckerMeta):
> ... internal = "message"


That seems like a backwards way to do it to me. If you're the author of 
the exception class, why not just subclass your exceptions the regular 
way and get a real subclass?

If you have enough control of the source to change the exception from 
DatabaseError to ExceptionHasMessage, then you ought to have enough 
control to change to a specific subclass as needed.

And if you *don't* have that control, then you're probably monkey- 
patching something you don't control and again, we shouldn't encourage 
that.

Do you have a better example of checking __instancecheck__ that doesn't 
involve something that's a risky hack?



-- 
Steve
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Let try-except check the exception instance

2018-05-30 Thread Danilo J. S. Bellini
Hi!
I was working on handling some exceptions from external software
(e.g. database constraint triggers)
switching the handler based on the messages that had been sent.
Today we can do something like (running on Python 3.6.5):


>>> try:
... # [...]
... session.commit() # Here it raises!
... # [...]
... except DatabaseError as exc:
... msg = get_db_error_msg_from_exception(exc)
... if msg == "beyond_limit":
... # [...]
... elif msg == "no_funds":
... # [...]
... else:
... raise


That works, but I'd like to do something like:


>>> try:
... # [...]
... except BeyondLimit:
... # [...]
... except NoFunds:
... # [...]


Creating classes to "match" the exception in their __instancecheck__.
Well, I tried to do so. A simplified example would be:


>>> class MsgCheckerMeta(type):
... def __instancecheck__(cls, instance):
... return str(instance) == cls.internal
...
>>> class ExceptionHasMessage(Exception, metaclass=MsgCheckerMeta):
... internal = "message"


Using these new classes, we would get this:


>>> try:
... raise Exception("message")
... except ExceptionHasMessage:
... print("Yeah!")
...
Traceback (most recent call last):
  File "", line 2, in 
Exception: message


Yet,


>>> isinstance(Exception("message"), ExceptionHasMessage)
True
>>> try:
... raise Exception("message")
... except Exception as exc:
... print(isinstance(exc, ExceptionHasMessage))
...
True


The idea is to allow catching exceptions beyond checking their MRO,
using a class that checks the exception instance by implementing
a custom __instancecheck__.

-- 
Danilo J. S. Bellini
---
"*It is not our business to set up prohibitions, but to arrive at
conventions.*" (R. Carnap)
___
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/