-
Notifications
You must be signed in to change notification settings - Fork 102
Make module loading lazy #162
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
Conversation
julia/core.py
Outdated
| split_path = module_path.split(".") | ||
| is_base = split_path[-1] == "Base" | ||
| recur_module = split_path[-1] == split_path[-2] | ||
| if not is_base and not recur_module: |
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.
Not sure what is the best way to handle submodules. Is there an easy way to check if some module is a submodule of other module in Julia? If not, it's probably better to let user explicitly import submodules (i.e., julia.Module1.Module2 is an AttributeError until the user does import julia.Module1.Module2)?
|
In the current master, we have Lines 77 to 88 in 7ebad22
What was the intention of this code? I mean, Was it probably not by intention? Was it maybe intended to be At the moment, this PR has no code for Python keyword handling. We can add something like: if name.startswith("jl_"):
try:
return self.__try_getattr(name[len("jl_"):])
except AttributeError:
pass |
d8c6bb8 to
d057d62
Compare
test/test_core.py
Outdated
| def test_import_without_setup(self): | ||
| command = [sys.executable, '-c', 'from julia import Base'] | ||
| print('Executing:', *command) | ||
| subprocess.check_call(command, env=_orig_env) |
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.
env=_orig_env probably is required to fix https://travis-ci.org/JuliaPy/pyjulia/jobs/379514563#L298
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.
Yes it looks like it fixes the failure: https://travis-ci.org/JuliaPy/pyjulia/builds/379524930
|
CI is now fixed, so you might want to rebase this PR |
|
Thanks. Rebased. |
julia/core.py
Outdated
| def __try_getattr(self, name): | ||
| juliapath = remove_prefix(self.__name__, "julia.") | ||
| try: | ||
| module_path = ".".join((juliapath, name)) |
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.
maybe qualified_name or something? Calling this a "path" is a bit confusing.
julia/core.py
Outdated
| return self.__loader__.load_module(newpath) | ||
| return self._julia.eval(module_path) | ||
| except JuliaError: | ||
| if isafunction(self._julia, name, mod_name=juliapath): |
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.
I'm confused by this isafunction check. Didn't it already try self._julia.eval(module_path) for all objects that are not modules, including functions? And if that threw an error, shouldn't we rethrow it?
julia/core.py
Outdated
|
|
||
| def __try_getattr(self, name): | ||
| juliapath = remove_prefix(self.__name__, "julia.") | ||
| try: |
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.
instead of this try block, maybe we should try calling isdefined(module, :name), throwing an AttributeError if it returns false, and letting any JuliaError that is thrown fall through to the caller.
|
@stevengj Thanks for the code review!
Some additional changes:
|
|
|
||
| # find_module was deprecated in v3.4 | ||
| def find_module(self, fullname, path=None): | ||
| if path is None: |
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.
It looks like this code was added in fa157b1 and carried around as-is.
Probably just a remnant of experimental code?
kmsquire
left a comment
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.
I made a few suggestions regarding wording changes.
I like the changes, especially being able to import Julia modules without setup.
However, I find that when I run ipython3 from the command line, I get a segfault during tab completion of anything in a Julia module:
# pip3 install ipython # if necessary
> ipython
Python 3.6.5 (default, Jun 17 2018, 12:13:06)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import julia
In [2]: from julia import Base
In [3]: Base.<tab>[1] 79420 segmentation fault ipython
master does not have this problem. Using regular python3 (instead of ipython3) does not have this problem.
README.md
Outdated
| Main.xs = [1, 2, 3] | ||
| ``` | ||
|
|
||
| so that, e.g., it can be evaluated at Julia side using Julia syntax: |
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.
Perhaps change the wording to:
which allows it to be accessed directly from Julia code, e.g.,
(While one can do this, I think it's better to pass values via function call arguments, rather than rely on values existing in global scope.)
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.
I totally agree that communication-by-sharing is a terrible idea and using this feature in Python modules should be forbidden :) But I was thinking in terms of interactive usage, mostly via %julia. In that case, passing around code via global namespace is pretty useful.
README.md
Outdated
| ### Low-level interface | ||
|
|
||
| If you need a custom setup for `pyjulia`, it must be done *before* any | ||
| imports of Julia modules. For example, to use Julia interpreter at |
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.
Suggestion for some (minor) wording changes:
If you need a custom setup for `pyjulia`, it must be done *before*
importing any Julia modules. For example, to use the Julia interpreter at
README.md
Outdated
| ### High-level interface | ||
|
|
||
| To call a Julia function in Julia module, import the Julia module (say | ||
| `Base`) by: |
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.
Suggestion for (minor) wording changes:
To call a Julia function in a Julia module, import the Julia module (say
`Base`) with
|
Hi @kmsquire, thanks a lot for correcting my English! BTW, it's interesting that you find that IPython tab completion works in pyjulia master. I thought, in theory, pyjulia would never work with IPython < 7.0 since the tab-completion is done in different thread #132 (comment) and pyjulia (or rather libjulia, I guess) is not thread-safe. I actually find out that IPython master (= 7.x) already works in single-thread just now. I have to check it... |
|
Test failure is due to CI setting. See #171. |
|
Is this ready to merge? I haven't really been keeping up with pyjulia… |
|
I think so. I mean, I used this branch for a few weeks just after creating it and I don't remember encountering any regressions. After that, the only change was in README. I'm not planning to add anything more to this PR. |
It adds an easier way to set variables in Julia's global namespace: >>> from julia import Main >>> Main.xs = [1, 2, 3]
This makes `from julia import <Julia module>` works without the initial setup. Note that a custom setup can still be done by calling `julia.Julia` with appropriate arguments *before* trying to import Julia modules. closes JuliaPy#39, JuliaPy#79
Otherwise, trying to access undefined variables such as `julia.Base.__path__` produces a warning from Julia before `JuliaModule.__getattr__` raising `AttributeError`.
as Base.REPL was pulled out from Base in Julia 0.7.
|
Rebased onto master. Travis and AppVeyor are all green now. |
@stevengj suggested to make JuliaModule lazy: #159 (comment)
Here is my implementation for that. I actually added some more changes on top that to solve relevant issues. If some components of the changes have to be removed, let me know. Those components are implemented roughly in the following order:
Basing on CI fix #149First of all this PR is based on #149 to run CI. Let me know if I need to rebase once #149 is merged.(Edited: now rebased)
Lazy module member loading
Julia.evalis called viaJuliaModule.__getattr__so that Julia object is brought to Python world only when it is requested.Implemented in dc40166 and finished by 843b519
Julia's interpreter global namespace as a module
julia.MainIn addition to usual
JuliaModule, the module classJuliaMainModuleofjulia.Mainhas an implementation of__setattr__so that you can send Python object to Julia by just setting it:Since it supersede
core.Julia, I suggest to deprecate accessing Julia values viacore.Julia().<name>: b30af45Automatically initialize Julia with
from julia import ...This makes
from julia import <Julia module>works without the initial setup. Note that a custom setup can still be done by callingjulia.Juliawith appropriate arguments before trying to import Julia modules:51997c0
See also the document on the new API:
https://github.com/tkf/pyjulia/blob/lazy-module/README.md#usage