Skip to content

Restructuring APIΒ #199

Closed
Closed
@tkf

Description

@tkf

Here are some ideas on refactoring PyJulia APIs and internals. Mainly, I suggest to introduce two new classes JuliaInfo and LibJulia.

JuliaInfo --- libjulia discovery mechanism

I suggest to turn JuliaInfo into a proper class. It is at the moment a namedtuple:

pyjulia/julia/core.py

Lines 263 to 267 in cdbefb1

JuliaInfo = namedtuple(
'JuliaInfo',
['JULIA_HOME', 'libjulia_path', 'image_file',
# Variables in PyCall/deps/deps.jl:
'pyprogramname', 'libpython'])

For example, we have is_compatible_python which is better be organized as a method.

class JuliaInfo:

    @classmethod
    def load(cls, executable="julia"):
        """
        Get basic information from Julia `executable`.
        """

    executable = ...
    """ Julia executable from which information is retrieved. """

    bindir = ...
    """ Sys.BINDIR of `.executable`. """

    libjulia_path = ...
    """ Path to libjulia. """

    python = ...
    """ Python executable with which PyCall.jl is configured. """

    libpython_path = ...
    """ libpython path used by PyCall.jl. """

    def is_compatible_python(self):
        """
        Check if python used by PyCall.jl is compatible with `sys.executable`.
        """

LibJulia --- Low-level C API

Julia has a rather lengthy __init__ which mainly setting up another object Julia.api. This is probably better be organized into another class.

Importantly, I suggest to make LibJulia.init_julia lazy (by default) and idempotent. It lets users to run functions that cannot be called after the initialization (e.g., jl_parse_opts; ok I'm not sure if there are something else) while integrating with high-level API provided by PyJulia.

def setup_libjulia(libjulia):
    libjulia.jl_eval_string.argtypes = [c_char_p]
    libjulia.jl_eval_string.restype = c_void_p
    # and so on...


class LibJulia:

    """
    Low-level interface to `libjulia` C-API.
    """

    def __init__(self, juliainfo, init_julia=None):
        libjulia_path = juliainfo.libjulia_path
        libjulia = ctypes.PyDLL(libjulia_path, ctypes.RTLD_GLOBAL)
        self.libjulia = libjulia
        self.juliainfo = juliainfo
        setup_libjulia(libjulia)

        if init_julia is not None:
            if init_julia:
                self.init_julia()
            else:
                # If `init_julia = False` is passed, remember that
                # libjulia should never be initialized again for this
                # Python process.
                self.__class__._julia_initialized = True

    _julia_initialized = False

    def init_julia(self):
        """
        Initialize `libjulia`.  Calling this method twice is a no-op.

        It calls `jl_init_with_image` (or `jl_init_with_image__threading`)
        but makes sure that it is called only once for each process.
        """
        if self._julia_initialized:
            return
        jl_init_path = self.juliainfo.bindir
        image_file = self.juliainfo.image_file.encode("utf-8")
        self.libjulia.jl_init_with_image(jl_init_path, image_file)
        # or maybe just jl_init()?

        self.__class__._julia_initialized = True
        # Using `self.__class__._julia_initialized` rather than
        # `self._julia_initialized` to have a unique flag within a
        # process.

I added some jl_unbox_* functions in another PR #186 (a954914). Adding those C API could be useful, too.

Julia --- High-level PyCall-based API

Since low-lvel libjulia handling is decoupled, Julia class now can focus on high-level API based on PyCall. I also suggest to move some "free functions" (e.g., isdefined) whose first argument was julia to methods.

class Julia:  # or maybe JuilaAPI
    """
    High-level Julia API.
    """

    capi = ...
    """ An instance of `.LibJulia` """

    def __init__(self, capi):
        self.capi = capi
        self.capi.init_julia()

    # Some functions are better be defined as methods:

    def isdefined(self, parent, member):
        ...

    def isamodule(self, julia_name):
        ...

    def isafunction(self, julia_name, mod_name=""):
        ...

get_julia

Since initializing Julia API now takes a few steps, I suggest to add a factory function which is equivalent to current julia.core.Julia.

def get_julia(init_julia=True, runtime=None, bindir=None):
    """
    Create a Python object that represents a live Julia interpreter.
    """
    return Julia(LibJulia(JuliaInfo.load(runtime), init_julia))

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions