pycall 0.1.0.alpha.20170711 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +13 -1
  4. data/CHANGES.md +35 -0
  5. data/Gemfile +0 -5
  6. data/README.md +41 -49
  7. data/Rakefile +22 -1
  8. data/appveyor.yml +9 -26
  9. data/examples/classifier_comparison.rb +52 -52
  10. data/examples/hist.rb +11 -11
  11. data/examples/notebooks/classifier_comparison.ipynb +51 -66
  12. data/examples/notebooks/forest_importances.ipynb +26 -49
  13. data/examples/notebooks/iruby_integration.ipynb +15 -36
  14. data/examples/notebooks/lorenz_attractor.ipynb +16 -47
  15. data/examples/notebooks/polar_axes.ipynb +29 -64
  16. data/examples/notebooks/sum_benchmarking.ipynb +109 -103
  17. data/examples/notebooks/xkcd_style.ipynb +12 -12
  18. data/examples/plot_forest_importances_faces.rb +8 -8
  19. data/examples/sum_benchmarking.rb +15 -19
  20. data/ext/pycall/extconf.rb +3 -0
  21. data/ext/pycall/gc.c +74 -0
  22. data/ext/pycall/libpython.c +217 -0
  23. data/ext/pycall/pycall.c +2184 -0
  24. data/ext/pycall/pycall_internal.h +700 -0
  25. data/ext/pycall/range.c +69 -0
  26. data/ext/pycall/ruby_wrapper.c +432 -0
  27. data/lib/pycall.rb +91 -19
  28. data/lib/pycall/dict.rb +28 -82
  29. data/lib/pycall/error.rb +10 -0
  30. data/lib/pycall/import.rb +45 -40
  31. data/lib/pycall/init.rb +44 -20
  32. data/lib/pycall/libpython.rb +6 -380
  33. data/lib/pycall/libpython/finder.rb +170 -0
  34. data/lib/pycall/list.rb +21 -51
  35. data/lib/pycall/pretty_print.rb +9 -0
  36. data/lib/pycall/pyerror.rb +14 -20
  37. data/lib/pycall/pyobject_wrapper.rb +157 -158
  38. data/lib/pycall/python/PyCall/__init__.py +1 -0
  39. data/lib/pycall/python/PyCall/six.py +23 -0
  40. data/lib/pycall/pytypeobject_wrapper.rb +79 -0
  41. data/lib/pycall/slice.rb +3 -22
  42. data/lib/pycall/tuple.rb +1 -7
  43. data/lib/pycall/version.rb +1 -1
  44. data/lib/pycall/wrapper_object_cache.rb +61 -0
  45. data/pycall.gemspec +4 -2
  46. data/tasks/pycall.rake +7 -0
  47. metadata +65 -27
  48. data/lib/pycall/eval.rb +0 -57
  49. data/lib/pycall/exception.rb +0 -13
  50. data/lib/pycall/pyobject.rb +0 -58
  51. data/lib/pycall/ruby_wrapper.rb +0 -137
  52. data/lib/pycall/type_object.rb +0 -11
  53. data/lib/pycall/types.rb +0 -19
  54. data/lib/pycall/utils.rb +0 -106
data/lib/pycall.rb CHANGED
@@ -1,19 +1,91 @@
1
- require "pycall/version"
2
- require "pycall/libpython"
3
- require "pycall/gc_guard"
4
- require "pycall/exception"
5
- require "pycall/pyobject_wrapper"
6
- require "pycall/pyobject"
7
- require "pycall/pyerror"
8
- require "pycall/eval"
9
- require "pycall/types"
10
- require "pycall/conversion"
11
- require "pycall/type_object"
12
- require "pycall/tuple"
13
- require "pycall/list"
14
- require "pycall/dict"
15
- require "pycall/set"
16
- require "pycall/slice"
17
- require "pycall/utils"
18
- require "pycall/ruby_wrapper"
19
- require "pycall/init"
1
+ module PyCall
2
+ require 'pycall/version'
3
+ require 'pycall/libpython'
4
+ require 'pycall/pyerror'
5
+ require 'pycall/pyobject_wrapper'
6
+ require 'pycall/pytypeobject_wrapper'
7
+ require 'pycall/init'
8
+
9
+ module_function
10
+
11
+ def builtins
12
+ @builtins ||= wrap_module(LibPython::API.builtins_module_ptr)
13
+ end
14
+
15
+ def callable?(obj)
16
+ case obj
17
+ when PyObjectWrapper
18
+ builtins.callable(obj.__pyptr__)
19
+ when PyPtr
20
+ builtins.callable(obj)
21
+ else
22
+ raise TypeError, "unexpected argument type #{obj.class} (expected PyCall::PyPtr or its wrapper)"
23
+ end
24
+ end
25
+
26
+ def dir(obj)
27
+ case obj
28
+ when PyObjectWrapper
29
+ builtins.dir(obj.__pyptr__)
30
+ when PyPtr
31
+ builtins.dir(obj)
32
+ else
33
+ raise TypeError, "unexpected argument type #{obj.class} (expected PyCall::PyPtr or its wrapper)"
34
+ end
35
+ end
36
+
37
+ def eval(expr, globals: nil, locals: nil)
38
+ globals ||= import_module(:__main__).__dict__
39
+ builtins.eval(expr, globals, locals)
40
+ end
41
+
42
+ def exec(code, globals: nil, locals: nil)
43
+ globals ||= import_module(:__main__).__dict__
44
+ if PYTHON_VERSION >= '3'
45
+ builtins.exec(code, globals, locals)
46
+ else
47
+ import_module('PyCall.six').exec_(code, globals, locals)
48
+ end
49
+ end
50
+
51
+ def import_module(name)
52
+ LibPython::Helpers.import_module(name)
53
+ end
54
+
55
+ def len(obj)
56
+ case obj
57
+ when PyObjectWrapper
58
+ builtins.len(obj.__pyptr__)
59
+ when PyPtr
60
+ builtins.len(obj)
61
+ else
62
+ raise TypeError, "unexpected argument type #{obj.class} (expected PyCall::PyPtr or its wrapper)"
63
+ end
64
+ end
65
+
66
+ def sys
67
+ @sys ||= import_module('sys')
68
+ end
69
+
70
+ def tuple(iterable=nil)
71
+ pyptr = if iterable
72
+ builtins.tuple.(iterable)
73
+ else
74
+ builtins.tuple.()
75
+ end
76
+ Tuple.wrap_pyptr(pyptr)
77
+ end
78
+
79
+ def with(ctx)
80
+ begin
81
+ yield ctx.__enter__
82
+ rescue PyError => err
83
+ raise err unless ctx.__exit__(err.type, err.value, err.traceback)
84
+ rescue Exception => err
85
+ # TODO: support telling what exception has been catched
86
+ raise err unless ctx.__exit__(err.class, err, err.backtrace_locations)
87
+ else
88
+ ctx.__exit__(nil, nil, nil)
89
+ end
90
+ end
91
+ end
data/lib/pycall/dict.rb CHANGED
@@ -1,102 +1,48 @@
1
1
  module PyCall
2
+ Dict = builtins.dict
2
3
  class Dict
3
- include PyObjectWrapper
4
+ register_python_type_mapping
4
5
 
