[Python-ideas] Re: Exception for parameter errors

2020-03-10 Thread Marco Sulla via Python-ideas
On Wed, 4 Mar 2020 at 12:33, Chris Angelico  wrote:
> Being able to easily distinguish "that isn't callable" from "the
> parameters don't line up" from "the function, during execution, raised
> TypeError" would be useful.

I **completely** agree. And I add that the change can also be
non-breaking, if the new ArgumentError is simply a subclass of
TypeError. Yes, it's incorrect, but this way no old code will break.

PS: if you read the official docs
(https://docs.python.org/3/library/exceptions.html#TypeError):

> exception TypeError
> Raised when an operation or function is applied to an object of inappropriate 
> type.

etc. It's never stated that TypeError should be raised when the number
of arguments is invalid, for example. It seems that TypeError itself
is saying "now I'm used outside my scope" :-)
___
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/FAD44C2LVEK6CY4DV7C6OSBXQKWUVRLD/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-05 Thread Christopher Barker
It seems to me that this thread is a bit too focused. I know I’ve found
that often I wish I knew better exactly what caused a particular Exception—
there are many that can be raised for various reasons, and you may need to
know which one in order to handle it properly.

At this point, parsing the error msg is often the only option. Though since
you can tack on arbitrary data to an Exception, some have ways of knowing.

So maybe a more formal and standardized way to to specify more detailed
information is in order. But it shouldn’t be just about TypeError.

On the other hand, subclassing exceptions works pretty well, so maybe
that’s fine. It worked pretty well for OSError, for example.

As for TypeError and calling functions. I believe the OP wanted to
distinguish between calling the function the wrong way: e.g. the wrong
[number of, name of, order of] arguments, and calling the function with the
wrong type of a given parameter. And Python being dynamically typed,
calling a function with the wrong Type is really an arbitrary
somthing-went-wrong-inside-the-function, not an error with calling per se.

And I think this is a useful distinction. It's actually a common problem,
when you do:

try:
func(...)
Except SomeError:
do_something

you never know where in the entire call stack SomeError came from.

In the case at hand, a TypeError may have come from how you are calling
func, or it may have come from somewhere inside func(), or indeed, anywhere
else deep in that hierachy.

So I think it would be very useful to have an Exception that is about the
"how the function was called" part. Of course, it could still have come
from another call inside the top-l;evel function, but I think it sill makes
a useful distinction.

-CHB











On Wed, Mar 4, 2020 at 7:11 AM Alex Hall  wrote:

> > Both of these calls raise TypeError, but for different reasons:
> > # right number of arguments passed to the key function,
> > # but the wrong argument type
> > sorted([3, 5, 1], key=len)
> >
> > # wrong number of arguments passed to the key function
> > sorted([3, 5, 1], key=isinstance)
> >
> > It might be useful to be able to distinguish the two cases.
>
> I'm hearing a lot of hypotheticals and nothing close to what feels like a
> real use case. Both those cases are completely wrong and I don't know why
> I'd need to distinguish the reason, or even catch TypeError at all.
>
> > > It seems to me that using this is like
> > > stating "this code might be wrong" and would generally produce bad
> code.
> > > It seems to me that your comment above could be equally said about any
> > use of exceptions: "this code might be wrong, so we'll stick it in a
> > try...except block". Only that's not how most of us actually use
> > exceptions.
>
> I don't understand, you seem to be contradicting yourself between "any use
> of exceptions" and "that's not how most of us actually use exceptions". The
> examples you're giving explicitly look like "this code might be wrong". The
> examples of exceptions I'm looking at in my own code are more like "the
> inputs might be wrong" or "something might go wrong beyond my control".
>
> > try:
> > # Test whether the aardvark function supports a
> > # `hovercraft` parameter.
> > result = aardvark(
> >  spam, eggs, cheese, hovercraft=1
> >  )
> > except ParameterError:
> > # No hovercraft parameter allowed, create a wrapper.
> > ...
>
> This is a weird hypothetical. Please provide a real example. Code that you
> have actually written vs what you wish you could have written. I have
> certainly never wanted to do something like this. And if I was in such a
> situation that's still not the way I'd want to solve it.
>
> > What if the caller of my library has back-ported the new, advanced
> > version of aardvark() to their Python? Instead of using the faster,
> > better tested official backport, my code will use my wrapper.
>
> I don't really understand how this would work, can you elaborate?
>
> > > If you catch ParameterError, how do you know that it
> > > came directly from the
> > > line you caught it from and not deeper within?
> > > Does it matter?
> > If the call aardvark(*args, hovercraft=1) leaks a ParameterError from
> > deep inside its internals, then even if the feature is technically
> > available according to a version check, it is too buggy to use and I
> > need to use my wrapper.
>
> But maybe the function `spam` that you passed to aardvark didn't take the
> right number of parameters?
>
> To make it more concrete, imagine code like this from a time when newer
> versions of `sort` accepted `key` but older versions could only accept
> `cmp` (I'm not sure if this is actually what happened, it's not important).
>
> try:
> lst.sort(key=foo_key)
> except ParameterError:
> lst.sort(cmp=foo_cmp)
>
> Does `except ParameterError:` mean that `sort` doesn't accept `key`, or
> does it mean that `foo_key` is wrong?
>
> > py> 

[Python-ideas] Re: Exception for parameter errors

2020-03-05 Thread Ethan Furman

On 03/04/2020 06:24 AM, Steven D'Aprano wrote:


My earlier use-case still stands: feature detection where a function has
changed its parameter list. More on this below.


It seems like `inspect.signature` is the right way to do this kind of
feature detection.



On Wed, Mar 04, 2020 at 12:19:25PM +0200, Alex Hall wrote:

If you catch ParameterError, how do you know that it came directly from the
line you caught it from and not deeper within?


On 03/04/2020 06:24 AM, Steven D'Aprano wrote:

Does it matter?

If the call aardvark(*args, hovercraft=1) leaks a ParameterError from
deep inside its internals, then even if the feature is technically
available according to a version check, it is too buggy to use and I
need to use my wrapper.


This feels like a separate problem than "the function I'm calling changed
it's signature".



On Wed, Mar 04, 2020 at 12:19:25PM +0200, Alex Hall wrote:

If you want to check that you're passing parameters properly, I suggest
writing code like this:

import inspect

signature = inspect.signature(func)


And what if the function I'm checking doesn't support signature
metadata?

https://docs.python.org/3/library/inspect.html#inspect.signature


Looks like ValueError and TypeError can be raised, so it seems like the question
is how often?  If it only fails rarely or seldom then the case for more built-in
exceptions is weakened.



I can wrap the call to signature() in try...except, but what's my
fallback if I can't check the signature?

 py> inspect.signature(math.gcd)
 Traceback (most recent call last):
 [...]
 TypeError: 'feature_version' is an invalid keyword argument
 for compile()


In those cases you'll need to use the existing TypeError -- and with unit tests 
to
ensure things are working correctly on the different versions you're targeting
you'll know you're good to go.

By the way, `inspect.signature(math.gcd)` works for me on Python 3.8.2 -- which
version were you testing with?

--
~Ethan~
___
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/YUDDL4ZNFMZAEFX3IMY3FKUIBUF6E7TZ/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Andrew Barnert via Python-ideas
On Mar 4, 2020, at 03:00, Steven D'Aprano  wrote:
> 
> On Wed, Mar 04, 2020 at 01:08:35AM -0800, Andrew Barnert wrote:
> 
>> I liked everything up to using magic numbers like this.
> 
> I'm not wedded to the idea of error number. I chose them because I 
> expect there will only be a few of them (three?) and the majority 
> of the time you won't bother to check the number.

OK, but what’s the downside of giving you a named constant instead of making 
you use the magic number 2? Making CPython 400 bytes bigger? Forcing the help 
for ParameterError to display the kinds of error the same way all named 
constants are displayed when you had a better way to display them?

>> But this is just arguing about the colour of the bikeshed. Do you agree
>> with me that the bikeshed itself is useful?

I’m +0 on this (if there’s an implementation that can cover all of the common 
cases). If people are switching on the error message string in real code (and I 
think I’ve done that once, for Python 2.7/3.3 code where I auto-generated 
wrappers around appscript proxies), there ought to be another way, and having a 
new error subclass is the obvious pythonic better way. The only question is 
whether it comes up often enough to be worth fixing.

