PEP new assert idiom
Bengt Richter
bokr at oz.net
Sun Nov 7 02:43:59 EST 2004
On Sun, 07 Nov 2004 03:50:43 GMT, "Raymond Hettinger" <vze4rx4y at verizon.net> wrote:
>[Fábio Mendes]
>> This is very similar to what I'm proposing, with the only inconvinience
>> that is uglier to type:
>>
>> assert \
>> exp1 and \
>> exp2 and \
>> ...
>> expn,
>> 'ErrorMsg'
>>
>> Instead of:
>>
>> assert \
>> exp1,
>> exp2,
>> ...
>> expn,
>> 'Errormsg'
>>
>> Well, I realize I didn't expressed my thoughts very clearly and that
>> it's indeed a very minor change in python's syntax. It won't let anyone
>> does anything new, IFAIK, but it makes a common pattern of code a little
>> more beautiful, (and why not? more expressive). If one consider the fact
>> that it won't break old code (only in one very unlikely case) I don't
>> see it as a completely unreasonable suggestion. Other people may think
>> differently though.
>
>The odds of Guido accepting this proposal are about zero. As you say, it
>doesn't do anything new, but it does require altering the grammar. Besides,
>TOOWTDI.
>
>Also, Guido tends to not be persuaded by arguments about "too much typing".
>This is doubly true is you're talking about replacing an "and" with a comma (big
>whoop).
>
>Also, one of the existing alternatives is quite readable:
>
>err = 'Errormsg'
>assert exp1, err
>assert exp2, err
>assert exp3, err
>
>The alternative has the advantage that a failure will point to the exact
>expression that failed. The disadvantage is the repetition of the error
>message; however, I disagree that your use case is common. Instead, it is
>likely more advantageous to have different error messages for each expression.
>For example, the following comes from the post condition checks in QR matrix
>decomposition:
>
>assert Q.tr().mmul(Q)==eye(min(m,n)), "Q is not orthonormal"
>assert isinstance(R,UpperTri), "R is not upper triangular"
>assert R.size==(m,n), "R is does not match the original dimensions"
>assert Q.mmul(R)==self, "Q*R does not reproduce the original matrix"
>
>One could link all of these by an "and" or the proposed comma, but then you
>end-up with a single, less informative error message, "The QR decomposition
>bombed, but I won't tell you why ;-) ".
>
Besides, if you want the single message with comma-delimited expressions,
you can already write:
>>> assert False not in map(bool, (
... True,
... 1,
... 2==2
... )), 'Error message'
Or to show what happens
>>> assert False not in map(bool, (
... True,
... 1,
... 2==3
... )), 'Error message'
Traceback (most recent call last):
File "<stdin>", line 1, in ?
AssertionError: Error message
Or with a little helper, and then two boilerplate lines for the assert,
you can have individual messages:
>>> def fff(*xm):
... """find first false xpr in seq xpr ,msg, xpr, msg and yield pair"""
... it = iter(xm)
... for x in it:
... m = it.next()
... if not x: yield x, m; break
...
>>> def test(x):
... assert not [t for t in fff(
...
... True, 'true msg',
... 1, 'one msg',
... 2==x, '2 eq x msg',
... 'xx', 'xx msg'
...
... )], 'Error: expr == %r, msg = %r'%t
...
>>> test(2)
>>> test(3)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 7, in test
AssertionError: Error: expr == False, msg = '2 eq x msg'
Actually, why not just make a utility function "asserts" to do it:
>>> def asserts(*expr_msg_seq):
... """find first false expression value and assert it with paired message"""
... it = iter(expr_msg_seq)
... for x in it:
... m = it.next()
... if not x:
... assert x, '%r -> %r'%(x, m)
...
>>> def test(x):
... asserts(
... True, 'true msg',
... 1, 'one msg',
... x, 'bool(x) is not True',
... x==2, '2 eq x msg',
... 'xx', 'xx msg'
... )
...
>>> test(2)
>>> test(3)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 7, in test
File "<stdin>", line 7, in asserts
AssertionError: False -> '2 eq x msg'
>>> test(0)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 7, in test
File "<stdin>", line 7, in asserts
AssertionError: 0 -> 'bool(x) is not True'
>>> test(())
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 7, in test
File "<stdin>", line 7, in asserts
AssertionError: () -> 'bool(x) is not True'
>>> test(type('foo',(),{'__nonzero__':lambda self:0})())
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 7, in test
File "<stdin>", line 7, in asserts
AssertionError: <__main__.foo object at 0x02EF17AC> -> 'bool(x) is not True'
>>> test(type('foo',(),{'__nonzero__':lambda self:1})())
Traceback (most recent call last):
File "<stdin>", line 1, in ?
File "<stdin>", line 7, in test
File "<stdin>", line 7, in asserts
AssertionError: False -> '2 eq x msg'
>>> test(type('foo',(),{'__nonzero__':lambda self:1, '__cmp__':lambda s,o:0})())
I doesn't shortcut, so you could get an exception in preparing the arg list for
a sequence like
asserts(
den !=0, 'denom must be zero',
num/den>5, 'better write it as assert num>5*den'
)
which would be safer as
assert den !=0, 'denom must be zero'
assert num/den>5 'better write it as assert num>5*den'
Not to mention side effects, but you shouldn't have those in asserts anyway.
Silliness ;-)
Regards,
Bengt Richter
More information about the Python-list
mailing list