pyjulia icon indicating copy to clipboard operation
pyjulia copied to clipboard

Pass symbol to Julia function

Open jdupl123 opened this issue 5 years ago • 6 comments

I am trying to pass a symbol to the hclust function from http://juliastats.github.io/Clustering.jl/stable/hclust.html

from julia import Clustering as C
result = hclust(costs, linkage=":ward")

I keep getting

TypeError: in #hclust, in typeassert, expected Symbol, got String

I also tried

from julia import Main as M
C.hclust(costs, linkage=M.Symbol('ward'))

but same error. I believe this is because PyCall is converting the symbol to a string before passing it through.

Current work around is:

jl = Julia(compiled_modules=False)
M.costs = costs
ctree=jl.eval('hclust(costs, linkage=:ward)')

jdupl123 avatar Jul 12 '19 09:07 jdupl123

This is a PyCall issue https://github.com/JuliaPy/PyCall.jl/issues/11

jl = Julia(compiled_modules=False)
M.costs = costs
ctree=jl.eval('hclust(costs, linkage=:ward)')

If you need to call hclust many times, a slightly better workaround than mutating the global variable in Main is to create a (anonymous) function on the Julia side:

hclust = jl.eval("costs -> hclust(costs, linkage=:ward)")
hclust(costs)

tkf avatar Jul 12 '19 09:07 tkf

Thanks for the quick reply.

jdupl123 avatar Jul 12 '19 11:07 jdupl123

I came across the same problem when writing a python wrapper for my Julia project. In the project, I'm using several “configuration structs” written with Parameters.jl so that the constructors are quite flexible. These struct have many fields, some of which require Symbol values. Wrapping them in a PyCall.pyfunction or anonymous functions would have resulted in a loss flexibility and would have made matters somewhat complicated.

Instead, I Main.include the following script right after initializing Julia in Python:

PC = PyCall;

PC.py"""
class SymStr():
    def __init__(self, *args, **kwargs):
        self.s = str(*args, **kwargs)
    def __str__(self):
        return self.s.__str__()
    def __repr__(self):
        return f'SymStr("{self.__str__()}")'
"""

sym_str_py_type = PC.py"SymStr";

PC.PyObject( s :: Symbol ) = PC.py"SymStr($(string(s)))"o
function PC.convert( ::Type{Symbol}, po :: PC.PyObject ) 
    sym_str = PC.pyisinstance( po, sym_str_py_type ) ? po.s : po;
    return Symbol(PC.convert(AbstractString, sym_str))
end
PC.pytype_mapping(sym_str_py_type, Symbol);
nothing

This basically follows the proposal in the corresponding PyCall issue (https://github.com/JuliaPy/PyCall.jl/issues/11) by defining a python class to wrap around strings that are meant to be Julia Symbols. I can now do something like Main.MyJuliaConstructor( sym_field = Main.Symbol("sym_val") ). Within Python I can get the string value from a SymStr from the field s or by wrapping it in str().

I don't know whether to open a pull request over at PyCall. Are they still working on an alternative conversion routine?

Edit: Removed single quotes within PC.py"SymStr($(string(s)))"o

manuelbb-upb avatar Feb 11 '21 08:02 manuelbb-upb

I came across the same problem when writing a python wrapper for my Julia project. In the project, I'm using several “configuration structs” written with Parameters.jl so that the constructors are quite flexible. These struct have many fields, some of which require Symbol values. Wrapping them in a PyCall.pyfunction or anonymous functions would have resulted in a loss flexibility and would have made matters somewhat complicated.

Instead, I Main.include the following script right after initializing Julia in Python:

PC = PyCall;

PC.py"""
class SymStr():
    def __init__(self, *args, **kwargs):
        self.s = str(*args, **kwargs)
    def __str__(self):
        return self.s.__str__()
    def __repr__(self):
        return f'SymStr("{self.__str__()}")'
"""

sym_str_py_type = PC.py"SymStr";

PC.PyObject( s :: Symbol ) = PC.py"SymStr($(string(s)))"o
function PC.convert( ::Type{Symbol}, po :: PC.PyObject ) 
    sym_str = PC.pyisinstance( po, sym_str_py_type ) ? po.s : po;
    return Symbol(PC.convert(AbstractString, sym_str))
end
PC.pytype_mapping(sym_str_py_type, Symbol);
nothing

This basically follows the proposal in the corresponding PyCall issue (JuliaPy/PyCall.jl#11) by defining a python class to wrap around strings that are meant to be Julia Symbols. I can now do something like Main.MyJuliaConstructor( sym_field = Main.Symbol("sym_val") ). Within Python I can get the string value from a SymStr from the field s or by wrapping it in str().

I don't know whether to open a pull request over at PyCall. Are they still working on an alternative conversion routine?

Edit: Removed single quotes within PC.py"SymStr($(string(s)))"o

Is there a way to pass a constructor that has symbols into another function? For example, a = Main.MyJuliaConstructor( sym_field1 = Main.Symbol("sym_val1"), sym_field2 = Main.Symbol("sym_val2" ) and then do 'Main.MyFucntion(a)'?

yonachache avatar Sep 03 '21 06:09 yonachache

I would have supposed that it should just work. In fact, I guess I am doing something similar in my project (outdated atm): Here I initilize a Julia AlgoConfig with kwargs and reference it in the Python object cfg as self.jlObj. In this line I then call the Julia function optimize and pass the AlgoConfig via cfg.jlObj.

manuelbb-upb avatar Sep 03 '21 09:09 manuelbb-upb

@jdupl123 Thank you for the solution. I think using the Main.eval is the best way to go.

sibyjackgrove avatar Dec 08 '21 19:12 sibyjackgrove