>> The other question: what exactly do you propose to change? I don’t 
>> think you can do this on the caller side (the ceval bytecode handler 
>> and the PyCall functions).
> 
> I don't know enough about the Python internals to give a definitive 
> answer, but I'm assuming/hoping that there is a single, or at most a 
> few, places in the interpreter that matches up arguments to formal 
> parameters, and if there's a discrepency it currently raises TypeError. 
> PyCall sounds promising :-)

I’m pretty sure there isn’t a single place, and PyCall is not useful—but I 
think there may be a small handful of places that’s good enough. IIRC, it works 
like this:

The PyCall functions and the bytecode handler eventually call the same code, 
but that code just prepares the arguments in a generic way that the callee can 
match them: as a tuple of positional args and a dict of keyword args. It 
doesn’t even know anything about the callee. The parsing of that tuple and dict 
is up to the callee. Which can be anything.

But usually, it’s either:

 * a function (or type or thing with __call__) written in Python, in which case 
the PyFunction object handles matching args to params and creating exceptions
 * a C function that just passes the tuple and dict to one of a small set of 
PyArg parse functions, which do the matching and exceptions
 * a C function with auto-generated argclinic code, which I think is the same 
as above, but if not argclinic is just one more place to change

A C function could just manually parse the tuple and dict, but at that point 
it’s like a Python function that asks for *args, **kw and parses those, and I 
think you’re right that we don’t need to worry about those. Especially since in 
most cases they’re just proxies or bridges to some other function that will 
parse the args the normal way and they’ll just pass that exception up.

So, I think those 2 or 3 places (that may turn out to be like 6 places in the 
CPython source) may be sufficient. But you’d have to check that, not just go by 
my memory. :)

Also, it’s probably worth checking some very popular special cases: ctypes 
(with argtypes), cffi, a couple of proxy callables like partial and MethodType, 
Cython, the code generator from NumPy, a few static and dynamic bridge 
libraries like boost::python and PyObjC, … I think most of them will already 
just work, except maybe for some dynamic bridges (e.g., if PyObjC is just 
converting the tuple and dict to an NSInvocation, calling that via ObjC, and 
then parsing the ObjC error to generate the Python one…), which I think would 
be perfectly acceptable. But it’s worth knowing whether there will be such 
cases.

>> That still leaves functions that 
>> parse *args and **kw and raise TypeError manually, whether in Python 
>> or in C, but I suppose you can say that in that case it really isn’t a 
>> parameter error (the signature really is *args, **kw so everything 
>> matches).
> 
> Indeed.
> 
> I don't have any expectations about those functions. If the maintainer 
> of the function eventually changes whatever error they are currently 
> raising to ParameterError, that will be grand, but I don't expect every 
> third-party function that implements some custom variety of parameter 
> handling to support this. If they do, great, if they don't, we're no 
> worse off than the status quo.
> 
>> What about inspect.Signature.bind?
> 
> Do you have a specific concern?

Just whether you’re proposing to change it as part of the proposal. I think you 
do want to, and I think it’ll be easy, but I don’t think either answer would be 
a deal breaker.

More generally, I’m just trying to make sure things like inspect.signature, and 
partial and so on, have been considered before anyone 

[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Alex Hall
> Both of these calls raise TypeError, but for different reasons:
> # right number of arguments passed to the key function, 
> # but the wrong argument type
> sorted([3, 5, 1], key=len)
> 
> # wrong number of arguments passed to the key function
> sorted([3, 5, 1], key=isinstance)
> 
> It might be useful to be able to distinguish the two cases.

I'm hearing a lot of hypotheticals and nothing close to what feels like a real 
use case. Both those cases are completely wrong and I don't know why I'd need 
to distinguish the reason, or even catch TypeError at all.

> > It seems to me that using this is like
> > stating "this code might be wrong" and would generally produce bad code.
> > It seems to me that your comment above could be equally said about any 
> use of exceptions: "this code might be wrong, so we'll stick it in a 
> try...except block". Only that's not how most of us actually use 
> exceptions.

I don't understand, you seem to be contradicting yourself between "any use of 
exceptions" and "that's not how most of us actually use exceptions". The 
examples you're giving explicitly look like "this code might be wrong". The 
examples of exceptions I'm looking at in my own code are more like "the inputs 
might be wrong" or "something might go wrong beyond my control".

> try:
> # Test whether the aardvark function supports a 
> # `hovercraft` parameter.
> result = aardvark(
>  spam, eggs, cheese, hovercraft=1
>  )
> except ParameterError:
> # No hovercraft parameter allowed, create a wrapper.
> ...

This is a weird hypothetical. Please provide a real example. Code that you have 
actually written vs what you wish you could have written. I have certainly 
never wanted to do something like this. And if I was in such a situation that's 
still not the way I'd want to solve it.

> What if the caller of my library has back-ported the new, advanced 
> version of aardvark() to their Python? Instead of using the faster, 
> better tested official backport, my code will use my wrapper.

I don't really understand how this would work, can you elaborate?

> > If you catch ParameterError, how do you know that it
> > came directly from the
> > line you caught it from and not deeper within?
> > Does it matter?
> If the call aardvark(*args, hovercraft=1) leaks a ParameterError from 
> deep inside its internals, then even if the feature is technically 
> available according to a version check, it is too buggy to use and I 
> need to use my wrapper.

But maybe the function `spam` that you passed to aardvark didn't take the right 
number of parameters?

To make it more concrete, imagine code like this from a time when newer 
versions of `sort` accepted `key` but older versions could only accept `cmp` 
(I'm not sure if this is actually what happened, it's not important).

try:
lst.sort(key=foo_key)
except ParameterError:
lst.sort(cmp=foo_cmp)

Does `except ParameterError:` mean that `sort` doesn't accept `key`, or does it 
mean that `foo_key` is wrong?

> py> inspect.signature(math.gcd)
> Traceback (most recent call last):
> [...]
> TypeError: 'feature_version' is an invalid keyword argument 
> for compile()

That's interesting, what version of Python gives you that? It works for me.
___
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/WEPACETVNORZCVRNGZIALDTXIHAHCI47/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Eric Fahlgren
On Wed, Mar 4, 2020 at 2:58 AM Steven D'Aprano  wrote:

> I don't know enough about the Python internals to give a definitive
> answer, but I'm assuming/hoping that there is a single, or at most a
> few, places in the interpreter that matches up arguments to formal
> parameters, and if there's a discrepency it currently raises TypeError.
> PyCall sounds promising :-)
>

In my experience, it's sprinkled all over the place.  For example, here are
the pertinent chunks of one of our functions that converts VAX floats to
IEEE...

static PyObject *vax_data_floats(PyObject *self, PyObject **args,
Py_ssize_t nargs)
{
if (nargs != 2) {
PyErr_Format(PyExc_TypeError, "floats: expected 2 arguments,
'bytes' and 'count'");
return NULL;
}
...
if (!PyLong_Check(py_count)) {
PyErr_Format(PyExc_TypeError, "floats: second argument must be an
int");
return NULL;
}
...
if (count*(Py_ssize_t)sizeof(float) > bytes_size) {
PyErr_Format(PyExc_TypeError,
 "floats: argument 2 is larger than data (only %d bytes
for requested %d floats)",
 bytes_size,
 count);
return NULL;
}
___
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/CRYO5NA463YGDLSTQDTFS3DWGBXVWDX2/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Steven D'Aprano
On Wed, Mar 04, 2020 at 12:19:25PM +0200, Alex Hall wrote:
> Can you explain where `except ParameterError` would produce better code
> than the best current alternative?

Serhiy came up with a good use-case that I hadn't considered: functions 
which take arbitrary callback functions as argument, e.g. sorted() with 
a key function.

Both of these calls raise TypeError, but for different reasons:

# right number of arguments passed to the key function, 
# but the wrong argument type
sorted([3, 5, 1], key=len)

# wrong number of arguments passed to the key function
sorted([3, 5, 1], key=isinstance)

It might be useful to be able to distinguish the two cases.

My earlier use-case still stands: feature detection where a function has 
changed its parameter list. More on this below.


> It seems to me that using this is like
> stating "this code might be wrong" and would generally produce bad code.

It seems to me that your comment above could be equally said about *any* 
use of exceptions: "this code might be wrong, so we'll stick it in a 
try...except block". Only that's not how most of us actually use 
exceptions.


> For example if I wanted to use math.gcd on multiple values but I need to
> support Python versions that only allow two arguments, then I'll just apply
> it to two arguments at a time, maybe with functools.reduce.

I've done that myself. Not specifically with gcd, but with other 
functions, which I don't remember off the top of my head.

gcd is a particularly simple example because it is so easy to wrap. 
Excluding function header and docstring, the code is just six lines:

if len(args) == 0:
return 1
elif len(args) == 1:
return abs(args[0])
else:
return reduce(_gcd, args)

Not all cases will be that simple, or they may involve a serious 
performance cost. In general, the official version is going to be 
faster and better tested than the wrapper version.

Why shouldn't I use the fast, well-tested standard version if it is 
available? I just need a reliable way to detect the needed feature.


This is not specifically about gcd. It could be about functions of 
arbitrary complexity:

try:
# Test whether the aardvark function supports a 
# `hovercraft` parameter.
result = aardvark(
 spam, eggs, cheese, hovercraft=1
 )
except ParameterError:
# No hovercraft parameter allowed, create a wrapper.
...

TypeError is less satisfactory because I cannot easily distinguish the 
two cases:

1. The 'hovercraft' parameter is not available;

2. The 'hovercraft' parameter is available, but I have made some other 
error in the function call which results in a TypeError.


> And in general
> if I want to distinguish between versions I'd much rather just check the
> version and thus assert "this code is correct for this version".

Feature detection is more reliable.

What if the caller of my library has back-ported the new, advanced 
version of aardvark() to their Python? Instead of using the faster, 
better tested official backport, my code will use my wrapper.

What if the caller is using my library with another interpreter, say 
PyPy or Jython, which doesn't support the feature I need? With feature 
detection, my code will simply fall back to the wrapper. With version 
checking, it will wrongly assert the feature is available, and then 
crash later.

As far as I can tell, feature detection has more or less completely 
overshadowed version checks in the browser and Javascript space. In my 
opinion, checking the version should only be used when it is too hard to 
detect the feature.

> If you catch ParameterError, how do you know that it came directly from the
> line you caught it from and not deeper within?

Does it matter?

If the call aardvark(*args, hovercraft=1) leaks a ParameterError from 
deep inside its internals, then even if the feature is technically 
available according to a version check, it is too buggy to use and I 
need to use my wrapper.


> If you want to check that you're passing parameters properly, I suggest
> writing code like this:
> 
> import inspect
> 
> signature = inspect.signature(func)

And what if the function I'm checking doesn't support signature 
metadata?

https://docs.python.org/3/library/inspect.html#inspect.signature

I can wrap the call to signature() in try...except, but what's my 
fallback if I can't check the signature?

py> inspect.signature(math.gcd)
Traceback (most recent call last):
[...]
TypeError: 'feature_version' is an invalid keyword argument 
for compile()


-- 
Steven
___
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 

[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Steven D'Aprano
On Wed, Mar 04, 2020 at 02:09:00PM +0200, Serhiy Storchaka wrote:

> If add such exception, ArgumentError looks more appropriate name.

Thanks Serhiy, I considered that but thought that ArgumentError was 
likely to be used in third-party libraries.

A quick google shows that argparse and Boost both have an ArgumentError 
exception.


[...]
> The way to get such error is when the caller does not know what function 
> it calls. For example,
> 
> functools.reduce(len, [1, 2])
> sorted([1, 2], key=divmod)
> 
> Authors of reduce() and sorted() do not know what function they call, it 
> is passed as an argument. The error is on the side of the user of these 
> function, it is passing a wrong *type* of the function. It is definitely 
> a TypeError.

That might be so, and I am not debating whether the error is really a 
TypeError or not. Backwards compatibility means that it must stay 
TypeError, or a subclass of TypeError.

I had not thought of reduce(), map(), sorted() etc, that's another good 
use-case, thank you!



-- 
Steven
___
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/VHZLMDD7HG7QVIHNABWWVVTOWZZDLNOJ/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Steven D'Aprano
On Wed, Mar 04, 2020 at 11:10:15AM +, Jonathan Fine wrote:

> We are talking here about an exception message that is generated by core
> Python. So there would be some warning of change, namely the review process
> for core Python.

You can't call the error message a stable part of the language without 
forbidding changes to the error message. Essentially, you are asking for 
changes to error messages to be treated as a breaking change.

That includes fixing typos and spelling errors, rewording clumsy phrases 
and adding extra or better quality information to the message. These are 
all breaking changes if you expect error messages to be stable.

The precise wording of error messages would have to be documented.

Not everyone follows the review process for interpreter changes, so 
changes would have to be listed in those docs, and the "What's New" for 
each release.

In my own code I would never accept a request to freeze error messages. 
To be frank, I think it would be intolerable.

I would never, ever ask the core developers to take on this additional 
burden.

It would also rule out any possibility of having error messages 
localised to the user's native language.


> I agree that there is a problem to be solved here. Perhaps it is easier and
> better for core Python to decide to freeze certain messages, than to add a
> new exception type.

How do you decide which functions should have their error messages 
frozen, and which do not?

Let's suppose you make that decision, and you declare that len(), map(), 
zip(), itertools.count() and functools.wraps() will have their error 
message frozen, but the other functions don't. How does that help me 
when I want to use feature detection to detect a change in the calling 
parameters to somelibrary.somefunction()?



-- 
Steven
___
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/IKDOVZ2BSMFI5JB3JWEG7RCSIQSMNWZE/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Steven D'Aprano
On Wed, Mar 04, 2020 at 10:12:34AM +, Jonathan Fine wrote:

> I find this behaviour perfectly satisfactory. The OP's problem can be
> resolved by looking at the exception, whose type is TypeError.
> 
> For this to be completely reliable, certain aspects the exception message
> need to be stable. It may be worth stating that this will be done, at least
> for certain exception.

Error messages are not part of the function API and are not guaranteed 
to be a constant part of the language. We shouldn't expect every single 
Python interpreter (CPython, IronPython, Jython, Nuitka, MicroPython, 
PyPy, Stackless etc) to use exactly the same error messages.

It shouldn't be a *feature change* if an error message changes. We 
shouldn't have to use a deprecation period or a `__future__` import 
before we can improve error messages, fix spelling or grammatical 
errors, or add extra information to the error message.

Error messages might be localised into the native language of the user.

Parsing error messages is a fragile and error-prone technique that 
should be avoided if at all possible.



-- 
Steven
___
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/V3PQVEOMHVL3DAA7TKOMAWKV2Q3BEXEO/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Serhiy Storchaka

04.03.20 09:06, Steven D'Aprano пише:

Proposal: add a new exception, ParameterError, for parameter errors. For
backwards compatibility it would have to be a subclass of TypeError.


If add such exception, ArgumentError looks more appropriate name.


Where the interpreter now raises TypeError for invalid parameters, it
will switch to ParameterError. SyntaxErrors will remain SyntaxErrors.

 min(key=func, values)
 SyntaxError: positional argument follows keyword argument


This will allow developers to distinguish genuine type errors
(argument is the wrong type) from other calling errors (argument
is the correct type, but passed in the wrong way).


Look at this from other side. The working code does not contain 
something like len(a, b) or divmod(a), because the author of the code 
know how much arguments the used function accepts. It may be short-time 
mistake in process of writing code, but it will be fixed after the first 
test (or be caught by a linter).


The way to get such error is when the caller does not know what function 
it calls. For example,


functools.reduce(len, [1, 2])
sorted([1, 2], key=divmod)

Authors of reduce() and sorted() do not know what function they call, it 
is passed as an argument. The error is on the side of the user of these 
function, it is passing a wrong *type* of the function. It is definitely 
a TypeError.

___
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/3QSASBOMEEWNJ2H5HRHYHCVZT7QV4AMW/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Chris Angelico
On Wed, Mar 4, 2020 at 9:58 PM Steven D'Aprano  wrote:
>
> The important thing is to be able to programmatically
> distinguish the case where arguments don't match the parameters from the
> case where an argument is the wrong type.
>

Or where you're trying to call something that isn't callable. There
are several distinct "phases" to calling a function, and TypeError
could come up from any of them:

>>> "spam"()
:1: SyntaxWarning: 'str' object is not callable; perhaps you
missed a comma?
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'str' object is not callable
>>> len(1, 2)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: len() takes exactly one argument (2 given)
>>> len(1)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: object of type 'int' has no len()

Being able to easily distinguish "that isn't callable" from "the
parameters don't line up" from "the function, during execution, raised
TypeError" would be useful.

So, I agree that the bikeshed is of value here. As to colour, I'd be
(weakly) inclined toward:

1) ParameterError and maybe UncallableError as well
2) Type flags with class constants eg ParameterError.TOO_MANY

Even if TOO_MANY is technically just the integer 2 (as opposed to an
IntEnum), encouraging people to use that will make it a lot easier to
read. C code can use a #define for maximum performance, and then the
cost of looking up the attribute is paid only if the exception is
caught AND checked for that status - minimal overhead on creation.

ChrisA
___
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/D6ZDQKMFMGWVGKDYWQVPE3WYPECWRBD2/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Jonathan Fine
Steven D'Aprano wrote:

Another alternative is to testing for key phrases in the exception
> message:
>
> if "too few arguments" in err.args[0]: ...
>
> but that's fragile and only works until the message gets changed to say
> "not enough arguments". (The error message is not part of the API, so it
> can change without warning.)
>

We are talking here about an exception message that is generated by core
Python. So there would be some warning of change, namely the review process
for core Python.

I agree that there is a problem to be solved here. Perhaps it is easier and
better for core Python to decide to freeze certain messages, than to add a
new exception type.
-- 
Jonathan
___
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/7IOPTECPYJA52T5H4C3DH6NTHQKGN3DQ/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Steven D'Aprano
On Wed, Mar 04, 2020 at 01:08:35AM -0800, Andrew Barnert wrote:

> I liked everything up to using magic numbers like this.

I'm not wedded to the idea of error number. I chose them because I 
expect there will only be a few of them (three?) and the majority 
of the time you won't bother to check the number.

If you are debugging an exception that occurs in code that's supposed to 
be working, you have a bug and you will read the error message to find 
out what it means, not inspect the error number. It's only when you 
intentionally capture the exception that the error number might matter, 
and even then probably not that often.

I expect that in the majority of cases, there's only one possible 
failure mode. E.g. you can't have both too many and too few arguments in 
the same call, and you'll know which is going to be the case:

try:
spam(1, 2)
except ParameterError:
# Some versions of spam only accept 1 argument;
# some accept 2. If we get a ParameterError, we
# must be running the old 1-arg version.

Another alternative is to testing for key phrases in the exception 
message:

if "too few arguments" in err.args[0]: ...

but that's fragile and only works until the message gets changed to say 
"not enough arguments". (The error message is not part of the API, so it 
can change without warning.)

But as I said, I don't think this part of the proposal would be used 
very often, and if it becomes a real sticking point I'd be prepared to 
drop it. The important thing is to be able to programmatically 
distinguish the case where arguments don't match the parameters from the 
case where an argument is the wrong type.


> Nobody will remember whether it’s 1 for too many arguments or too 
> few; 

If we use an error number, I will beg for the numbers to be documented 
in the exception docstring so that `help(ParameterError)` will show the 
list. I don't expect that there will be many. I can only think of three, 
even if there is triple that it's still small enough to put into the 
docstring.

But this is just arguing about the colour of the bikeshed. Do you agree 
with me that the bikeshed itself is useful?


> The other question: what exactly do you propose to change? I don’t 
> think you can do this on the caller side (the ceval bytecode handler 
> and the PyCall functions).

I don't know enough about the Python internals to give a definitive 
answer, but I'm assuming/hoping that there is a single, or at most a 
few, places in the interpreter that matches up arguments to formal 
parameters, and if there's a discrepency it currently raises TypeError. 
PyCall sounds promising :-)


