-
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:
|
@@ -140,8 +140,6 @@ class JuliaImporter(object): | |||
|
|||
# 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?
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.eval
is 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.Main
In addition to usual
JuliaModule
, the module classJuliaMainModule
ofjulia.Main
has 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.Julia
with 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