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
@@ -0,0 +1,69 @@
1
+ #include "pycall_internal.h"
2
+
3
+ struct enumerator_head {
4
+ VALUE obj;
5
+ ID meth;
6
+ VALUE args;
7
+ };
8
+
9
+ int
10
+ pycall_obj_is_step_range(VALUE obj)
11
+ {
12
+ struct enumerator_head *eh;
13
+
14
+ if (!RB_TYPE_P(obj, T_DATA)) {
15
+ return 0;
16
+ }
17
+
18
+ if (!rb_obj_is_kind_of(obj, rb_cEnumerator)) {
19
+ return 0;
20
+ }
21
+
22
+ eh = (struct enumerator_head *)DATA_PTR(obj);
23
+
24
+ if (!rb_obj_is_kind_of(eh->obj, rb_cRange)) {
25
+ return 0;
26
+ }
27
+ if (eh->meth == rb_intern("step")) {
28
+ if (!RB_TYPE_P(eh->args, T_ARRAY)) {
29
+ return 0;
30
+ }
31
+ return (RARRAY_LEN(eh->args) == 1);
32
+ }
33
+
34
+ return 0;
35
+ }
36
+
37
+ int
38
+ pycall_extract_range(VALUE obj, VALUE *pbegin, VALUE *pend, int *pexclude_end, VALUE *pstep)
39
+ {
40
+ ID id_begin, id_end, id_exclude_end;
41
+ VALUE begin, end, exclude_end, step = Qnil;
42
+
43
+ CONST_ID(id_begin, "begin");
44
+ CONST_ID(id_end, "end");
45
+ CONST_ID(id_exclude_end, "exclude_end?");
46
+
47
+ if (rb_obj_is_kind_of(obj, rb_cRange)) {
48
+ begin = rb_funcallv(obj, id_begin, 0, 0);
49
+ end = rb_funcallv(obj, id_end, 0, 0);
50
+ exclude_end = rb_funcallv(obj, id_exclude_end, 0, 0);
51
+ }
52
+ else if (pycall_obj_is_step_range(obj)) {
53
+ struct enumerator_head *eh = (struct enumerator_head *)DATA_PTR(obj);
54
+ begin = rb_funcallv(eh->obj, id_begin, 0, 0);
55
+ end = rb_funcallv(eh->obj, id_end, 0, 0);
56
+ exclude_end = rb_funcallv(eh->obj, id_exclude_end, 0, 0);
57
+ step = RARRAY_AREF(eh->args, 0);
58
+ }
59
+ else {
60
+ return 0;
61
+ }
62
+
63
+ if (pbegin) *pbegin = begin;
64
+ if (pend) *pend = end;
65
+ if (pexclude_end) *pexclude_end = RTEST(exclude_end);
66
+ if (pstep) *pstep = step;
67
+
68
+ return 1;
69
+ }
@@ -0,0 +1,432 @@
1
+ #include "pycall_internal.h"
2
+
3
+ static PyMemberDef PyRuby_members[] = {
4
+ {"ruby_object_ptr", Py_T_PYSSIZET, offsetof(PyRubyObject, ruby_object), Py_READONLY},
5
+ {NULL} /* sentinel */
6
+ };
7
+
8
+ static void PyRuby_dealloc(PyRubyObject *);
9
+ static PyObject * PyRuby_repr(PyRubyObject *);
10
+ static PyObject * PyRuby_call(PyRubyObject *, PyObject *, PyObject *);
11
+ static PyObject * PyRuby_getattro(PyRubyObject *, PyObject *);
12
+
13
+ PyTypeObject PyRuby_Type = {
14
+ PyVarObject_HEAD_INIT(NULL, 0)
15
+ "PyCall.ruby_object", /* tp_name */
16
+ sizeof(PyRubyObject), /* tp_basicsize */
17
+ 0, /* tp_itemsize */
18
+ (destructor)PyRuby_dealloc, /* tp_dealloc */
19
+ 0, /* tp_print */
20
+ 0, /* tp_getattr */
21
+ 0, /* tp_setattr */
22
+ 0, /* tp_reserved */
23
+ (reprfunc)PyRuby_repr, /* tp_repr */
24
+ 0, /* tp_as_number */
25
+ 0, /* tp_as_sequence */
26
+ 0, /* tp_as_mapping */
27
+ {0}, /* tp_hash */
28
+ (ternaryfunc)PyRuby_call, /* tp_call */
29
+ 0, /* tp_str */
30
+ (getattrofunc)PyRuby_getattro, /* tp_getattro */
31
+ 0, /* tp_setattro */
32
+ 0, /* tp_as_buffer */
33
+ Py_TPFLAGS_BASETYPE, /* tp_flags */
34
+ "ruby object wrapper", /* tp_doc */
35
+ 0, /*tp_traverse*/
36
+ 0, /*tp_clear*/
37
+ 0, /*tp_richcompare*/
38
+ 0, /*tp_weaklistoffset*/
39
+ 0, /*tp_iter*/
40
+ 0, /*tp_iternext*/
41
+ 0, /*tp_methods*/
42
+ PyRuby_members, /*tp_members*/
43
+ };
44
+
45
+ PyObject *
46
+ PyRuby_New(VALUE ruby_object)
47
+ {
48
+ PyRubyObject *op;
49
+
50
+ op = (PyRubyObject *)Py_API(_PyObject_New)(&PyRuby_Type);
51
+ op->ruby_object = ruby_object;
52
+ pycall_gcguard_register_pyrubyobj((PyObject *)op);
53
+ return (PyObject *)op;
54
+ }
55
+
56
+ static VALUE
57
+ funcall_id2ref(VALUE object_id)
58
+ {
59
+ VALUE rb_mObjSpace;
60
+ object_id = rb_check_to_integer(object_id, "to_int");
61
+ rb_mObjSpace = rb_const_get(rb_cObject, rb_intern("ObjectSpace"));
62
+ return rb_funcall(rb_mObjSpace, rb_intern("_id2ref"), 1, object_id);
63
+ }
64
+
65
+ static VALUE
66
+ protect_id2ref(VALUE object_id)
67
+ {
68
+ VALUE obj;
69
+ int state;
70
+
71
+ obj = rb_protect((VALUE (*)(VALUE))funcall_id2ref, object_id, &state);
72
+ if (state)
73
+ return Qundef;
74
+
75
+ return obj;
76
+ }
77
+
78
+ static VALUE
79
+ protect_id2ref_and_set_pyerr(VALUE object_id)
80
+ {
81
+ VALUE obj = protect_id2ref(object_id);
82
+ if (obj != Qundef)
83
+ return obj;
84
+
85
+ obj = rb_errinfo();
86
+ if (RTEST(rb_obj_is_kind_of(obj, rb_eRangeError))) {
87
+ Py_API(PyErr_SetString)(Py_API(PyExc_RuntimeError), "[BUG] referenced object was garbage-collected");
88
+ }
89
+ else {
90
+ VALUE emesg = rb_check_funcall(obj, rb_intern("message"), 0, 0);
91
+ Py_API(PyErr_Format)(Py_API(PyExc_RuntimeError),
92
+ "[BUG] Unable to obtain ruby object from ID: %s (%s)",
93
+ StringValueCStr(emesg), rb_class2name(CLASS_OF(obj)));
94
+ }
95
+ return Qundef;
96
+ }
97
+
98
+ static VALUE
99
+ PyRuby_get_ruby_object_and_set_pyerr(PyObject *pyobj)
100
+ {
101
+ VALUE obj_id;
102
+ if (!PyRuby_Check(pyobj))
103
+ return Qundef;
104
+ obj_id = rb_obj_id(PyRuby_get_ruby_object(pyobj));
105
+ return protect_id2ref_and_set_pyerr(obj_id);
106
+ }
107
+
108
+ static void
109
+ PyRuby_dealloc(PyRubyObject *pyro)
110
+ {
111
+ VALUE obj;
112
+
113
+ obj = PyRuby_get_ruby_object((PyObject *)pyro);
114
+
115
+ #ifdef PYCALL_DEBUG_DUMP_REFCNT
116
+ fprintf(stderr, "PyRuby_dealloc(%p), ruby_object=%"PRI_LL_PREFIX"d\n", pyro, NUM2LL(rb_obj_id(obj)));
117
+ #endif /* PYCALL_DEBUG_DUMP_REFCNT */
118
+
119
+ if (obj == Qundef)
120
+ return;
121
+
122
+ pycall_gcguard_unregister_pyrubyobj((PyObject *)pyro);
123
+ }
124
+
125
+ static PyObject *
126
+ PyRuby_repr(PyRubyObject *pyro)
127
+ {
128
+ VALUE obj, str;
129
+ PyObject *res;
130
+
131
+ obj = PyRuby_get_ruby_object_and_set_pyerr((PyObject *)pyro);
132
+ if (obj == Qundef)
133
+ return NULL;
134
+
135
+ str = rb_inspect(obj);
136
+ res = pycall_pystring_from_format("<PyCall.ruby_object %s>", StringValueCStr(str));
137
+ return res;
138
+ }
139
+
140
+ #if SIZEOF_SSIZE_T < 8
141
+ /* int64to32hash from src/support/hashing.c in Julia */
142
+ static inline uint32_t
143
+ int64to32hash(uint64_t key)
144
+ {
145
+ key = (~key) + (key << 18); // key = (key << 18) - key - 1;
146
+ key = key ^ (key >> 31);
147
+ key = key * 21; // key = (key + (key << 2)) + (key << 4);
148
+ key = key ^ (key >> 11);
149
+ key = key + (key << 6);
150
+ key = key ^ (key >> 22);
151
+ return (uint32_t)key;
152
+ }
153
+ #endif
154
+
155
+ static long
156
+ PyRuby_hash_long(PyRubyObject *pyro)
157
+ {
158
+ VALUE obj, rbhash;
159
+ long h;
160
+
161
+ obj = PyRuby_get_ruby_object_and_set_pyerr((PyObject *)pyro);
162
+ if (obj == Qundef)
163
+ return -1;
164
+
165
+ rbhash = rb_hash(obj);
166
+ h = FIX2LONG(rbhash); /* Ruby's hash value is a Fixnum */
167
+
168
+ return h == -1 ? pycall_hash_salt : h;
169
+ }
170
+
171
+ static Py_hash_t
172
+ PyRuby_hash_hash_t(PyRubyObject *pyro)
173
+ {
174
+ VALUE obj, rbhash;
175
+ Py_hash_t h;
176
+
177
+ obj = PyRuby_get_ruby_object_and_set_pyerr((PyObject *)pyro);
178
+ if (obj == Qundef)
179
+ return -1;
180
+
181
+ rbhash = rb_hash(obj);
182
+ #if SIZEOF_PY_HASH_T == SIZEOF_LONG
183
+ /* In this case, we can assume sizeof(Py_hash_t) == sizeof(long) */
184
+ h = NUM2SSIZET(rbhash);
185
+ return h == -1 ? (Py_hash_t)pycall_hash_salt : h;
186
+ #else
187
+ /* In this case, we can assume sizeof(long) == 4 and sizeof(Py_hash_t) == 8 */
188
+ h = (pycall_hash_salt_32 << 32) | FIX2LONG(rbhash);
189
+ return h == -1 ? ((pycall_hash_salt << 32) | pycall_hash_salt) : h;
190
+ #endif
191
+ }
192
+
193
+ struct call_rb_funcallv_params {
194
+ VALUE recv;
195
+ ID meth;
196
+ int argc;
197
+ VALUE *argv;
198
+ };
199
+
200
+ static VALUE
201
+ call_rb_funcallv(struct call_rb_funcallv_params *params)
202
+ {
203
+ return rb_funcallv(params->recv, params->meth, params->argc, params->argv);
204
+ }
205
+
206
+ static VALUE
207
+ rb_protect_funcallv(VALUE recv, ID meth, int argc, VALUE *argv, int *pstate)
208
+ {
209
+ struct call_rb_funcallv_params params;
210
+ VALUE res;
211
+ int state;
212
+
213
+ params.recv = recv;
214
+ params.meth = meth;
215
+ params.argc = argc;
216
+ params.argv = argv;
217
+
218
+ res = rb_protect((VALUE (*)(VALUE))call_rb_funcallv, (VALUE)&params, &state);
219
+ if (pstate) *pstate = state;
220
+ if (state) return Qnil;
221
+ return res;
222
+ }
223
+
224
+ static PyObject *
225
+ PyRuby_call(PyRubyObject *pyro, PyObject *pyobj_args, PyObject *pyobj_kwargs)
226
+ {
227
+ ID id_call;
228
+ VALUE obj, args, kwargs, res;
229
+ PyObject *pyobj_res;
230
+ int state;
231
+
232
+ obj = PyRuby_get_ruby_object_and_set_pyerr((PyObject *)pyro);
233
+ if (obj == Qundef)
234
+ return NULL;
235
+
236
+ id_call = rb_intern("call");
237
+ if (!rb_respond_to(obj, id_call)) {
238
+ Py_API(PyErr_SetString)(Py_API(PyExc_TypeError), "non-callable ruby object");
239
+ return NULL;
240
+ }
241
+
242
+ args = pycall_pyobject_to_a(pyobj_args);
243
+ if (pyobj_kwargs) {
244
+ kwargs = pycall_pyobject_to_ruby(pyobj_kwargs);
245
+ rb_ary_push(args, kwargs);
246
+ }
247
+
248
+ res = rb_protect_funcallv(obj, id_call, (int)RARRAY_LEN(args), RARRAY_PTR(args), &state);
249
+ if (state) {
250
+ /* TODO: pyerr set */
251
+ }
252
+
253
+ pyobj_res = pycall_pyobject_from_ruby(res);
254
+ return pyobj_res;
255
+ }
256
+
257
+ static PyObject *
258
+ PyRuby_getattro(PyRubyObject *pyro, PyObject *pyobj_name)
259
+ {
260
+ VALUE obj, name, res;
261
+ char const *name_cstr;
262
+ ID name_id;
263
+ PyObject *pyobj_res;
264
+
265
+ obj = PyRuby_get_ruby_object_and_set_pyerr((PyObject *)pyro);
266
+ if (obj == Qundef)
267
+ return NULL;
268
+
269
+ name = pycall_pyobject_to_ruby(pyobj_name);
270
+ name_cstr = StringValueCStr(name);
271
+ name_id = rb_intern(name_cstr);
272
+
273
+ /* TODO: should handle exception */
274
+ if (strncmp(name_cstr, "__name__", 8) == 0 ||
275
+ strncmp(name_cstr, "func_name", 9) == 0) {
276
+ if (rb_respond_to(obj, rb_intern("name"))) {
277
+ res = rb_funcall(obj, rb_intern("name"), 0);
278
+ }
279
+ else {
280
+ res = rb_any_to_s(obj);
281
+ }
282
+ }
283
+ else if (strncmp(name_cstr, "__doc__", 7) == 0 ||
284
+ strncmp(name_cstr, "func_doc", 8) == 0) {
285
+ /* TODO: support docstring */
286
+ res = Qnil;
287
+ }
288
+ else if (strncmp(name_cstr, "__module__", 10) == 0) {
289
+ res = Qnil;
290
+ }
291
+ else if (strncmp(name_cstr, "__defaults__", 12) == 0 ||
292
+ strncmp(name_cstr, "func_defaults", 13) == 0) {
293
+ res = Qnil;
294
+ }
295
+ else if (strncmp(name_cstr, "__closure__", 11) == 0 ||
296
+ strncmp(name_cstr, "func_closure", 12) == 0) {
297
+ res = Qnil;
298
+ }
299
+ else if (name_cstr[0] == '_' && name_cstr[1] == '_') {
300
+ /* name.start_with? "__" */
301
+ /* TODO: handle `__code__` and `func_code` */
302
+ return Py_API(PyObject_GenericGetAttr)((PyObject *)pyro, pyobj_name);
303
+ }
304
+ else {
305
+ /* TODO: handle `__code__` and `func_code` */
306
+ if (rb_respond_to(obj, name_id)) {
307
+ VALUE method = rb_obj_method(obj, name);
308
+ return PyRuby_New(method);
309
+ }
310
+ return Py_API(PyObject_GenericGetAttr)((PyObject *)pyro, pyobj_name);
311
+ }
312
+
313
+ pyobj_res = pycall_pyobject_from_ruby(res);
314
+ return pyobj_res;
315
+ }
316
+
317
+ /* ==== PyCall::PyRubyPtr ==== */
318
+
319
+ VALUE cPyRubyPtr;
320
+
321
+ const rb_data_type_t pycall_pyrubyptr_data_type = {
322
+ "PyCall::PyRubyPtr",
323
+ { 0, pycall_pyptr_free, pycall_pyptr_memsize, },
324
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
325
+ &pycall_pyptr_data_type, 0, RUBY_TYPED_FREE_IMMEDIATELY
326
+ #endif
327
+ };
328
+
329
+ static inline int
330
+ is_pycall_pyrubyptr(VALUE obj)
331
+ {
332
+ return rb_typeddata_is_kind_of(obj, &pycall_pyrubyptr_data_type);
333
+ }
334
+
335
+ static inline PyRubyObject*
336
+ get_pyrubyobj_ptr(VALUE obj)
337
+ {
338
+ PyRubyObject *pyruby;
339
+ TypedData_Get_Struct(obj, PyRubyObject, &pycall_pyrubyptr_data_type, pyruby);
340
+ return pyruby;
341
+ }
342
+
343
+ static inline PyRubyObject*
344
+ try_get_pyrubyobj_ptr(VALUE obj)
345
+ {
346
+ if (is_pycall_pyrubyptr(obj)) return NULL;
347
+ return (PyRubyObject*)DATA_PTR(obj);
348
+ }
349
+
350
+ static inline PyRubyObject *
351
+ check_get_pyrubyobj_ptr(VALUE obj)
352
+ {
353
+ PyRubyObject *pyrubyobj;
354
+ if (!is_pycall_pyrubyptr(obj))
355
+ rb_raise(rb_eTypeError, "unexpected type %s (expected PyCall::PyRubyPtr)", rb_class2name(CLASS_OF(obj)));
356
+
357
+ pyrubyobj = get_pyrubyobj_ptr(obj);
358
+ if (!PyRuby_Check(pyrubyobj))
359
+ rb_raise(rb_eTypeError, "unexpected Python type %s (expected ruby object)", Py_TYPE(pyrubyobj)->tp_name);
360
+
361
+ return pyrubyobj;
362
+ }
363
+
364
+ static VALUE
365
+ pycall_pyruby_allocate(VALUE klass)
366
+ {
367
+ return TypedData_Wrap_Struct(klass, &pycall_pyrubyptr_data_type, NULL);
368
+ }
369
+
370
+ VALUE
371
+ pycall_pyrubyptr_new(PyObject *pyrubyobj)
372
+ {
373
+ VALUE obj;
374
+
375
+ if (!PyRuby_Check(pyrubyobj)) {
376
+ rb_raise(rb_eTypeError, "wrong type of python object %s (expect PyRubyObject)", Py_TYPE(pyrubyobj)->tp_name);
377
+ }
378
+
379
+ obj = pycall_pyruby_allocate(cPyRubyPtr);
380
+ DATA_PTR(obj) = pyrubyobj;
381
+ return obj;
382
+ }
383
+
384
+ static VALUE
385
+ pycall_pyruby_get_ruby_object_id(VALUE obj)
386
+ {
387
+ PyRubyObject *pyrubyobj;
388
+
389
+ pyrubyobj = check_get_pyrubyobj_ptr(obj);
390
+ return rb_obj_id(pyrubyobj->ruby_object);
391
+ }
392
+
393
+ VALUE
394
+ pycall_wrap_ruby_object(VALUE obj)
395
+ {
396
+ PyObject *pyobj;
397
+ pyobj = PyRuby_New(obj);
398
+ return pycall_pyrubyptr_new(pyobj);
399
+ }
400
+
401
+ static VALUE
402
+ pycall_m_wrap_ruby_object(VALUE mod, VALUE obj)
403
+ {
404
+ return pycall_wrap_ruby_object(obj);
405
+ }
406
+
407
+ void
408
+ pycall_init_ruby_wrapper(void)
409
+ {
410
+ PyRuby_Type.ob_base.ob_type = Py_API(PyType_Type);
411
+ PyRuby_Type.tp_flags |= pycall_default_tp_flags();
412
+ PyRuby_Type.tp_new = Py_API(PyType_GenericNew);
413
+ if (pycall_python_long_hash)
414
+ PyRuby_Type.tp_hash._long = (hashfunc_long)PyRuby_hash_long;
415
+ else
416
+ PyRuby_Type.tp_hash._hash_t = (hashfunc_hash_t)PyRuby_hash_hash_t;
417
+
418
+ if (Py_API(PyType_Ready)(&PyRuby_Type) < 0) {
419
+ pycall_pyerror_fetch_and_raise("PyType_Ready in pycall_init_ruby_wrapper");
420
+ }
421
+ Py_API(Py_IncRef)((PyObject *)&PyRuby_Type);
422
+
423
+ /* TODO */
424
+
425
+ /* PyCall::PyRubyPtr */
426
+
427
+ cPyRubyPtr = rb_define_class_under(mPyCall, "PyRubyPtr", cPyPtr);
428
+ rb_define_alloc_func(cPyRubyPtr, pycall_pyruby_allocate);
429
+ rb_define_method(cPyRubyPtr, "__ruby_object_id__", pycall_pyruby_get_ruby_object_id, 0);
430
+
431
+ rb_define_module_function(mPyCall, "wrap_ruby_object", pycall_m_wrap_ruby_object, 1);
432
+ }