• Stars
    star
    1,015
  • Rank 43,543 (Top 0.9 %)
  • Language
    C
  • License
    MIT License
  • Created over 7 years ago
  • Updated 7 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Calling Python functions from the Ruby language
pycall.rb logo

Build Status Build status

This library provides the features to directly call and partially interoperate with Python from the Ruby language. You can import arbitrary Python modules into Ruby modules, call Python functions with automatic type conversion from Ruby to Python.

Supported Ruby versions

pycall.rb supports Ruby version 2.4 or higher.

Supported Python versions

pycall.rb supports Python version 3.7 or higher.

Note for pyenv users

pycall.rb requires Python's shared library (e.g. libpython3.7m.so). pyenv does not build the shared library in default, so you need to specify --enable-shared option at the installation like below:

$ env PYTHON_CONFIGURE_OPTS='--enable-shared' pyenv install 3.7.2

Installation

Add this line to your application's Gemfile:

gem 'pycall'

And then execute:

$ bundle

Or install it yourself as:

$ gem install --pre pycall

Usage

Here is a simple example to call Python's math.sin function and compare it to the Math.sin in Ruby:

require 'pycall'
math = PyCall.import_module("math")
math.sin(math.pi / 4) - Math.sin(Math::PI / 4)   # => 0.0

Type conversions from Ruby to Python are automatically performed for numeric, boolean, string, arrays, and hashes.

Calling a constructor

In Python, we call the constructor of a class by classname(x, y, z) syntax. Pycall.rb maps this syntax to classname.new(x, y, z).

Calling a callable object

In Python, we can call the callable object by obj(x, y, z) syntax. PyCall.rb maps this syntax to obj.(x, y, z).

Passing keyword arguments

In Python, we can pass keyword arguments by func(x=1, y=2, z=3) syntax. In pycallrb, we should rewrite x=1 to x: 1.

The callable attribute of an object

Pycall.rb maps the callable attribute of an object to the instance method of the corresponding wrapper object. So, we can write a Python expression obj.meth(x, y, z=1) as obj.meth(x, y, z: 1) in Ruby. This mapping allows us to call these attributes naturally as Ruby's manner.

But, unfortunately, this mapping prohibits us to get the callable attributes. We need to write PyCall.getattr(obj, :meth) in Ruby to get obj.meth object while we can write obj.meth in Python.

Specifying the Python version

If you want to use a specific version of Python instead of the default, you can change the Python version by setting the PYTHON environment variable to the path of the python executable.

When PYTHON is not specified, pycall.rb tries to use python3 first, and then tries to use python.

Releasing the RubyVM GVL during Python function calls

You may want to release the RubyVM GVL when you call a Python function that takes very long runtime. PyCall provides PyCall.without_gvl method for such purpose. When PyCall performs python function call, PyCall checks the current context, and then it releases the RubyVM GVL when the current context is in a PyCall.without_gvl's block.

PyCall.without_gvl do
  # In this block, all Python function calls are performed without
  # the GVL acquisition.
  pyobj.long_running_function()
end

# Outside of PyCall.without_gvl block,
# all Python function calls are performed with the GVL acquisition.
pyobj.long_running_function()

Debugging python finder

When you encounter PyCall::PythonNotFound error, you can investigate PyCall's python finder by setting PYCALL_DEBUG_FIND_LIBPYTHON environment variable to 1. You can see the log like below:

