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/list.rb CHANGED
@@ -1,73 +1,43 @@
1
1
  module PyCall
2
+ List = builtins.list
2
3
  class List
3
- include PyObjectWrapper
4
+ register_python_type_mapping
5
+
4
6
  include Enumerable
5
7
 
6
- def self.new(init=nil)
7
- case init
8
- when LibPython::PyObjectStruct
9
- super
10
- when nil
11
- new(0)
12
- when Integer
13
- new(LibPython.PyList_New(init))
14
- when Array
15
- new.tap do |list|
16
- init.each do |item|
17
- list << item
18
- end
19
- end
20
- else
21
- new(obj.to_ary)
22
- end
8
+ def include?(item)
9
+ LibPython::Helpers.sequence_contains(__pyptr__, item)
23
10
  end
24
11
 
25
- def <<(value)
26
- value = Conversions.from_ruby(value)
27
- LibPython.PyList_Append(__pyobj__, value)
28
- self
12
+ def length
13
+ PyCall.len(self)
29
14
  end
30
15
 
31
- def size
32
- LibPython.PyList_Size(__pyobj__)
16
+ def each
17
+ return enum_for unless block_given?
18
+ LibPython::Helpers.sequence_each(__pyptr__, &proc)
19
+ self
33
20
  end
34
21
 
35
- alias length size
22
+ def <<(item)
23
+ append(item)
24
+ end
36
25
 
37
- def include?(value)
38
- value = Conversions.from_ruby(value)
39
- value = LibPython.PySequence_Contains(__pyobj__, value)
40
- raise PyError.fetch if value == -1
41
- 1 == value
26
+ def push(*items)
27
+ items.each {|i| append(i) }
42
28
  end
43
29
 
44
- def ==(other)
45
- case other
46
- when Array
47
- self.to_a == other
48
- else
49
- super
50
- end
30
+ def sort
31
+ dup.sort!
51
32
  end
52
33
 
53
- def each
54
- return enum_for unless block_given?
55
- i, n = 0, size
56
- while i < n
57
- yield self[i]
58
- i += 1
59
- end
34
+ def sort!
35
+ LibPython::Helpers.getattr(__pyptr__, :sort).__call__
60
36
  self
61
37
  end
62
38
 
63
39
  def to_a
64
- [].tap do |a|
65
- i, n = 0, size
66
- while i < n
67
- a << self[i]
68
- i += 1
69
- end
70
- end
40
+ Array.new(length) {|i| self[i] }
71
41
  end
72
42
 
73
43
  alias to_ary to_a
@@ -0,0 +1,9 @@
1
+ require 'pycall'
2
+ require 'pp'
3
+
4
+ PyCall.init
5
+ module PyCall
6
+ class PyPtr < BasicObject
7
+ include ::PP::ObjectMixin
8
+ end
9
+ end
@@ -1,36 +1,30 @@
1
- module PyCall
2
- class PyError < StandardError
3
- def self.fetch
4
- ptrs = FFI::MemoryPointer.new(:pointer, 3)
5
- ptype = ptrs + 0 * ptrs.type_size
6
- pvalue = ptrs + 1 * ptrs.type_size
7
- ptraceback = ptrs + 2 * ptrs.type_size
8
- LibPython.PyErr_Fetch(ptype, pvalue, ptraceback)
9
- LibPython.PyErr_NormalizeException(ptype, pvalue, ptraceback)
10
- type = Conversions.to_ruby(ptype.read(:pointer))
11
- value = Conversions.to_ruby(pvalue.read(:pointer))
12
- traceback = Conversions.to_ruby(ptraceback.read(:pointer))
13
- new(type, value, traceback)
14
- end
1
+ require 'pycall/error'
15
2
 
3
+ module PyCall
4
+ class PyError < Error
16
5
  def initialize(type, value, traceback)
17
6
  @type = type
18
7
  @value = value
19
8
  @traceback = traceback
20
- super("Error occurred in Python")
9
+ super("Exception occurred in Python")
21
10
  end
22
11
 
23
12
  attr_reader :type, :value, :traceback
24
13
 
25
14
  def to_s
26
15
  "#{type}: #{value}".tap do |msg|
27
- unless traceback.nil? || traceback.null?
28
- if (strs = PyCall.format_traceback(traceback))
29
- msg << "\n"
30
- strs.each {|s| msg << s }
31
- end
16
+ if (strs = format_traceback)
17
+ msg << "\n"
18
+ strs.each {|s| msg << s }
32
19
  end
33
20
  end
34
21
  end
22
+
23
+ private
24
+
25
+ def format_traceback
26
+ return nil if traceback.nil?
27
+ ::PyCall.import_module('traceback').format_tb(traceback)
28
+ end
35
29
  end
36
30
  end
@@ -1,213 +1,212 @@
1
- module PyCall
2
- HASH_SALT = "PyCall::PyObject".hash
3
-
4
- Py_LT = 0
5
- Py_LE = 1
6
- Py_EQ = 2
7
- Py_NE = 3
8
- Py_GT = 4
9
- Py_GE = 5
10
-
11
- RICH_COMPARISON_OPCODES = {
12
- :< => Py_LT,
13
- :<= => Py_LE,
14
- :== => Py_EQ,
15
- :!= => Py_NE,
16
- :> => Py_GT,
17
- :>= => Py_GE
18
- }.freeze
1
+ require 'pycall/wrapper_object_cache'
19
2
 
3
+ module PyCall
20
4
  module PyObjectWrapper
21
- module ClassMethods
22
- private
23
-
24
- def wrap_class(pyclass)
25
- pyclass__pyobj__ = pyclass.__pyobj__
26
- define_singleton_method(:__pyobj__) { pyclass__pyobj__ }
27
-
28
- PyCall.dir(__pyobj__).each do |name|
29
- obj = PyCall.getattr(__pyobj__, name)
30
- next unless obj.kind_of?(PyCall::PyObject) || obj.kind_of?(PyCall::PyObjectWrapper)
31
- next unless PyCall.callable?(obj)
5
+ attr_reader :__pyptr__
32
6
 
33
- define_method(name) do |*args, **kwargs|
34
- PyCall.getattr(__pyobj__, name).(*args, **kwargs)
35
- end
7
+ def self.extend_object(obj)
8
+ pyptr = obj.instance_variable_get(:@__pyptr__)
9
+ unless pyptr.kind_of? PyPtr
10
+ raise TypeError, "@__pyptr__ should have PyCall::PyPtr object"
11
+ end
12
+ super
13
+ end
14
+
15
+ OPERATOR_METHOD_NAMES = {
16
+ :+ => :__add__,
17
+ :- => :__sub__,
18
+ :* => :__mul__,
19
+ :/ => :__truediv__,
20
+ :% => :__mod__,
21
+ :** => :__pow__,
22
+ :<< => :__lshift__,
23
+ :>> => :__rshift__,
24
+ :& => :__and__,
25
+ :^ => :__xor__,
26
+ :| => :__or__
27
+ }.freeze
28
+
29
+ def method_missing(name, *args)
30
+ name_str = name.to_s if name.kind_of?(Symbol)
31
+ name_str.chop! if name_str.end_with?('=')
32
+ case name
33
+ when *OPERATOR_METHOD_NAMES.keys
34
+ op_name = OPERATOR_METHOD_NAMES[name]
35
+ if LibPython::Helpers.hasattr?(__pyptr__, op_name)
36
+ LibPython::Helpers.define_wrapper_method(self, op_name)
37
+ singleton_class.__send__(:alias_method, name, op_name)
38
+ return self.__send__(name, *args)
36
39
  end
37
-
38
- class << self
39
- def method_missing(name, *args, **kwargs)
40
- return super unless PyCall.hasattr?(__pyobj__, name)
41
- PyCall.getattr(__pyobj__, name)
42
- end
40
+ else
41
+ if LibPython::Helpers.hasattr?(__pyptr__, name_str)
42
+ LibPython::Helpers.define_wrapper_method(self, name)
43
+ return self.__send__(name, *args)
43
44
  end
44
-
45
- PyCall::Conversions.python_type_mapping(__pyobj__, self)
46
45
  end
46
+ super
47
47
  end
48
48
 
49
- def self.included(mod)
50
- mod.extend ClassMethods
49
+ def respond_to_missing?(name, include_private)
50
+ return true if LibPython::Helpers.hasattr?(__pyptr__, name)
51
+ super
51
52
  end
52
53
 
53
- def initialize(pyobj)
54
- pyobj = LibPython::PyObjectStruct.new(pyobj) if pyobj.kind_of? FFI::Pointer
55
- pyobj = pyobj.__pyobj__ unless pyobj.kind_of? LibPython::PyObjectStruct
56
- @__pyobj__ = pyobj
54
+ def kind_of?(cls)
55
+ case cls
56
+ when PyTypeObjectWrapper
57
+ __pyptr__.kind_of?(cls.__pyptr__)
58
+ else
59
+ super
60
+ end
57
61
  end
58
62
 
59
- attr_reader :__pyobj__
60
-
61
- def eql?(other)
62
- rich_compare(other, :==)
63
+ [:==, :!=, :<, :<=, :>, :>=].each do |op|
64
+ class_eval("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1)
65
+ begin;
66
+ def #{op}(other)
67
+ case other
68
+ when PyObjectWrapper
69
+ LibPython::Helpers.compare(:#{op}, __pyptr__, other.__pyptr__)
70
+ else
71
+ other = Conversion.from_ruby(other)
72
+ LibPython::Helpers.compare(:#{op}, __pyptr__, other)
73
+ end
74
+ end
75
+ end;
63
76
  end
64
77
 
65
- def hash
66
- hash_value = LibPython.PyObject_Hash(__pyobj__)
67
- return super if hash_value == -1
68
- hash_value
78
+ def [](*key)
79
+ LibPython::Helpers.getitem(__pyptr__, key)
69
80
  end
70
81
 
71
- def type
72
- LibPython.PyObject_Type(__pyobj__).to_ruby
82
+ def []=(*key, value)
83
+ LibPython::Helpers.setitem(__pyptr__, key, value)
73
84
  end
74
85
 
75
- def null?
76
- __pyobj__.null?
86
+ def call(*args)
87
+ LibPython::Helpers.call_object(__pyptr__, *args)
77
88
  end
78
89
 
79
- def to_ptr
80
- __pyobj__.to_ptr
81
- end
90
+ class SwappedOperationAdapter
91
+ def initialize(obj)
92
+ @obj = obj
93
+ end
82
94
 
83
- def py_none?
84
- to_ptr == PyCall.None.to_ptr
85
- end
95
+ attr_reader :obj
86
96
 
87
- def kind_of?(klass)
88
- case klass
89
- when PyObjectWrapper
90
- __pyobj__.kind_of? klass.__pyobj__
91
- when LibPython::PyObjectStruct
92
- __pyobj__.kind_of? klass
93
- else
94
- super
97
+ def +(other)
98
+ other.__radd__(self.obj)
95
99
  end
96
- end
97
100
 
98
- def rich_compare(other, op)
99
- opcode = RICH_COMPARISON_OPCODES[op]
100
- raise ArgumentError, "Unknown comparison op: #{op}" unless opcode
101
+ def -(other)
102
+ other.__rsub__(self.obj)
103
+ end
101
104
 
102
- other = Conversions.from_ruby(other)
103
- return other.null? if __pyobj__.null?
104
- return false if other.null?
105
+ def *(other)
106
+ other.__rmul__(self.obj)
107
+ end
105
108
 
106
- value = LibPython.PyObject_RichCompare(__pyobj__, other, opcode)
107
- raise "Unable to compare: #{self} #{op} #{other}" if value.null?
108
- value.to_ruby
109
- end
109
+ def /(other)
110
+ other.__rtruediv__(self.obj)
111
+ end
110
112
 
111
- RICH_COMPARISON_OPCODES.keys.each do |op|
112
- define_method(op) {|other| rich_compare(other, op) }
113
- end
113
+ def %(other)
114
+ other.__rmod__(self.obj)
115
+ end
114
116
 
115
- def [](*indices)
116
- if indices.length == 1
117
- indices = indices[0]
118
- else
119
- indices = PyCall.tuple(*indices)
117
+ def **(other)
118
+ other.__rpow__(self.obj)
120
119
  end
121
- PyCall.getitem(self, indices)
122
- end
123
120
 
124
- def []=(*indices_and_value)
125
- value = indices_and_value.pop
126
- indices = indices_and_value
127
- if indices.length == 1
128
- indices = indices[0]
129
- else
130
- indices = PyCall.tuple(*indices)
121
+ def <<(other)
122
+ other.__rlshift__(self.obj)
123
+ end
124
+
125
+ def >>(other)
126
+ other.__rrshift__(self.obj)
127
+ end
128
+
129
+ def &(other)
130
+ other.__rand__(self.obj)
131
+ end
132
+
133
+ def ^(other)
134
+ other.__rxor__(self.obj)
135
+ end
136
+
137
+ def |(other)
138
+ other.__ror__(self.obj)
131
139
  end
132
- PyCall.setitem(self, indices, value)
133
140
  end
134
141
 
135
- def +(other)
136
- other = Conversions.from_ruby(other)
137
- value = LibPython.PyNumber_Add(__pyobj__, other)
138
- return value.to_ruby unless value.null?
139
- raise PyError.fetch
142
+ def coerce(other)
143
+ [SwappedOperationAdapter.new(other), self]
140
144
  end
141
145
 
142
- def -(other)
143
- other = Conversions.from_ruby(other)
144
- value = LibPython.PyNumber_Subtract(__pyobj__, other)
145
- return value.to_ruby unless value.null?
146
- raise PyError.fetch
146
+ def dup
147
+ super.tap do |duped|
148
+ copied = PyCall.import_module('copy').copy(__pyptr__)
149
+ copied = copied.__pyptr__ if copied.kind_of? PyObjectWrapper
150
+ duped.instance_variable_set(:@__pyptr__, copied)
151
+ end
147
152
  end
148
153
 
149
- def *(other)
150
- other = Conversions.from_ruby(other)
151
- value = LibPython.PyNumber_Multiply(__pyobj__, other)
152
- return value.to_ruby unless value.null?
153
- raise PyError.fetch
154
+ def inspect
155
+ PyCall.builtins.repr(__pyptr__)
154
156
  end
155
157
 
156
- def /(other)
157
- other = Conversions.from_ruby(other)
158
- value = LibPython.PyNumber_TrueDivide(__pyobj__, other)
159
- return value.to_ruby unless value.null?
160
- raise PyError.fetch
158
+ def to_s
159
+ LibPython::Helpers.str(__pyptr__)
161
160
  end
162
161
 
163
- def **(other)
164
- other = Conversions.from_ruby(other)
165
- value = LibPython.PyNumber_Power(__pyobj__, other, PyCall.None)
166
- return value.to_ruby unless value.null?
167
- raise PyError.fetch
162
+ def to_i
163
+ LibPython::Helpers.call_object(PyCall::builtins.int.__pyptr__, __pyptr__)
168
164
  end
169
165
 
170
- def coerce(other)
171
- [PyObject.new(Conversions.from_ruby(other)), self]
166
+ def to_f
167
+ LibPython::Helpers.call_object(PyCall::builtins.float.__pyptr__, __pyptr__)
172
168
  end
169
+ end
170
+
171
+ module_function
173
172
 
174
- def call(*args, **kwargs)
175
- args = PyCall::Tuple[*args]
176
- kwargs = kwargs.empty? ? PyObject.null : PyCall::Dict.new(kwargs)
177
- res = LibPython.PyObject_Call(__pyobj__, args.__pyobj__, kwargs.__pyobj__)
178
- return res.to_ruby if LibPython.PyErr_Occurred().null?
179
- raise PyError.fetch
173
+ class WrapperModuleCache < WrapperObjectCache
174
+ def initialize
175
+ super(LibPython::API::PyModule_Type)
180
176
  end
181
177
 
182
- def method_missing(name, *args, **kwargs)
183
- name_s = name.to_s
184
- if name_s.end_with? '='
185
- name = name_s[0..-2]
186
- if PyCall.hasattr?(__pyobj__, name.to_s)
187
- PyCall.setattr(__pyobj__, name, args.first)
188
- else
189
- raise NameError, "object has no attribute `#{name}'"
190
- end
191
- elsif PyCall.hasattr?(__pyobj__, name.to_s)
192
- PyCall.getattr(__pyobj__, name)
193
- else
194
- super
178
+ def check_wrapper_object(wrapper_object)
179
+ unless wrapper_object.kind_of?(Module) && wrapper_object.kind_of?(PyObjectWrapper)
180
+ raise TypeError, "unexpected type #{wrapper_object.class} (expected Module extended by PyObjectWrapper)"
195
181
  end
196
182
  end
197
183
 
198
- def to_s
199
- s = LibPython.PyObject_Repr(__pyobj__)
200
- if s.null?
201
- LibPython.PyErr_Clear()
202
- s = LibPython.PyObject_Str(__pyobj__)
203
- if s.null?
204
- LibPython.PyErr_Clear()
205
- return super
206
- end
184
+ def self.instance
185
+ @instance ||= self.new
186
+ end
187
+ end
188
+
189
+ private_constant :WrapperModuleCache
190
+
191
+ def wrap_module(pymodptr)
192
+ check_ismodule(pymodptr)
193
+ WrapperModuleCache.instance.lookup(pymodptr) do
194
+ Module.new do |mod|
195
+ mod.instance_variable_set(:@__pyptr__, pymodptr)
196
+ mod.extend PyObjectWrapper
207
197
  end
208
- s.to_ruby
209
198
  end
199
+ end
200
+
201
+ def check_isclass(pyptr)
202
+ pyptr = pyptr.__pyptr__ if pyptr.kind_of? PyObjectWrapper
203
+ return if pyptr.kind_of? LibPython::API::PyType_Type
204
+ return defined?(LibPython::API::PyClass_Type) && pyptr.kind_of?(LibPython::API::PyClass_Type)
205
+ raise TypeError, "PyType object is required"
206
+ end
210
207
 
211
- alias inspect to_s
208
+ def check_ismodule(pyptr)
209
+ return if pyptr.kind_of? LibPython::API::PyModule_Type
210
+ raise TypeError, "PyModule object is required"
212
211
  end
213
212
  end