When to use assert
Anton
anton.schattenfeld at gmail.com
Tue Oct 21 03:48:23 EDT 2014
On Saturday, November 16, 2013 11:35:50 PM UTC-8, Steven D'Aprano wrote:
> The question of when to use the assert statement comes up occasionally,
>
> usually in response to somebody misusing it, so I thought I'd write a
>
> post describing when and why to use assertions, and when not to.
>
>
>
> For those who aren't aware of it, Python's "assert" checks a condition,
>
> if it is true it does nothing, and if it is false it raises an
>
> AssertionError with an optional error message. For example:
>
>
>
> py> x = 23
>
> py> assert x > 0, "x is not zero or negative"
>
> py> assert x%2 == 0, "x is not an even number"
>
> Traceback (most recent call last):
>
> File "<stdin>", line 1, in <module>
>
> AssertionError: x is not an even number
>
>
>
>
>
> Many people use asserts as a quick and easy way to raise an exception if
>
> an argument is given the wrong value. But this is wrong, dangerously
>
> wrong, for two reasons. The first is that AssertionError is usually the
>
> wrong error to give when testing function arguments. You wouldn't write
>
> code like this:
>
>
>
> if not isinstance(x, int):
>
> raise AssertionError("not an int")
>
>
>
> you'd raise TypeError instead. "assert" raises the wrong sort of
>
> exception.
>
>
>
> But, and more dangerously, there's a twist with assert: it can be
>
> compiled away and never executed, if you run Python with the -O or -OO
>
> optimization flags, and consequently there is no guarantee that assert
>
> statements will actually be run. When using assert properly, this is a
>
> feature, but when assert is used inappropriately, it leads to code that
>
> is completely broken when running with the -O flag.
>
>
>
> When should use assert? In no particular order, assertions should be used
>
> for:
>
>
>
> * defensive programming;
>
> * runtime checks on program logic;
>
> * checking contracts (e.g. pre-conditions and post-conditions);
>
> * program invariants; and
>
> * checked documentation.
>
>
>
> (It's also acceptable to use assert when testing code, as a sort of quick-
>
> and-dirty poor man's unit testing, so long as you accept that the tests
>
> simply won't do anything if you run with the -O flag. And I sometimes use
>
> "assert False" in code to mark code branches that haven't been written
>
> yet, and I want them to fail. Although "raise NotImplementedError" is
>
> probably better for that, if a little more verbose.)
>
>
>
> Opinions on assertions vary, because they can be a statement of
>
> confidence about the correctness of the code. If you're certain that the
>
> code is correct, then assertions are pointless, since they will never
>
> fail and you can safely remove them. If you're certain the checks can
>
> fail (e.g. when testing input data provided by the user), then you dare
>
> not use assert since it may be compiled away and then your checks will be
>
> skipped.
>
>
>
> It's the situations in between those two that are interesting, times when
>
> you're certain the code is correct but not *quite* absolutely certain.
>
> Perhaps you've missed some odd corner case (we're all only human). In
>
> this case an extra runtime check helps reassure you that any errors will
>
> be caught as early as possible rather than in distant parts of the code.
>
>
>
> (This is why assert can be divisive. Since we vary in our confidence
>
> about the correctness of code, one person's useful assert is another
>
> person's useless runtime test.)
>
>
>
> Another good use for asserts is checking program invariants. An invariant
>
> is some condition which you can rely on to be true unless a bug causes it
>
> to become false. If there's a bug, better to find out as early as
>
> possible, so we make a test for it, but we don't want to slow the code
>
> down with such tests. Hence assert, which can be turned on in development
>
> and off in production.
>
>
>
> An example of an invariant might be, if your function expects a database
>
> connection to be open when it starts, and promises that it will still be
>
> open when it returns, that's an invariant of the function:
>
>
>
> def some_function(arg):
>
> assert not DB.closed()
>
> ... # code goes here
>
> assert not DB.closed()
>
> return result
>
>
>
>
>
> Assertions also make good checked comments. Instead of writing a comment:
>
>
>
> # when we reach here, we know that n > 2
>
>
>
> you can ensure it is checked at runtime by turning it into an assert:
>
>
>
> assert n > 2
>
>
>
> Assertions are also a form of defensive programming. You're not
>
> protecting against errors in the code as it is now, but protecting
>
> against changes which introduce errors later. Ideally, unit tests will
>
> pick those up, but let's face it, even when tests exist at all, they're
>
> often incomplete. Build-bots can be down and nobody notices for weeks, or
>
> people forget to run tests before committing code. Having an internal
>
> check is another line of defence against errors sneaking in, especially
>
> those which don't noisily fail but cause the code to malfunction and
>
> return incorrect results.
>
>
>
> Suppose you have a series of if...elif blocks, where you know ahead of
>
> time what values some variable is expected to have:
>
>
>
> # target is expected to be one of x, y, or z, and nothing else.
>
> if target == x:
>
> run_x_code()
>
> elif target == y:
>
> run_y_code()
>
> else:
>
> run_z_code()
>
>
>
>
>
> Assume that this code is completely correct now. But will it stay
>
> correct? Requirements change. Code changes. What happens if the
>
> requirements change to allow target = w, with associated action
>
> run_w_code? If we change the code that sets target, but neglect to change
>
> this block of code, it will wrongly call run_z_code() and Bad Things will
>
> occur. It would be good to write this block of code defensively, so that
>
> it will either be correct, or fail immediately, even in the face of
>
> future changes.
>
>
>
> The comment at the start of the block is a good first step, but people
>
> are notorious for failing to read and update comments. Chances are it
>
> will soon be obsolete. But with an assertion, we can both document the
>
> assumptions of this block, and cause a clean, immediate failure if they
>
> are violated:
>
>
>
> assert target in (x, y, z)
>
> if target == x:
>
> run_x_code()
>
> elif target == y:
>
> run_y_code()
>
> else:
>
> assert target == z
>
> run_z_code()
>
>
>
>
>
> Here, the assertions are both defensive programming and checked
>
> documentation. I consider this to be a far superior solution than this:
>
>
>
> if target == x:
>
> run_x_code()
>
> elif target == y:
>
> run_y_code()
>
> elif target == z:
>
> run_z_code()
>
> else:
>
> # This can never happen. But just in case it does...
>
> raise RuntimeError("an unexpected error occurred")
>
>
>
>
>
> This tempts some helpful developer to "clean it up" by removing the
>
> "unnecessary" test for value==c and removing the "dead code" of the
>
> RuntimeError. Besides, "unexpected error" messages are embarrassing when
>
> they occur, and they will.
>
>
>
> Design by contract is another good use of assertions. In design by
>
> contract, we consider that functions make "contracts" with their callers.
>
> E.g. something like this:
>
>
>
> "If you pass me an non-empty string, I guarantee to return the first
>
> character of that string converted to uppercase."
>
>
>
> If the contract is broken by either the function or the code calling it,
>
> the code is buggy. We say that functions have pre-conditions (the
>
> constraints that arguments are expected to have) and post-conditions (the
>
> constraints on the return result). So this function might be coded as:
>
>
>
> def first_upper(astring):
>
> assert isinstance(astring, str) and len(astring) > 0
>
> result = astring[0].upper()
>
> assert isinstance(result, str) and len(result) == 1
>
> assert result == result.upper()
>
> return result
>
>
>
>
>
> The aim of Design By Contract is that in a correct program, the pre-
>
> conditions and post-conditions will always hold. Assertions are typically
>
> used, since (so the idea goes) by the time we release the bug-free
>
> program and put it into production, the program will be correct and we
>
> can safely remove the checks.
>
>
>
> Here's my advice when *not* to use assertions:
>
>
>
> * Never use them for testing user-supplied data, or for anything
>
> where the check must take place under all circumstances.
>
>
>
> * Don't use assert for checking anything that you expect might fail
>
> in the ordinary use of your program. Assertions are for extraordinary
>
> failure conditions. Your users should never see an AssertionError;
>
> if they do, it's a bug to be fixed.
>
>
>
> * In particular, don't use assert just because it's shorter than an
>
> explicit test followed by a raise. Assert is not a shortcut for
>
> lazy coders.
>
>
>
> * Don't use them for checking input arguments to public library
>
> functions (private ones are okay) since you don't control the
>
> caller and can't guarantee that it will never break the
>
> function's contract.
>
>
>
> * Don't use assert for any error which you expect to recover from.
>
> In other words, you've got no reason to catch an AssertionError
>
> exception in production code.
>
>
>
> * Don't use so many assertions that they obscure the code.
>
>
>
>
>
>
>
> --
>
> Steven
I use ORM and often need to write a function that either takes an id of the record or already loaded model object.
So I end up writing a piece of code like below:
def do_something(instance):
if isinstance(instance_or_id, int):
instance = Model.get(instance)
assert isinstance(instance, Model)
# Code that assumes that instance is an object of type Model
do_somthing is not a part of public library, though it is a public function, which can and intended to be used by other programmers; and assert should never happen if a client uses the function as planned.
I wonder if this use-case is controversial to this part:
> Many people use asserts as a quick and easy way to raise an exception if
>
> an argument is given the wrong value. But this is wrong, dangerously
>
> wrong, for two reasons. The first is that AssertionError is usually the
>
> wrong error to give when testing function arguments. You wouldn't write
>
> code like this:
>
>
>
> if not isinstance(x, int):
>
> raise AssertionError("not an int")
>
>
>
> you'd raise TypeError instead. "assert" raises the wrong sort of
>
> exception.
>
Thanks,
Anton.
More information about the Python-list
mailing list