List comprehension for testing **params
Steven D'Aprano
steve+comp.lang.python at pearwood.info
Mon Nov 12 02:32:02 EST 2012
On Sun, 11 Nov 2012 18:21:32 -0600, Tim Chase wrote:
> On 11/11/12 17:18, Steven D'Aprano wrote:
>> but that leaves you with the next two problems:
>>
>> 2) Fixing the assert still leaves you with the wrong exception. You
>> wouldn't raise a ZeroDivisionError, or a UnicodeDecodeError, or an
>> IOError would you? No of course not. So why are you suggesting raising
>> an AssertionError? That is completely inappropriate, the right
>> exception to use is TypeError. Don't be lazy and treat assert as a
>> quick and easy way to get exceptions for free.
>
> I'd say that it depends on whether the dictionary/kwargs gets populated
> from user-supplied data (whether mail, a file, direct input, etc), or
> whether it's coder-supplied.
No, it doesn't matter if it is end-user supplied or coder-supplied. As
usual, look at the standard library and Python builtins for best
practices:
py> len(42) # do we get an AssertionError?
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: object of type 'int' has no len()
> I like to think of "assert" as a "hey, you bone-headed programmer, you
> did something that violated conditions for using this code properly!"
You mean like passing a list to ord()? Or trying to add a number and a
string?
Raise the appropriate exception for the error: TypeError for type errors
and missing function parameters, ValueErrors for objects of the right
type but a bad argument, etc. Assertion errors are for asserting facts
about internal program logic. "I assert that such-and-such is a fact",
rather than "I require that such-and-such is a fact".
For example, this might be a reasonable (although trivial) use of assert:
def spam(n):
if isinstance(n, int) and n > 0:
result = ' '.join(["spam"]*n)
assert result, "expected non-empty string but got empty"
return result
else:
raise SpamError("No spam for you!!!")
This gives a measure of protection against logic errors such as writing
n >= 0.
The presence of the assert is partly documentation and partly error
checking, but the important thing is that it checks things which depend
on the internal logic of the code itself, not on the input to the code.
Given that you have already explicitly tested that n > 0, then the
logical consequence is that "spam"*n will be non-empty. Hence an
assertion "I assert that the string is not empty", rather than an overt
test "Is the string empty?"
Now, I accept that sometimes it is hard to draw a line between "internal
program logic" and external input. E.g. if one function generates a value
x, then calls another function with x as argument, the state of x is an
internal detail to the first function and an external input to the other
function. The rules I use are:
* If the argument is a public parameter to a public function,
then you must not use assert to validate it;
* If the argument is a private parameter, or if the function
is a private function, then you may use assert to validate
it (but probably shouldn't);
* You can use assert to check conditions of function internal
variables (locals that you control);
* Once you cross the boundary to another function, your objects
should be treated as external again.
E.g. I might have:
def foo(a, b):
# foo, a and b are public, so don't use assert
if not (isinstance(a, int) and isinstance(b, int)):
raise TypeError
x = abs(a) + abs(b)
# x is purely internal, so okay to assert
assert x >= 0, "Hey McFly, you screwed up!"
return bar(x)
def bar(x):
if x <= 0:
raise ValueError("expected positive x")
...
This now guards against something other than foo calling bar.
--
Steven
More information about the Python-list
mailing list