Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 9 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Here is a simple example to call Python's `math.sin` function and
compare it to the built-in Julia `sin`:

using PyCall
@pyimport math
math = pyimport("math")
math.sin(math.pi / 4) - sin(pi / 4) # returns 0.0

Type conversions are automatically performed for numeric, boolean,
Expand All @@ -104,12 +104,6 @@ arrays/lists, and dictionaries of these types. (Python functions can
be converted/passed to Julia functions and vice versa!) Other types
are supported via the generic PyObject type, below.

Python submodules must be imported by a separate `@pyimport` call, and
in this case you must supply an identifier to to use in Julia. For example

@pyimport numpy.random as nr
nr.rand(3,4)

Multidimensional arrays exploit the NumPy array interface for
conversions between Python and Julia. By default, they are passed
from Julia to Python without making a copy, but from Python to Julia a
Expand All @@ -120,7 +114,7 @@ Keyword arguments can also be passed. For example, matplotlib's
[pyplot](http://matplotlib.org/) uses keyword arguments to specify plot
options, and this functionality is accessed from Julia by:

@pyimport matplotlib.pyplot as plt
plt = pyimport("matplotlib.pyplot")
x = linspace(0,2*pi,1000); y = sin(3*x + 4*cos(2*x));
plt.plot(x, y, color="red", linewidth=2.0, linestyle="--")
plt.show()
Expand All @@ -133,7 +127,7 @@ Arbitrary Julia functions can be passed to Python routines taking
function arguments. For example, to find the root of cos(x) - x,
we could call the Newton solver in scipy.optimize via:

@pyimport scipy.optimize as so
so = pyimport("scipy.optimize")
so.newton(x -> cos(x) - x, 1)

A macro exists for mimicking Python's "with statement". For example:
Expand All @@ -147,23 +141,6 @@ conversion, use `f::PyObject`). Similarly, if the context manager returns a type
which is automatically converted to a Julia type, you will have override this
via `@pywith EXPR::PyObject ...`.


