Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't use assert in the library code when an exception would be better #5090

Open
asmeurer opened this issue Jul 20, 2010 · 16 comments · Fixed by #26530 or #26536
Open

Don't use assert in the library code when an exception would be better #5090

asmeurer opened this issue Jul 20, 2010 · 16 comments · Fixed by #26530 or #26536
Assignees
Labels
Bug Code quality Easy to Fix This is a good issue for new contributors. Feel free to work on this if no one else has already. imported

Comments

@asmeurer
Copy link
Member

asmeurer commented Jul 20, 2010

Basically, you should rewrite it to use an if construct and a proper exception (ValueError, TypeError, NotImplementedError, or a special error for the module are the usual suspects).

$ git grep assert | grep -v test

Will show most offending lines. I have already fixed matrices.py in my integration3 branch.

Original issue for #5090: http://code.google.com/p/sympy/issues/detail?id=1991
Original author: https://code.google.com/u/asmeurer@gmail.com/
Original owner: https://code.google.com/u/asmeurer@gmail.com/

@asmeurer
Copy link
Member Author

asmeurer commented Jul 20, 2010

This is easy to fix. Basically, each assertion should be negated using the rules of http://en.wikipedia.org/wiki/De_Morgan%27s_Law , i.e., change

assert x is None and y == 0

to

if x is not None or y != 0:
    raise ReleventError("With a good error message, too.")

And also, you might as well add a raises() test to the relevant test file, if you can reproduce the exception (I have done this for the matrices, which is 846af4a by the way, and can be reviewed).

Labels: EasyToFix NeedsReview

Original comment: http://code.google.com/p/sympy/issues/detail?id=1991#c1
Original author: https://code.google.com/u/asmeurer@gmail.com/

@smichr
Copy link
Member

smichr commented Jul 22, 2010

Sometimes the assert is there because that is an assumption at that point in the code that you want the reader to be made aware of. They are like road signs that help keep you oriented...not that you couldn't look at a map and figure out where you were, but in the limited amount of lines to a screen (and finite "where am I" memory) they also provide a nanny-ish role. e.g. in the ode module there is a portion where perhaps you think that there should only be one solution and you act on that belief. I would just like to put "assert len(sol) == 1" and proceed with confidence. I maybe can't (in my limited imagination) think of a case where there would be more solutions but if, for some reason there are I want someone to make the code len(sol) > 1 compliant. "Today" I don't want to have to think up imaginative names for errors that probably won't exist. I am happy enough to know that I alerted someone -- like a zone of red on a tachometer -- that there is a pending problem.

I once looked at some personal code of Tim Peters and it was loaded with assertions. He writes some pretty slick stuff. I asked what that was for and he taught me about the assertion.

I'm not saying there isn't a place for a good error message. I am just saying that sometimes it isn't a user error as much as a logical error that the assertion protects against.

Original comment: http://code.google.com/p/sympy/issues/detail?id=1991#c2
Original author: https://code.google.com/u/117933771799683895267/

@asmeurer
Copy link
Member Author

asmeurer commented Jul 22, 2010

So maybe they do have their place. I just got through removing all assertions from matrix.py (see the above cited commit), because it had things like

assert self.cols == self.row

when there was even a custom NonSquareMatrixError already made. So those obviously should have been

if not self.is_square:
    raise NonSquareMatrixError("A matrix must be square to apply <whatever>.")

Here's the problem with having assertions in code like that. If you are writing general purpose code, say something that calls on an arbitrary matrix the .inv() method, which only works if the matrix is square. You then see that it sometimes fails with AssertionError, so you write your code as

try:
    M.inv()
except AssertionError:
    raise NotImplementedError

But except AssertionError will catch all AssertionError's that that code block happens to raise, and in this case, will mask them with NotImplementedError, which no longer looks like a bug. You really only wanted the try block to catch the case where the matrix inversion failed because it was not square, so it's much better to have Matrix.inv() raise NonSquareMatrixError and for your code to catch that. I remember making a change like this in solve() last year (cf781bc). I was forced to have "except AssertionError, ValueError, NotImplementedError:" in my code, where I really only wanted "except NotImplementedError:". Keeping those other ones would have masked other errors in the code that raised those exceptions. So I think you should only use assertions where the code should unequivocally stop because some assertion that has to be true for the algorithm to be correct no longer is. In other words, if the assertion does fail, it's not because of bad input, but because there is some unexpected bug in my code, which if I ever see this error I will need to go in and fix, or special case with a more descriptive exception.

So maybe the issue title should be "Don't use assert in the library code when a better exception can be used instead," i.e., ValueError, TypeError, NotImplementedError, etc. And even if the assertion does belong, it should be given an error message, which can be done by adding a string to the end of the line:

assert a == b, "We seem to have run into a problem."

Original comment: http://code.google.com/p/sympy/issues/detail?id=1991#c3
Original author: https://code.google.com/u/asmeurer@gmail.com/

@asmeurer
Copy link
Member Author

asmeurer commented Sep 6, 2010

**Summary:** Don't use assert in the library code when an exception would be better  
**Labels:** asmeurer  

Original comment: http://code.google.com/p/sympy/issues/detail?id=1991#c4
Original author: https://code.google.com/u/asmeurer@gmail.com/

@asmeurer
Copy link
Member Author

The fix mentioned above is included in my integration3-backport branch ( https://github.com/sympy/sympy/pull/131 ).

**Status:** Started  

Original comment: http://code.google.com/p/sympy/issues/detail?id=1991#c5
Original author: https://code.google.com/u/asmeurer@gmail.com/

@asmeurer
Copy link
Member Author

asmeurer commented May 3, 2011

The patch was pushed in. I'm leaving this open because that patch only addressed the matrices code.

**Labels:** -NeedsReview -asmeurer  

Original comment: http://code.google.com/p/sympy/issues/detail?id=1991#c6
Original author: https://code.google.com/u/asmeurer@gmail.com/

@shiprabanga
Copy link
Contributor

shiprabanga commented Dec 6, 2013

I want to apply for GSOC 2014 and I am interested in solving this bug. Although I am still a little bit confused as to where assert is necessary and where it has to be replaced with a raise exception construct.

Original comment: http://code.google.com/p/sympy/issues/detail?id=1991#c7
Original author: https://code.google.com/u/100029932375768335599/

@asmeurer
Copy link
Member Author

asmeurer commented Dec 6, 2013

See the top of this page for a command that will show where assert is used.

Original comment: http://code.google.com/p/sympy/issues/detail?id=1991#c8
Original author: https://code.google.com/u/asmeurer@gmail.com/

@asmeurer
Copy link
Member Author

asmeurer commented Dec 6, 2013

Here's a useful guide on when assert should and should not be used. https://mail.python.org/pipermail/python-list/2013-November/660401.html

Original comment: http://code.google.com/p/sympy/issues/detail?id=1991#c9
Original author: https://code.google.com/u/asmeurer@gmail.com/

@shiprabanga
Copy link
Contributor

shiprabanga commented Dec 15, 2013

I addressed this issue and I have went through the entire code base and removed asserts wherever necessary.Please refer to this: #2655

Original comment: http://code.google.com/p/sympy/issues/detail?id=1991#c10
Original author: https://code.google.com/u/100029932375768335599/

@langston-barrett
Copy link
Contributor

PR #2655 was merged. Does any work still need to be done here?

@asmeurer
Copy link
Member Author

The command from the top shows some asserts for me, although I don't know how many of them are valid. I saw at least one that isn't:

sympy/solvers/pde.py:851:        assert ValueError('Unknown strategy: %s' % strategy)

@SaiHarsh
Copy link

SaiHarsh commented Mar 6, 2017

@asmeurer I have changed sympy/solvers/pde.py:851: assert ValueError('Unknown strategy: %s' % strategy) to exception
It works the same
strategy_dic={}
strategy_dic['add'] = True
strategy_dic['mul'] = False
try:
do_add = strategy_dic[strategy]
print do_add
except:
print('Unknown strategy: %s' % strategy)
(It's properly indented code just the look is different).
I have found few asserts that can be converted to exception.
sympy/solvers/diophantine.py:2660,2665,2672
Please have a look once.

gxyd added a commit that referenced this issue Oct 14, 2017
Replaced assert with exception, issue #5090
czgdp1807 added a commit that referenced this issue Sep 26, 2019
Replaced some asserts in non-test code with exceptions (#5090)
@rohan-agr
Copy link

Hi,

@ethandeguire and I have begun looking into this issue. We are new contributors to open source. Has any new work been done on this issue/are there any guidelines we should be aware of in fixing this? We were planning of just expanding on the work that others have done in replacing some asserts with exceptions in files where it is still necessary

NickHarder added a commit to NickHarder/sympy that referenced this issue Apr 22, 2024
Replaced assertion with raising a TypeError in core/backend.py when matrix M
is not an instance of Matrix or Immutable Matrix. Does not create accompanying
core/tests/test_backend.py.

Issue: sympy#5090
NickHarder added a commit to NickHarder/sympy that referenced this issue Apr 23, 2024
Replaced assertion with raising a TypeError in core/backend.py when matrix M
is not an instance of Matrix or Immutable Matrix. Does not create accompanying
core/tests/test_backend.py.

Issue: sympy#5090
@ethandeguire
Copy link
Contributor

ethandeguire commented Apr 23, 2024

Hello, I am working on this issue and I just wanted to add a note about finding these potentially unwanted assert statements:

Modified command to find offending line, and print their file and line number, excluding documentation and test files (.rst, .md)
git grep -n assert | grep -v test | grep -v .rst | grep -v .md

Or to find all files with an offending assert:
git grep -n assert | grep -v test | grep -v .rst | grep -v .md | cut -d':' -f1 | sort | uniq

@asmeurer
Copy link
Member Author

I think this wasn't supposed to be automatically closed.

@asmeurer asmeurer reopened this Apr 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment