-
Notifications
You must be signed in to change notification settings - Fork 36
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
Delay import of sympy until actually needed. #194
Conversation
db50cae
to
6327459
Compare
src/python/bezier/_symbolic.py
Outdated
|
||
return ensure_sympy | ||
|
||
|
||
@require_sympy | ||
def to_symbolic(nodes): | ||
def to_symbolic(sympy, nodes): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would mean Args
needs to be updated for this function. (Once upon a time Pylint would've flagged the mismatch, not sure what the difference is has changed.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I spoke too soon, lint failure from this PR:
************* Module bezier._symbolic
src/python/bezier/_symbolic.py:52:0: W9015: "sympy" missing in parameter documentation (missing-param-doc)
src/python/bezier/_symbolic.py:52:0: W9016: "sympy" missing in parameter type documentation (missing-type-doc)
src/python/bezier/_symbolic.py:73:0: W9015: "sympy" missing in parameter documentation (missing-param-doc)
src/python/bezier/_symbolic.py:73:0: W9016: "sympy" missing in parameter type documentation (missing-type-doc)
src/python/bezier/_symbolic.py:98:0: W9015: "sympy" missing in parameter documentation (missing-param-doc)
src/python/bezier/_symbolic.py:98:0: W9016: "sympy" missing in parameter type documentation (missing-type-doc)
src/python/bezier/_symbolic.py:111:16: E1120: No value for argument 'nodes' in function call (no-value-for-parameter)
src/python/bezier/_symbolic.py:114:31: E1120: No value for argument 's' in function call (no-value-for-parameter)
src/python/bezier/_symbolic.py:122:0: W9015: "sympy" missing in parameter documentation (missing-param-doc)
src/python/bezier/_symbolic.py:122:0: W9016: "sympy" missing in parameter type documentation (missing-type-doc)
src/python/bezier/_symbolic.py:139:0: W9015: "sympy" missing in parameter documentation (missing-param-doc)
src/python/bezier/_symbolic.py:139:0: W9016: "sympy" missing in parameter type documentation (missing-type-doc)
src/python/bezier/_symbolic.py:157:22: E1120: No value for argument 'degree' in function call (no-value-for-parameter)
src/python/bezier/_symbolic.py:159:11: E1120: No value for argument 's' in function call (no-value-for-parameter)
src/python/bezier/_symbolic.py:139:22: W0613: Unused argument 'sympy' (unused-argument)
src/python/bezier/_symbolic.py:163:0: W9015: "sympy" missing in parameter documentation (missing-param-doc)
src/python/bezier/_symbolic.py:163:0: W9016: "sympy" missing in parameter type documentation (missing-type-doc)
src/python/bezier/_symbolic.py:197:0: W9015: "sympy" missing in parameter documentation (missing-param-doc)
src/python/bezier/_symbolic.py:197:0: W9016: "sympy" missing in parameter type documentation (missing-type-doc)
src/python/bezier/_symbolic.py:211:16: E1120: No value for argument 'nodes' in function call (no-value-for-parameter)
src/python/bezier/_symbolic.py:214:31: E1120: No value for argument 't' in function call (no-value-for-parameter)
src/python/bezier/_symbolic.py:222:0: W9015: "sympy" missing in parameter documentation (missing-param-doc)
src/python/bezier/_symbolic.py:222:0: W9016: "sympy" missing in parameter type documentation (missing-type-doc)
src/python/bezier/_symbolic.py:246:0: W9015: "sympy" missing in parameter documentation (missing-param-doc)
src/python/bezier/_symbolic.py:246:0: W9016: "sympy" missing in parameter type documentation (missing-type-doc)
src/python/bezier/_symbolic.py:264:25: E1120: No value for argument 'degree' in function call (no-value-for-parameter)
src/python/bezier/_symbolic.py:266:11: E1120: No value for argument 't' in function call (no-value-for-parameter)
src/python/bezier/_symbolic.py:246:25: W0613: Unused argument 'sympy' (unused-argument)
************* Module bezier.triangle
src/python/bezier/triangle.py:1180:29: E1120: No value for argument 'degree' in function call (no-value-for-parameter)
src/python/bezier/triangle.py:1218:15: E1120: No value for argument 'degree' in function call (no-value-for-parameter)
************* Module bezier.curve
src/python/bezier/curve.py:764:26: E1120: No value for argument 'degree' in function call (no-value-for-parameter)
src/python/bezier/curve.py:800:15: E1120: No value for argument 'degree' in function call (no-value-for-parameter)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's not really an argument -- callers don't need to pass it, the decorator handles it.
I guess getting rid of requires_sympy
and having something like
def _load_sympy():
try: import sympy
except ImportError: raise OSError(...)
else: return sympy
def to_symbolic(nodes):
sympy = _load_sympy()
...
would minimize repetition which remaining linter-friendly?
Will try to think about what I'd "prefer" to make Pylint happy. While we're on this "avoid imports" kick, does the SciPy import also slow things down? It's only needed for the pure Python implementations, so if you've got a wheel (or you manually built the Fortran speedups from source) you never need SciPy. |
Sure, looks like (in a completely clean venv) the presence of scipy slows down importing bezier from ~150ms to 240ms, so while it's not as much as sympy, it's definitely quite significant. I can prepare a PR once we figure out the best pattern to deal with sympy. |
Somewhat related (i.e. caused builds in #195 to fail): I pushed a change to avoid pushing to Merge |
I can rebase, but let's first figure out how you prefer me to import sympy? |
So one of the things that's throwing off Pylint is the fact that the decorator "swallows" one of the arguments. I'm not a fan of magic like that either, so rather than a decorator, we could at least consider starting each function with sympy = require_sympy() (and changing In |
basically #194 (comment)? |
Haha absolutely yes! |
done |
sympy is a rather slow import (~330ms for me) which contributes nearly half of bezier's import time (~750ms). Delay the import until actually needed. As a typical use case where this is visible: I have a command-line utility which does some computation using bezier. Right now invoking something as simple as `my-program --help` takes over a second to complete (certainly a noticeable delay), in particular due to such slow imports -- even if I do not use the symbolic computation part at all. Obviously there are other ways in which this can be implemented, e.g. by duplicating the `try... except ImportError` in each function, or with another helper function, but given that you already have a requires_sympy decorator, I thought I may as well reuse it for this purpose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but I'm feeling like we can ditch require_sympy()
altogether. Do you think it's a bad user experience to get an ImportError
with no explanation why your dependency is missing?
I actually think an ImportError is nicer than an OSError -- if I have a missing package, I'd rather have the "natural" exception being thrown (especially that with recent Pythons you get an even more accurate ModuleNotFoundError), rather than some wrapper around it. |
Good deal! So let's dump the |
Please do :) |
sympy is a rather slow import (~330ms for me) which contributes nearly
half of bezier's import time (~750ms). Delay the import until actually
needed.
As a typical use case where this is visible: I have a command-line
utility which does some computation using bezier. Right now invoking
something as simple as
my-program --help
takes over a second tocomplete (certainly a noticeable delay), in particular due to such slow
imports -- even if I do not use the symbolic computation part at all.
Obviously there are other ways in which this can be implemented, e.g. by
duplicating the
try... except ImportError
in each function, or withanother helper function, but given that you already have a
requires_sympy decorator, I thought I may as well reuse it for this
purpose.
Edit: The decorator magic apparently makes pylint/flake8 extremely unhappy. I can fix that with whichever approach you prefer -- skip linting on the offending lines, or just duplicate the try: except import everywhere, or a helper function.