> That still leaves functions that 
> parse *args and **kw and raise TypeError manually, whether in Python 
> or in C, but I suppose you can say that in that case it really isn’t a 
> parameter error (the signature really is *args, **kw so everything 
> matches).

Indeed.

I don't have any expectations about those functions. If the maintainer 
of the function eventually changes whatever error they are currently 
raising to ParameterError, that will be grand, but I don't expect every 
third-party function that implements some custom variety of parameter 
handling to support this. If they do, great, if they don't, we're no 
worse off than the status quo.


> What about inspect.Signature.bind?

Do you have a specific concern?

Signature.bind promises to raise TypeError if the arguments don't match 
the parameters. A change to ParameterError should be completely 
backwards compatible since it will be a subclass of TypeError.



-- 
Steven
___
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/D44ZJK5YFPBGFOEQNFUB2MDWWTFCPXFM/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Jonathan Fine
Postscript: Things are not as clean as I have hoped and said. However, I
don't think this changes the general form of the conclusion. Just that a
little more work is needed to achieve the goal.

Python 3.7 and 3.8:
>>> def f(x): pass
>>> f()
TypeError: f() missing 1 required positional argument: 'x'

Also note that in Python 3.8 we have:
>>> def f(x,/): pass
>>> f()
TypeError: f() missing 1 required positional argument: 'x'

Recall that we have
>>> len()
TypeError: len() takes exactly one argument (0 given)

Note that the parameter of 'f' has a name, but not the parameter for 'len'.
I think I was expecting
>>> def f(x,/): pass
>>> f()
TypeError: f() takes exactly one argument (0 given)

-- 
Jonathan
___
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/5BKOPHXXG6JYFBNPA2VWFZEVO5YFBOFV/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Alex Hall
Can you explain where `except ParameterError` would produce better code
than the best current alternative? It seems to me that using this is like
stating "this code might be wrong" and would generally produce bad code.
For example if I wanted to use math.gcd on multiple values but I need to
support Python versions that only allow two arguments, then I'll just apply
it to two arguments at a time, maybe with functools.reduce. And in general
if I want to distinguish between versions I'd much rather just check the
version and thus assert "this code is correct for this version".

If you catch ParameterError, how do you know that it came directly from the
line you caught it from and not deeper within?

If you want to check that you're passing parameters properly, I suggest
writing code like this:

import inspect

signature = inspect.signature(func)
try:
signature.bind(*args, **kwargs)
except TypeError as e:
result = ???
else:
result = func(*args, **kwargs)

That's basically code that I've actually used in practice.

If you want it to look prettier, you could probably write a decorator like
this:

@check_params
def func(...):
...

try:
result = func(*args, **kwargs)
except func.ParameterError as e:
result = ???
___
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/TVWVCXAYIY4JKEJIGO7EPAYKLQG4IFAV/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Jonathan Fine
Hi

The present behaviour is:
>>> len()
TypeError: len() takes exactly one argument (0 given)

>>> len(1, 2)
TypeError: len() takes exactly one argument (2 given)

This is nothing special about built-ins. The same goes for:
>>> def f(): pass
>>> f(1)
TypeError: f() takes 0 positional arguments but 1 was given

I find this behaviour perfectly satisfactory. The OP's problem can be
resolved by looking at the exception, whose type is TypeError.

For this to be completely reliable, certain aspects the exception message
need to be stable. It may be worth stating that this will be done, at least
for certain exception.

By the way, using the exception instance will produce code that works with
earlier versions of Python.

-- 
Jonathan
___
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/CLC5PF3LY4FELGWG7DIE2W2X3SKJXJKC/
Code of Conduct: http://python.org/psf/codeofconduct/


[Python-ideas] Re: Exception for parameter errors

2020-03-04 Thread Andrew Barnert via Python-ideas
On Mar 3, 2020, at 23:11, Steven D'Aprano  wrote:
> 
> But for stability reasons (what if the error messages are localised, or 
> change in the future?), we could give the exception a "reason" 
> attribute, with a documented stable ID, and make that the official way 
> to programmatically distinguish between the different kinds of parameter 
> errors:
> 
>except ParameterError as err:
>if err.reason == 1:
># too few arguments

I liked everything up to using magic numbers like this. Nobody will remember 
whether it’s 1 for too many arguments or too few; they’ll have to check the 
docs every time it’s been more than 20 minutes since they wrote the last 
handler. If you need to programmatically distinguish these, and you can’t use 
subclasses to do it, at least make it something like a named class attribute, 
so you can check if it’s TooFewArguments instead of 1. (That still isn’t as 
nice as an enum, but presumably 95% of the time when you display the reason 
you’ll also be displaying the error message, so that’s fine.)

The other question: what exactly do you propose to change? I don’t think you 
can do this on the caller side (the ceval bytecode handler and the PyCall 
functions). But if you put it on the callee side, how do you handle everything? 
Presumably you want to handle C functions as well as Python functions; can you 
change the PyArg parsing functions? (I assume argclinic uses those?) That still 
leaves functions that parse *args and **kw and raise TypeError manually, 
whether in Python or in C, but I suppose you can say that in that case it 
really isn’t a parameter error (the signature really is *args, **kw so 
everything matches). What about inspect.Signature.bind?

___
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/5MICTLXMFJSZLHV5NIJXFJJE3PQX4URK/
Code of Conduct: http://python.org/psf/codeofconduct/