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/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