pycall 0.1.0.alpha.20170711 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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