5
- def self.new(init=nil)
6
- case init
7
- when LibPython::PyObjectStruct
8
- super
9
- when nil
10
- new(LibPython.PyDict_New())
11
- when Hash
12
- new.tap do |dict|
13
- init.each do |key, value|
14
- dict[key] = value
15
- end
16
- end
17
- else
18
- raise TypeError, "the argument must be a PyObject or a Hash"
19
- end
20
- end
21
-
22
- def [](key)
23
- key = key.to_s if key.is_a? Symbol
24
- key = key.__pyobj__ if key.respond_to?(:__pyobj__)
25
- value = if key.is_a? String
26
- LibPython.PyDict_GetItemString(__pyobj__, key).to_ruby
27
- else
28
- LibPython.PyDict_GetItem(__pyobj__, key).to_ruby
29
- end
30
- ensure
31
- case value
32
- when LibPython::PyObjectStruct
33
- PyCall.incref(value)
34
- when PyObjectWrapper
35
- PyCall.incref(value.__pyobj__)
36
- end
37
- end
38
-
39
- def []=(key, value)
40
- key = key.to_s if key.is_a? Symbol
41
- key = key.__pyobj__ if key.respond_to?(:__pyobj__)
42
- value = Conversions.from_ruby(value)
43
- value = value.__pyobj__ unless value.kind_of? LibPython::PyObjectStruct
44
- if key.is_a? String
45
- LibPython.PyDict_SetItemString(__pyobj__, key, value)
46
- else
47
- LibPython.PyDict_SetItem(__pyobj__, key, value)
48
- end
49
- value
50
- end
51
-
52
- def delete(key)
53
- key = key.to_s if key.is_a? Symbol
54
- key = key.__pyobj__ if key.respond_to?(:__pyobj__)
55
- if key.is_a? String
56
- value = LibPython.PyDict_GetItemString(__pyobj__, key).to_ruby
57
- LibPython.PyDict_DelItemString(__pyobj__, key)
58
- else
59
- value = LibPython.PyDict_GetItem(__pyobj__, key).to_ruby
60
- LibPython.PyDict_DelItem(__pyobj__, key)
61
- end
62
- value
63
- end
64
-
65
- def size
66
- LibPython.PyDict_Size(__pyobj__)
67
- end
6
+ include Enumerable
68
7
 
69
- alias length size
70
-
71
- def keys
72
- LibPython.PyDict_Keys(__pyobj__).to_ruby
8
+ def self.new(h)
9
+ super(h, {})
73
10
  end
74
11
 
75
- def values
76
- LibPython.PyDict_Values(__pyobj__).to_ruby
12
+ def length
13
+ PyCall.len(self)
77
14
  end
78
15
 
79
16
  def has_key?(key)
80
- key = Conversions.from_ruby(key)
81
- value = LibPython.PyDict_Contains(__pyobj__, key)
82
- raise PyError.fetch if value == -1
83
- 1 == value
17
+ LibPython::Helpers.dict_contains(__pyptr__, key)
84
18
  end
85
19
 
86
- def default=(val)
87
- # TODO: PYDict_SetDefault
20
+ alias include? has_key?
21
+ alias key? has_key?
22
+ alias member? has_key?
23
+
24
+ def [](key)
25
+ super
26
+ rescue PyError
27
+ nil
88
28
  end
89
29
 
90
- def dup
91
- # TODO: PyDict_Copy
30
+ def delete(key)
31
+ v = self[key]
32
+ LibPython::Helpers.delitem(__pyptr__, key)
33
+ v
92
34
  end
93
35
 
94
- def to_a
95
- LibPython.PyDict_Items(__pyobj__).to_ruby
36
+ def each
37
+ return enum_for unless block_given?
38
+ LibPython::Helpers.dict_each(__pyptr__, &proc)
39
+ self
96
40
  end
97
41
 
98
- def to_hash
99
- # TODO: PyDict_Next
42
+ def to_h
43
+ inject({}) do |h, (k, v)|
44
+ h.update(k => v)
45
+ end
100
46
  end
101
47
  end
102
48
  end
@@ -0,0 +1,10 @@
1
+ module PyCall
2
+ class Error < StandardError
3
+ end
4
+
5
+ class PythonNotFound < Error
6
+ end
7
+
8
+ class LibPythonFunctionNotFound < Error
9
+ end
10
+ end
data/lib/pycall/import.rb CHANGED
@@ -2,57 +2,48 @@ require 'pycall'
2
2
 
3
3
  module PyCall
4
4
  module Import
5
- def self.main_object
6
- @main_object
5
+ class << self
6
+ attr_reader :main_object
7
7
  end
8
8
  end
9
9
  end
10
10
 
11
- main_object = self
12
- PyCall::Import.class_eval { @main_object = main_object }
11
+ PyCall::Import.instance_variable_set(:@main_object, self)
13
12
 
14
13
  module PyCall
15
14
  module Import
16
15
  def pyimport(mod_name, as: nil)
17
- case as
18
- when nil
19
- as = mod_name
20
- end
21
-
16
+ as = mod_name unless as
22
17
  check_valid_module_variable_name(mod_name, as)
23
-
24
18
  mod = PyCall.import_module(mod_name)
25
- raise PyError.fetch unless mod
26
-
27
19
  define_singleton_method(as) { mod }
28
20
  end
29
21
 
30
22
  # This function is implemented as a mimic of `import_from` function defined in `Python/ceval.c`.
31
23
  def pyfrom(mod_name, import: nil)
32
- raise ArgumentError, "missing identifiers to be imported" unless import
24
+ raise ArgumentError, "missing identifier(s) to be imported" unless import
33
25
 
34
26
  mod_name = mod_name.to_str if mod_name.respond_to? :to_str
35
27
  mod_name = mod_name.to_s if mod_name.is_a? Symbol
36
28
 
37
29
  import = Array(import)
38
- fromlist = LibPython.PyTuple_New(import.length)
39
- import.each_with_index do |import_name, i|
40
- name = case import_name
41
- when assoc_array_matcher
42
- import_name[0]
43
- when Symbol, String
44
- import_name
45
- end
46
- LibPython.PyTuple_SetItem(fromlist, i, Conversions.from_ruby(name))
30
+ fromlist = import.map.with_index do |import_name, i|
31
+ case import_name
32
+ when assoc_array_matcher
33
+ import_name[0]
34
+ when Symbol, String
35
+ import_name
36
+ else
37
+ raise ArgumentError, "wrong type of import name #{import_name.class} (expected String or Symbol)"
38
+ end
47
39
  end
40
+ from_list = PyCall.tuple(from_list)
48
41
 
49
- main_dict = PyCall::Eval.__send__ :main_dict
50
- globals = main_dict.__pyobj__ # FIXME: this should mimic to `import_name` function defined in `Python/ceval.c`.
51
- locals = main_dict.__pyobj__ # FIXME: this should mimic to `import_name` function defined in `Python/ceval.c`.
42
+ main_dict_ptr = PyCall.import_module('__main__').__dict__.__pyptr__
43
+ globals = main_dict_ptr # FIXME: this should mimic to `import_name` function defined in `Python/ceval.c`.
44
+ locals = main_dict_ptr # FIXME: this should mimic to `import_name` function defined in `Python/ceval.c`.
52
45
  level = 0 # TODO: support prefixed dots (#25)
53
- mod = LibPython.PyImport_ImportModuleLevel(mod_name, globals, locals, fromlist, level)
54
- raise PyError.fetch if mod.null?
55
- mod = mod.to_ruby
46
+ mod = LibPython::Helpers.import_module(mod_name, globals, locals, fromlist, level)
56
47
 
57
48
  import.each do |import_name|
58
49
  case import_name
@@ -62,18 +53,18 @@ module PyCall
62
53
  name, asname = import_name, import_name
63
54
  end
64
55
 
65
- if PyCall.hasattr?(mod, name)
66
- pyobj = PyCall.getattr(mod, name)
56
+ if PyCall::LibPython::Helpers.hasattr?(mod.__pyptr__, name)
57
+ pyobj = PyCall::LibPython::Helpers.getattr(mod.__pyptr__, name)
67
58
  define_name(asname, pyobj)
68
59
  next
69
60
  end
70
61
 
71
- if PyCall.hasattr?(mod, :__name__)
72
- pkgname = PyCall.getattr(mod, :__name__)
62
+ if mod.respond_to? :__name__
63
+ pkgname = mod.__name__
73
64
  fullname = "#{pkgname}.#{name}"
74
- module_dict = LibPython.PyImport_GetModuleDict()
75
- if PyCall.getattr(module_dict, fullname)
76
- pyobj = PyCall.getattr(module_dict, fullname)
65
+ sys_modules = PyCall.import_module('sys').modules
66
+ if sys_modules.has_key?(fullname)
67
+ pyobj = module_dict[fullname]
77
68
  define_name(asname, pyobj)
78
69
  next
79
70
  end
@@ -86,12 +77,18 @@ module PyCall
86
77
  private
87
78
 
88
79
  def define_name(name, pyobj)