$ PYCALL_DEBUG_FIND_LIBPYTHON=1 ruby -rpycall -ePyCall.builtins
DEBUG(find_libpython) find_libpython(nil)
DEBUG(find_libpython) investigate_python_config("python3")
DEBUG(find_libpython) libs: ["Python.framework/Versions/3.7/Python", "Python", "libpython3.7m", "libpython3.7", "libpython"]
DEBUG(find_libpython) libpaths: ["/opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib", "/opt/brew/opt/python/lib", "/opt/brew/opt/python/Frameworks", "/opt/brew/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7", "/opt/brew/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7/lib"]
DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/Python.framework/Versions/3.7/Python
DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/Python.framework/Versions/3.7/Python.dylib
DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/darwin/Python.framework/Versions/3.7/Python
DEBUG(find_libpython) Unable to find /opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/lib/darwin/Python.framework/Versions/3.7/Python.dylib
DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/Python.framework/Versions/3.7/Python
DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/Python.framework/Versions/3.7/Python.dylib
DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/darwin/Python.framework/Versions/3.7/Python
DEBUG(find_libpython) Unable to find /opt/brew/opt/python/lib/darwin/Python.framework/Versions/3.7/Python.dylib
DEBUG(find_libpython) dlopen("/opt/brew/opt/python/Frameworks/Python.framework/Versions/3.7/Python") = #<Fiddle::Handle:0x00007fc012048650>

Special notes for specific libraries

matplotlib

Use mrkn/matplotlib.rb instead of just importing it by PyCall.import_module("matplotlib").

numpy

Use mrkn/numpy.rb instead of just importing it by PyCall.import_module("numpy").

pandas

Use mrkn/pandas.rb instead of just importing it by PyCall.import_module("pandas").

PyCall object system

PyCall wraps pointers of Python objects in PyCall::PyPtr objects. PyCall::PyPtr class has two subclasses, PyCall::PyTypePtr and PyCall::PyRubyPtr. PyCall::PyTypePtr is specialized for type objects, and PyCall::PyRubyPtr is for the objects that wraps pointers of Ruby objects.

These PyCall::PyPtr objects are used mainly in PyCall infrastructure. Instead, we usually treats the instances of Object, Class, Module, or other classes that are extended by PyCall::PyObjectWrapper module.

PyCall::PyObjectWrapper is a mix-in module for objects that wraps Python objects. A wrapper object should have PyCall::PyPtr object in its instance variable @__pyptr__. PyCall::PyObjectWrapper assumes the existance of @__pyptr__, and provides general translation mechanisms between Ruby object system and Python object system. For example, PyCall::PyObjectWrapper translates Ruby's coerce system into Python's swapped operation protocol.

Deploying on Heroku

Heroku's default version of Python is not compiled with the --enabled-shared option and can't be accessed by PyCall.

There are many ways to make our heroku use Python that is compiled with the --enabled-shared option:

  • use Heroku's official Python buildpacks post_compile hooks to recompile the python if the --enabled-shared option is not enabled. example script of post_compile in ruby on rails app bin/post_compile.

    set -e
    buildpack_url=https://github.com/heroku/heroku-buildpack-python
    buildpack_vsn=v197 # adjust version accordingly https://github.com/heroku/heroku-buildpack-python/tags
    
    # rebuild python if it's missing enable-shared
    if  ! python3 -msysconfig | grep enable-shared \
        > /dev/null; then
      PYTHON_VERSION="$(< runtime.txt)"
      git clone -b "$buildpack_vsn" "$buildpack_url" _buildpack
      export WORKSPACE_DIR="$PWD/_buildpack/builds"
      rm -f .heroku/python/bin/python   # prevent failing ln after build
      sed -i 's!figure --pre!figure --enable-shared --pre!' \
        "$WORKSPACE_DIR"/runtimes/python3
      "$WORKSPACE_DIR/runtimes/$PYTHON_VERSION" /app/.heroku/python/
      rm -fr _buildpack
    fi
    
  • use your own precompiled python with --enabled-shared options then fork the official heroku python buildspacks and change the BUILDPACK_S3_BASE_URL with your own uploaded precompiled python in Amazon's S3.

  • use 3rd party buildpacks from the markets that have python with --enabled-shared option.

The buildpack will expect to find both a runtime.txt and a requirements.txt file in the root of your project. You will need to add these to specify the version of Python and any packages to be installed via pip, e.g to use version Python 3.8.1 and version 2.5 of the 'networkx' package:

$ echo "python-3.8.1" >> runtime.txt
$ echo "networkx==2.5" >> requirements.txt

Commit these two files into project's repository. You'll use these to manage your Python environment in much the same way you use the Gemfile to manage Ruby.

Heroku normally detects which buildpacks to use, but you will want to override this behavior. It's probably best to clear out existing buildpacks and specify exactly which buildpacks from scratch.

First, take stock of your existing buildpacks:

$ heroku buildpack [-a YOUR_APP_NAME]

For a Ruby/Rails application this will typically report the stock heroku/ruby buildpack, or possibly both heroku/ruby and heroku/nodejs.

Clear the list and progressively add back your buildpacks, starting with the Python community-developed buildpack. For example, if ruby and nodejs buildpacks were previously installed, and chosing the 'ReforgeHQ' buildback, your setup process will be similar to this:

$ heroku buildpacks:clear
$ heroku buildpacks:add https://github.com/ReforgeHQ/heroku-buildpack-python -i 1
$ heroku buildpacks:add heroku/nodejs -i 2
# heroku buildpacks:add heroku/ruby -i 3

If you have multiple applications on Heroku you will need to append each of these with application's identifier (e.g. heroku buildpacks:clear -a YOUR_APP_NAME).

With each buildpack we are registering its index (the -i switch) in order to specify the order Heroku will load runtimes and execute bootstrapping code. It's important for the Python environment to be engaged first, as PyCall will need to be able to find it when Ruby-based processes start.

Once you have set up your buildpacks, and have commited both requirements.txt and runtime.txt files to git, deploy your Heroku application as your normally would. The Python bootstrapping process will appear in the log first, followed by the Ruby and so on. PyCall should now be able to successfully call Python functions from within the Heroku environment.

NB It is also possible to specify buildpacks within Docker images on Heroku. See Heroku's documentation on using Docker Images.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/mrkn/pycall.rb.

Acknowledgement

PyCall.jl is referred too many times to implement this library.

License

The gem is available as open source under the terms of the MIT License.

More Repositories

1

pandas.rb

Pandas wrapper for Ruby
Ruby
117
star
2

enumerable-statistics

Ruby
115
star
3

matplotlib.rb

matplotlib wrapper for Ruby
Ruby
87
star
4

mxnet.rb

MXNet binding for Ruby
Ruby
48
star
5

numpy.rb

Numpy wrapper for Ruby
Ruby
38
star
6

ruby-julia

Julia on Ruby
Julia
21
star
7

vim-cruby

CRuby syntax for Vim.
Vim Script
19
star
8

Rubyistokei-app

Rubyistokei on your Mac
Objective-C
17
star
9

mrkn256.vim

A color scheme for vim available on 88- and 256-color xterm.
Vim Script
16
star
10

tf-waifu2x

Python
14
star
11

webdb_deep_learning

WEB+DB PRESS Vol.89 特集2のサンプルコード群
Python
11
star
12

ar-with-arrow-poc

ActiveRecord with Arrow PoC
Ruby
10
star
13

SFMT

SIMD-oriented Fast Mersenne Twister
C
10
star
14

chainer-waifu2x

Yet another waifu2x implementation by using chainer
Python
9
star
15

IMF

🎨 Image Manipulation Framework for Ruby
Ruby
9
star
16

scikit-learn.rb

scikit-learn wrapper for Ruby
Ruby
8
star
17

ruby-measure

A library to handle measurement numbers for Ruby
Ruby
8
star
18

num_buffer

NumBuffer -- Numerical buffer
C
7
star
19

daru-td

Interactive data analysis with Daru and Treasure Data.
Ruby
6
star
20

kernel_let

Kernel#let for block-local vairables
Ruby
6
star
21

ruby-hijack

hijack method definition
C
5
star
22

chainer-srcnn

SRCNN implementation using Chainer
Python
5
star
23

bugs-viewer-rk2017

bugs.ruby-lang.org viewer for the demonstration in RubyKaigi 2017
Ruby
5
star
24

typical_colors

Extracting typical colors from images
Jupyter Notebook
4
star
25

iterm2-mirror

My forked version and the original mirror of iTerm2.
Objective-C
4
star
26

config

configuration files
Vim Script
4
star
27

pdfdiv

An utility for dividing each page in PDF files.
4
star
28

nadoka

nadoka
Ruby
3
star
29

ruby-odbc

Extension library to use ODBC data sources from Ruby.
C
3
star
30

chatbot

Ruby
3
star
31

active_support_alias_class_method

A supplementary library of activesupport to provide alias_class_method and alias_class_method_chain.
Ruby
3
star
32

aquaskk

Mirror of AquaSKK
C
3
star
33

ruby10

Ruby 1.0 for Mac OS X based on ftp://ftp.ruby-lang.org/pub/ruby/1.0/ruby-1.0-971225.tar.gz
C
3
star
34

mecaby

Mecaby is an Ruby extension library to use MeCab.
C
3
star
35

screen

GNU screen mirror and my modification
C
3
star
36

movabletype

Copy of the Movable Type Open Source
Perl
3
star
37

sugoi-haskell-learning

Haskell
3
star
38

dotfiles

dotfiles
Ruby
3
star
39

tddbc_sapporo_2011.09.24

Ruby
2
star
40

infinity

Infinite objects for any Comparable objects.
2
star
41

WipeYourMac

Wipe your mac.
Objective-C
2
star
42

mecab-ruby-generator

mecab-ruby.gem generator
Ruby
2
star
43

qwik-formatter

Text formatter extracted from qwik
Ruby
2
star
44

lua

lua mirror
C
2
star
45

rubydoc

bootstrap for git svn clone http://jp.rubyist.net/svn/rurema/doctree/trunk
PHP
2
star
46

graph_layout_viewer

Visualizing and exporting layouted graph structure
2
star
47

W2vUtils.jl

Word2vec utilities for Julia
Julia
2
star
48

ruby-gnome2-samples

Ruby-GNOME2 samples
2
star
49

som-practice

practice for self-organizing map
Objective-C
2
star
50

irc-say-bot

Say bot for IRC
2
star
51

manage_download_dir

download directory management scripts
Ruby
2
star
52

sandbox

mrkn's sandbox
TeX
2
star
53

ruby-css

CSS Parser Library for Ruby
2
star
54

numo-sparse

Numo::Sparse
Ruby
2
star
55

streamlit-julia-call

An extension for Streamlit that makes it available to call Julia from Streamlit applications
Python
2
star
56

photo_scan

Photo scanner application for windows image aquisition
C++
2
star
57

letter_stamp_mail_delivery

Mail delivery method to save delivered mails with filenames that allows us to easily recognize the location at which mails are delivered.
Ruby
2
star
58

boost-framework

Boost framework for Mac OS X and iPhone OS
2
star
59

image-file

A library for handing image files on Ruby.
C
1
star
60

redash_rails_demo

A demonstration project to integrate Rails app and re:dash
Ruby
1
star
61

diary

mrkn diary
Ruby
1
star
62

ruby-mnist

Ruby
1
star
63

racuda

Ruby Adapted CUDA
Ruby
1
star
64

googledrive_sample

Samples of GoogleDrive
Ruby
1
star
65

comprehension

You can write `->(i, j) { i*j }.comprehension(1..9, 1..9)`
Ruby
1
star
66

talk-rubykaigi-takeout-2021

Talk material at RubyKaigi Takeout 2021
Jupyter Notebook
1
star
67

zoom-mic-control-from-alfred

You can control Zoom's mic mute/unmute state from Alfred
1
star