**Important:** The biggest difference from Python is that object attributes/members are
accessed with `o[:attribute]` rather than `o.attribute`, so that `o.method(...)` in
Python is replaced by `o[:method](...)` in Julia. Also, you use
`get(o, key)` rather than `o[key]`. (However, you can access integer
indices via `o[i]` as in Python, albeit with 1-based Julian indices rather
than 0-based Python indices.) (This will be changed to `o.method` once Julia
0.6 support is dropped, now that Julia supports `.` overloading.) See also the section on
`PyObject` below, as well as the `pywrap` function to create anonymous
modules that simulate `.` access (this is what `@pyimport` does). For
example, using [Biopython](http://biopython.org/wiki/Seq) we can do:

@pyimport Bio.Seq as s
@pyimport Bio.Alphabet as a
my_dna = s.Seq("AGTACACTGGT", a.generic_dna)
my_dna.find("ACT")

## Troubleshooting

Here are solutions to some common problems:
Expand All @@ -172,8 +149,8 @@ Here are solutions to some common problems:

## Python object interfaces

The `@pyimport` macro is built on top of several routines for
manipulating Python objects in Julia, via a type `PyObject` described
PyCall provides many routines for
manipulating Python objects in Julia via a type `PyObject` described
below. These can be used to have greater control over the types and
data passed between Julia and Python, as well as to access additional
Python functionality (especially in conjunction with the low-level interfaces
Expand Down Expand Up @@ -311,7 +288,7 @@ and the fact that the Julia JIT compiler can no longer infer the type).

### Calling Python

In most cases, the `@pyimport` macro automatically makes the
In most cases, PyCall automatically makes the
appropriate type conversions to Julia types based on runtime
inspection of the Python objects. However greater control over these
type conversions (e.g. to use a no-copy `PyArray` for a Python
Expand All @@ -331,13 +308,6 @@ and also by providing more type information to the Julia compiler.
`@pycall function(args...)::returntype` into
`pycall(function,returntype,args...)`.

* `pyimport(s)`: Import the Python module `s` (a string or symbol) and
return a pointer to it (a `PyObject`). Functions or other symbols
in the module may then be looked up by `s.name` where `name` is a
string (for the raw `PyObject`) or symbol (for automatic
type-conversion). Unlike the `@pyimport` macro, this does not
define a Julia module.

* `py"..."` evaluates `"..."` as a Python string, equivalent to
Python's [`eval`](https://docs.python.org/2/library/functions.html#eval) function, and returns the result
converted to `PyAny`. Alternatively, `py"..."o` returns the raw `PyObject`
Expand All @@ -363,18 +333,6 @@ and also by providing more type information to the Julia compiler.
symbol it returns the builtin converted to `PyAny`. (You can also use `py"s"`
to look up builtins or other Python globas.)

* `pywrap(o::PyObject)` returns a wrapper `w` that is an anonymous
module which provides (read) access to converted versions of `o`'s
members as `w.member`. (For example, `@pyimport module as name` is
equivalent to `name = pywrap(pyimport("module"))`.) If the Python
module contains identifiers that are reserved words in Julia
(e.g. `function`), they cannot be accessed as `w.member`; one must
instead use `w.pymember(:member)` (for the `PyAny` conversion) or
`w.pymember("member")` (for the raw `PyObject`). `pywrap` is rather
inefficient since it converts *every* member of `o` at once; you
are generally encouraged to simply access members via `o.member`
rather than using `pywrap`.

Occasionally, you may need to pass a keyword argument to Python that
is a [reserved word](https://en.wikipedia.org/wiki/Reserved_word) in Julia.
For example, calling `f(x, function=g)` will fail because `function` is
Expand Down Expand Up @@ -403,7 +361,7 @@ documentation `?pyfunction` and `?pyfunctionret` for more information.
`@pydef` creates a Python class whose methods are implemented in Julia.
For instance,

@pyimport numpy.polynomial as P
P = pyimport("numpy.polynomial")
@pydef mutable struct Doubler <: P.Polynomial
function __init__(self, x=10)
self.x = x
Expand Down Expand Up @@ -441,7 +399,7 @@ The method arguments and return values are automatically converted between Julia
Here's another example using [Tkinter](https://wiki.python.org/moin/TkInter):

using PyCall
@pyimport Tkinter as tk
tk = pyimport("Tkinter")

@pydef mutable struct SampleApp <: tk.Tk
__init__(self, args...; kwargs...) = begin
Expand Down Expand Up @@ -579,14 +537,12 @@ end

end
```
Then you can access the `scipy.optimize` functions as `scipy_opt[:newton]`
Then you can access the `scipy.optimize` functions as `scipy_opt.newton`
and so on.

Here, instead of `pyimport`, we have used the function `pyimport_conda`. The second argument is the name of the [Anaconda package](https://docs.continuum.io/anaconda/pkg-docs) that provides this module. This way, if importing `scipy.optimize` fails because the user hasn't installed `scipy`, it will either (a) automatically install `scipy` and retry the `pyimport` if PyCall is configured to use the [Conda](https://github.com/Luthaf/Conda.jl) Python install (or
any other Anaconda-based Python distro for which the user has installation privileges), or (b) throw an error explaining that `scipy` needs to be installed, and explain how to configure PyCall to use Conda so that it can be installed automatically. More generally, you can call `pyimport(module, package, channel)` to specify an optional Anaconda "channel" for installing non-standard Anaconda packages.

(Note that you cannot use `@pyimport` safely with precompilation, because that declares a global constant that internally has a pointer to the module. You can use `pywrap(pyimport(...))` in your `__init__` function to a assign a global variable using the `.` notation like `@pyimport`, however, albeit without the type stability of the global `const` as above.)

## Author

This package was written by [Steven G. Johnson](http://math.mit.edu/~stevenj/).
42 changes: 26 additions & 16 deletions src/PyCall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ PyPtr(o::PyObject) = getfield(o, :o)
"""
≛(x, y)

`PyPtr` based comparison of `x` and `y`, which can be of type `PyObject` or `PyPtr`.
`PyPtr` based comparison of `x` and `y`, which can be of type `PyObject` or `PyPtr`.
"""
≛(o1::Union{PyObject,PyPtr}, o2::Union{PyObject,PyPtr}) = PyPtr(o1) == PyPtr(o2)

Expand Down Expand Up @@ -303,7 +303,7 @@ end

getproperty(o::PyObject, s::Symbol) = convert(PyAny, getproperty(o, string(s)))

propertynames(o::PyObject) = map(x->Symbol(first(x)),
propertynames(o::PyObject) = map(x->Symbol(first(x)),
pycall(inspect."getmembers", PyObject, o))

# avoiding method ambiguity
Expand Down Expand Up @@ -361,6 +361,22 @@ keys(o::PyObject) = Symbol[m[1] for m in pycall(inspect."getmembers",
# we skip wrapping Julia reserved words (which cannot be type members)
const reserved = Set{String}(["while", "if", "for", "try", "return", "break", "continue", "function", "macro", "quote", "let", "local", "global", "const", "abstract", "typealias", "type", "bitstype", "immutable", "ccall", "do", "module", "baremodule", "using", "import", "export", "importall", "pymember", "false", "true", "Tuple"])

function _pywrap(o::PyObject, mname::Symbol)
members = convert(Vector{Tuple{AbstractString,PyObject}},
pycall(inspect."getmembers", PyObject, o))
filter!(m -> !(m[1] in reserved), members)
m = Module(mname, false)
consts = [Expr(:const, Expr(:(=), Symbol(x[1]), convert(PyAny, x[2]))) for x in members]
exports = try
convert(Vector{Symbol}, o."__all__")
catch
[Symbol(x[1]) for x in filter(x -> x[1][1] != '_', members)]
end
Core.eval(m, Expr(:toplevel, consts..., :(pymember(s) = $(getindex)($(o), s)),
Expr(:export, exports...)))
m
end

"""
pywrap(o::PyObject)

Expand All @@ -371,19 +387,13 @@ For example, `@pyimport module as name` is equivalent to `const name = pywrap(py
If the Python module contains identifiers that are reserved words in Julia (e.g. function), they cannot be accessed as `w.member`; one must instead use `w.pymember(:member)` (for the PyAny conversion) or w.pymember("member") (for the raw PyObject).
"""
function pywrap(o::PyObject, mname::Symbol=:__anon__)
members = convert(Vector{Tuple{AbstractString,PyObject}},
pycall(inspect."getmembers", PyObject, o))
filter!(m -> !(m[1] in reserved), members)
m = Module(mname, false)
consts = [Expr(:const, Expr(:(=), Symbol(x[1]), convert(PyAny, x[2]))) for x in members]
exports = try
convert(Vector{Symbol}, o."__all__")
catch
[Symbol(x[1]) for x in filter(x -> x[1][1] != '_', members)]
end
Core.eval(m, Expr(:toplevel, consts..., :(pymember(s) = $(getindex)($(o), s)),
Expr(:export, exports...)))
m
Base.depwarn("`pywrap(o)`` is deprecated in favor of direct property access `o.foo`.", :pywrap)
return _pywrap(o, mname)
end

function _pywrap_pyimport(o::PyObject, mname::Symbol=:__anon__)
Base.depwarn("`@pyimport foo` is deprecated in favor of `foo = pyimport(\"foo\")`.", :_pywrap_pyimport)
return _pywrap(o, mname)
end

#########################################################################
Expand Down Expand Up @@ -563,7 +573,7 @@ macro pyimport(name, optional_varname...)
quoteName = Expr(:quote, Name)
quote
if !isdefined($__module__, $quoteName)
const $(esc(Name)) = pywrap(pyimport($mname))
const $(esc(Name)) = _pywrap_pyimport(pyimport($mname))
elseif !isa($(esc(Name)), Module)
error("@pyimport: ", $(Expr(:quote, Name)), " already defined")
end
Expand Down
4 changes: 2 additions & 2 deletions src/pyclass.jl
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ function parse_pydef(expr)
if isfunction(line)
def_dict = splitdef(line)
py_f = def_dict[:name]
# The dictionary of the new Julia-side definition.
# The dictionary of the new Julia-side definition.
jl_def_dict = copy(def_dict)
if isa(py_f, Symbol)
# Method definition
Expand Down Expand Up @@ -133,7 +133,7 @@ end
`@pydef` creates a Python class whose methods are implemented in Julia.
For instance,

@pyimport numpy.polynomial as P
P = pyimport("numpy.polynomial")
@pydef type Doubler <: P.Polynomial
__init__(self, x=10) = (self.x = x)
my_method(self, arg1::Number) = arg1 + 20
Expand Down
6 changes: 1 addition & 5 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ roundtrip(x) = roundtrip(PyAny, x)
roundtripeq(T, x) = roundtrip(T, x) == x
roundtripeq(x) = roundtrip(x) == x

@pyimport math
const math = pyimport("math")

struct TestConstruct
x
Expand Down Expand Up @@ -128,10 +128,6 @@ const PyInt = pyversion < v"3" ? Int : Clonglong

@test collect(PyObject([1,"hello",5])) == [1,"hello",5]

@test try @eval (@pyimport os.path) catch ex
isa((ex::LoadError).error, ArgumentError)
end

@test PyObject("hello") == PyObject("hello")
@test PyObject("hello") != PyObject("hellö")
@test PyObject(hash) == PyObject(hash)
Expand Down