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