89
- if constant_name?(name)
90
- context = self
91
- context = (self == PyCall::Import.main_object) ? Object : self
92
- context.module_eval { const_set(name, pyobj) }
80
+ if callable?(pyobj) && !type_object?(pyobj)
81
+ define_singleton_method(name) do |*args|
82
+ LibPython::Helpers.call_object(pyobj.__pyptr__, *args)
83
+ end
93
84
  else
94
- define_singleton_method(name) { pyobj }
85
+ if constant_name?(name)
86
+ context = self
87
+ context = (self == PyCall::Import.main_object) ? Object : self
88
+ context.module_eval { const_set(name, pyobj) }
89
+ else
90
+ define_singleton_method(name) { pyobj }
91
+ end
95
92
  end
96
93
  end
97
94
 
@@ -111,5 +108,13 @@ module PyCall
111
108
  ary.is_a?(Array) && ary.length == 2
112
109
  end
113
110
  end
111
+
112
+ def callable?(pyobj)
113
+ LibPython::Helpers.callable?(pyobj.__pyptr__)
114
+ end
115
+
116
+ def type_object?(pyobj)
117
+ pyobj.__pyptr__.kind_of? PyTypePtr
118
+ end
114
119
  end
115
120
  end
data/lib/pycall/init.rb CHANGED
@@ -1,31 +1,55 @@
1
1
  module PyCall
2
- private_class_method def self.__initialize_pycall__
3
- initialized = (0 != PyCall::LibPython.Py_IsInitialized())
4
- return if initialized
2
+ def self.const_missing(name)
3
+ case name
4
+ when :PyPtr, :PyTypePtr, :PyObjectWrapper, :PYTHON_DESCRIPTION, :PYTHON_VERSION
5
+ PyCall.init
6
+ const_get(name)
7
+ else
8
+ super
9
+ end
10
+ end
5
11
 
6
- PyCall::LibPython.Py_InitializeEx(0)
12
+ module LibPython
13
+ def self.const_missing(name)
14
+ case name
15
+ when :API, :Conversion, :Helpers, :PYTHON_DESCRIPTION, :PYTHON_VERSION
16
+ PyCall.init
17
+ const_get(name)
18
+ else
19
+ super
20
+ end
21
+ end
22
+ end
7
23
 
8
- FFI::MemoryPointer.new(:pointer, 1) do |argv|
9
- argv.write_pointer(FFI::MemoryPointer.from_string(""))
10
- PyCall::LibPython.PySys_SetArgvEx(0, argv, 0)
24
+ def self.init(python = ENV['PYTHON'])
25
+ return false if LibPython.instance_variable_defined?(:@handle)
26
+ class << PyCall
27
+ remove_method :const_missing
28
+ end
29
+ class << PyCall::LibPython
30
+ remove_method :const_missing
11
31
  end
12
32
 
13
- @builtin = LibPython.PyImport_ImportModule(PYTHON_VERSION < '3.0.0' ? '__builtin__' : 'builtins').to_ruby
33
+ ENV['PYTHONPATH'] = [ File.expand_path('../python', __FILE__), ENV['PYTHONPATH'] ].compact.join(File::PATH_SEPARATOR)
14
34
 
15
- begin
16
- import_module('stackless')
17
- @has_stackless_extension = true
18
- rescue PyError
19
- @has_stackless_extension = false
35
+ LibPython.instance_variable_set(:@handle, LibPython::Finder.find_libpython(python))
36
+ class << LibPython
37
+ undef_method :handle
38
+ attr_reader :handle
20
39
  end
21
40
 
22
- __initialize_ruby_wrapper__
23
- end
41
+ begin
42
+ major, minor, _ = RUBY_VERSION.split('.')
43
+ require "#{major}.#{minor}/pycall.so"
44
+ rescue LoadError
45
+ require 'pycall.so'
46
+ end
24
47
 
25
- class << self
26
- attr_reader :builtin
27
- attr_reader :has_stackless_extension
48
+ require 'pycall/dict'
49
+ require 'pycall/list'
50
+ require 'pycall/slice'
51
+ const_set(:PYTHON_VERSION, LibPython::PYTHON_VERSION)
52
+ const_set(:PYTHON_DESCRIPTION, LibPython::PYTHON_DESCRIPTION)
53
+ true
28
54
  end
29
-
30
- __initialize_pycall__
31
55
  end