Skip to content

Commit dc40166

Browse files
committed
Make module loading lazy
1 parent f0be124 commit dc40166

File tree

2 files changed

+67
-33
lines changed

2 files changed

+67
-33
lines changed

julia/core.py

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,50 @@ class JuliaError(Exception):
4545
pass
4646

4747

48+
def jl_name(name):
49+
if name.endswith('_b'):
50+
return name[:-2] + '!'
51+
return name
52+
53+
4854
class JuliaModule(ModuleType):
49-
pass
55+
56+
def __init__(self, loader, *args, **kwargs):
57+
super(JuliaModule, self).__init__(*args, **kwargs)
58+
self._julia = loader.julia
59+
self.__loader__ = loader
60+
61+
def __getattr__(self, name):
62+
try:
63+
return self.__try_getattr(name)
64+
except AttributeError:
65+
if name.endswith("_b"):
66+
try:
67+
return self.__try_getattr(jl_name(name))
68+
except AttributeError:
69+
pass
70+
raise
71+
72+
def __try_getattr(self, name):
73+
juliapath = self.__name__.lstrip("julia.")
74+
try:
75+
module_path = ".".join((juliapath, name))
76+
is_module = self._julia.eval("isa({}, Module)".format(module_path))
77+
if is_module:
78+
# TODO: find a better way to handle submodules
79+
split_path = module_path.split(".")
80+
is_base = split_path[-1] == "Base"
81+
recur_module = split_path[-1] == split_path[-2]
82+
if not is_base and not recur_module:
83+
newpath = ".".join((self.__name__, name))
84+
return self.__loader__.load_module(newpath)
85+
return self._julia.eval(module_path)
86+
except Exception:
87+
if isafunction(self._julia, name, mod_name=juliapath):
88+
func = "{}.{}".format(juliapath, name)
89+
return self._julia.eval(func)
90+
91+
raise AttributeError(name)
5092

5193

5294

@@ -72,38 +114,7 @@ def __init__(self, julia):
72114
def load_module(self, fullname):
73115
juliapath = fullname.lstrip("julia.")
74116
if isamodule(self.julia, juliapath):
75-
mod = sys.modules.setdefault(fullname, JuliaModule(fullname))
76-
mod.__loader__ = self
77-
names = self.julia.eval("names({}, true, false)".format(juliapath))
78-
for name in names:
79-
if (ismacro(name) or
80-
isoperator(name) or
81-
isprotected(name) or
82-
notascii(name)):
83-
continue
84-
attrname = name
85-
if name.endswith("!"):
86-
attrname = name.replace("!", "_b")
87-
if keyword.iskeyword(name):
88-
attrname = "jl".join(name)
89-
try:
90-
module_path = ".".join((juliapath, name))
91-
module_obj = self.julia.eval(module_path)
92-
is_module = self.julia.eval("isa({}, Module)"
93-
.format(module_path))
94-
if is_module:
95-
split_path = module_path.split(".")
96-
is_base = split_path[-1] == "Base"
97-
recur_module = split_path[-1] == split_path[-2]
98-
if is_module and not is_base and not recur_module:
99-
newpath = ".".join((fullname, name))
100-
module_obj = self.load_module(newpath)
101-
setattr(mod, attrname, module_obj)
102-
except Exception:
103-
if isafunction(self.julia, name, mod_name=juliapath):
104-
func = "{}.{}".format(juliapath, name)
105-
setattr(mod, name, self.julia.eval(func))
106-
return mod
117+
return sys.modules.setdefault(fullname, JuliaModule(self, fullname))
107118
elif isafunction(self.julia, juliapath):
108119
return getattr(self.julia, juliapath)
109120
else:

test/test_core.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import array
22
import math
33
import unittest
4+
from types import ModuleType
45

56
from julia import Julia, JuliaError
67
import sys
@@ -61,6 +62,28 @@ def test_import_julia_functions(self):
6162
else:
6263
pass
6364

65+
def test_import_julia_module_existing_function(self):
66+
from julia import Base
67+
assert Base.mod(2, 2) == 0
68+
69+
def test_import_julia_module_non_existing_name(self):
70+
from julia import Base
71+
try:
72+
Base.spamspamspam
73+
self.fail('No AttributeError')
74+
except AttributeError:
75+
pass
76+
77+
def test_julia_module_bang(self):
78+
from julia import Base
79+
xs = [1, 2, 3]
80+
ys = Base.scale_b(xs[:], 2)
81+
assert all(x * 2 == y for x, y in zip(xs, ys))
82+
83+
def test_import_julia_submodule(self):
84+
from julia.Base import REPL
85+
assert isinstance(REPL, ModuleType)
86+
6487
#TODO: this causes a segfault
6588
"""
6689
def test_import_julia_modules(self):

0 commit comments

Comments
 (0)