1 /* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 #include "tensorflow/python/lib/core/py_seq_tensor.h"
17
18 #include "tensorflow/c/eager/tfe_context_internal.h"
19 #include "tensorflow/c/eager/tfe_tensorhandle_internal.h"
20 #include "tensorflow/c/tensor_interface.h"
21 #include "tensorflow/c/tf_tensor_internal.h"
22 #include "tensorflow/core/framework/tensor.h"
23 #include "tensorflow/core/framework/types.h"
24 #include "tensorflow/core/lib/core/errors.h"
25 #include "tensorflow/core/lib/strings/str_util.h"
26 #include "tensorflow/core/platform/errors.h"
27 #include "tensorflow/core/platform/macros.h"
28 #include "tensorflow/core/platform/tstring.h"
29 #include "tensorflow/core/platform/types.h"
30 #include "tensorflow/python/lib/core/ndarray_tensor.h"
31 #include "tensorflow/python/lib/core/ndarray_tensor_bridge.h"
32 #include "tensorflow/python/lib/core/numpy.h"
33 #include "tensorflow/python/lib/core/py_util.h"
34 #include "tensorflow/python/lib/core/safe_ptr.h"
35
36 namespace tensorflow {
37 namespace {
38
PyIsInstance(PyObject * obj,PyTypeObject * t)39 inline bool PyIsInstance(PyObject* obj, PyTypeObject* t) {
40 return PyObject_IsInstance(obj, reinterpret_cast<PyObject*>(t));
41 }
42
PyType(PyObject * obj)43 inline PyObject* PyType(PyObject* obj) {
44 return reinterpret_cast<PyObject*>(obj->ob_type);
45 }
46
IsPyString(PyObject * obj)47 bool IsPyString(PyObject* obj) {
48 return PyBytes_Check(obj) || PyUnicode_Check(obj);
49 }
50
IsPyInt(PyObject * obj)51 bool IsPyInt(PyObject* obj) {
52 #if PY_MAJOR_VERSION >= 3
53 return PyLong_Check(obj) ||
54 PyIsInstance(obj, &PyIntegerArrType_Type); // NumPy integers
55 #else
56 return PyInt_Check(obj) || PyLong_Check(obj) ||
57 PyIsInstance(obj, &PyIntegerArrType_Type); // NumPy integers
58 #endif
59 }
60
IsPyDouble(PyObject * obj)61 bool IsPyDouble(PyObject* obj) {
62 return PyIsInstance(obj, &PyDoubleArrType_Type); // NumPy double type.
63 }
64
IsNumpyHalf(PyObject * obj)65 bool IsNumpyHalf(PyObject* obj) {
66 return PyIsInstance(obj, &PyHalfArrType_Type);
67 }
68
IsPyFloat(PyObject * obj)69 bool IsPyFloat(PyObject* obj) {
70 return PyFloat_Check(obj) ||
71 PyIsInstance(obj, &PyFloatingArrType_Type); // NumPy float types
72 }
73
74 struct ConverterState {
75 // The inferred tensor shape.
76 gtl::InlinedVector<int64_t, 4> inferred_shape;
77
78 // The inferred tensor data type.
79 DataType inferred_dtype;
80
81 // The following fields are used by ZeroDimArrayToScalar.
82 // We cache the last result of the check for a zero dimensional array: the
83 // function is called many times in a conversion, and most of the time is
84 // to check for the same type. This cache can reduce the conversion time by
85 // about 25%.
86 PyTypeObject* last_zerodim_type;
87 bool last_zerodim_check;
88
ConverterStatetensorflow::__anonfc08bad90111::ConverterState89 ConverterState() : inferred_dtype(DT_INVALID), last_zerodim_type(nullptr) {}
90 };
91
92 // If the input is a zero dimensional PyArray return it converted to a scalar.
93 // Otherwise return the input and increment its reference count.
94 // Users must Py_DECREF the output of this method.
ZeroDimArrayToScalar(PyObject * obj,ConverterState * state)95 PyObject* ZeroDimArrayToScalar(PyObject* obj, ConverterState* state) {
96 auto type = Py_TYPE(obj);
97 auto pyarray_obj = reinterpret_cast<PyArrayObject*>(obj);
98 if (type != state->last_zerodim_type) {
99 state->last_zerodim_type = type;
100 state->last_zerodim_check =
101 PyObject_TypeCheck(obj, &PyArray_Type) &&
102 !PyObject_TypeCheck(obj, &PyGenericArrType_Type);
103 }
104
105 if (state->last_zerodim_check && PyArray_NDIM(pyarray_obj) == 0) {
106 obj = PyArray_ToScalar(PyArray_DATA(pyarray_obj), pyarray_obj);
107 } else {
108 Py_INCREF(obj);
109 }
110 return obj;
111 }
112
113 // Sets *elem to a NEW reference to an element in seq on success.
114 // REQUIRES: PySequence_Check(seq) && PySequence_Length(seq) > 0.
SampleElementFromSequence(PyObject * seq,PyObject ** elem)115 Status SampleElementFromSequence(PyObject* seq, PyObject** elem) {
116 *elem = PySequence_GetItem(seq, 0);
117 if (*elem != nullptr) return OkStatus();
118 // seq may implement the sequence protocol (i.e., implement __getitem__)
119 // but may legitimately not have a 0-th element (__getitem__(self, 0)
120 // raises a KeyError). For example:
121 // seq = pandas.Series([0, 1, 2], index=[2, 4, 6])
122 //
123 // We don't actually care for the element at key 0, any element will do
124 // for inferring the element types. All elements are expected to
125 // have the same type, and this will be validated when converting
126 // to an EagerTensor.
127 PyErr_Clear();
128 Safe_PyObjectPtr iter(PyObject_GetIter(seq));
129 if (PyErr_Occurred()) {
130 return errors::InvalidArgument("Cannot infer dtype of a ",
131 Py_TYPE(seq)->tp_name,
132 " object: ", PyExceptionFetch());
133 }
134 *elem = PyIter_Next(iter.get());
135 if (PyErr_Occurred()) {
136 return errors::InvalidArgument(
137 "Cannot infer dtype of a ", Py_TYPE(seq)->tp_name,
138 " object, as iter(<object>).next() failed: ", PyExceptionFetch());
139 }
140 if (*elem == nullptr) {
141 return errors::InvalidArgument("Cannot infer dtype of a ",
142 Py_TYPE(seq)->tp_name,
143 " object since it is an empty sequence");
144 }
145 return OkStatus();
146 }
147
148 tstring PyRepr(PyObject* obj);
149 bool IsPyDimension(PyObject* obj);
150
InferShapeAndType(PyObject * obj,ConverterState * state)151 Status InferShapeAndType(PyObject* obj, ConverterState* state) {
152 std::vector<Safe_PyObjectPtr> refs_to_clean;
153 while (true) {
154 // Convert any zero dimensional numpy arrays to scalars first of all.
155 // We also have to make sure a reference to the safe_obj is kept.
156 obj = ZeroDimArrayToScalar(obj, state);
157 refs_to_clean.push_back(make_safe(obj));
158 // We test strings first, in case a string is considered a sequence.
159 if (IsPyString(obj)) {
160 state->inferred_dtype = DT_STRING;
161 } else if (PySequence_Check(obj)) {
162 auto length = PySequence_Length(obj);
163 if (length > 0) {
164 state->inferred_shape.push_back(length);
165 PyObject* elem = nullptr;
166 TF_RETURN_IF_ERROR(SampleElementFromSequence(obj, &elem));
167 obj = elem;
168 refs_to_clean.push_back(make_safe(obj));
169 continue;
170 } else if (length == 0) {
171 state->inferred_shape.push_back(length);
172 state->inferred_dtype = DT_INVALID; // Invalid dtype for empty tensors.
173 } else {
174 // The sequence does not have a valid length (PySequence_Length < 0).
175 if (PyErr_Occurred()) {
176 // PySequence_Length failed and set an exception. Fetch the message
177 // and convert it to a failed status.
178 return errors::InvalidArgument(PyExceptionFetch());
179 } else {
180 // This is almost certainly dead code: PySequence_Length failed but
181 // did not set an exception.
182 return errors::InvalidArgument(
183 "Attempted to convert an invalid sequence to a Tensor.");
184 }
185 }
186 } else if (IsPyDouble(obj)) {
187 state->inferred_dtype = DT_DOUBLE;
188 } else if (IsNumpyHalf(obj)) {
189 state->inferred_dtype = DT_HALF;
190 } else if (IsPyFloat(obj)) {
191 state->inferred_dtype = DT_FLOAT;
192 } else if (PyBool_Check(obj) || PyIsInstance(obj, &PyBoolArrType_Type)) {
193 // Have to test for bool before int, since IsInt(True/False) == true.
194 state->inferred_dtype = DT_BOOL;
195 } else if (IsPyInt(obj)) {
196 state->inferred_dtype = DT_INT64;
197 } else if (IsPyDimension(obj)) {
198 state->inferred_dtype = DT_INT64;
199 } else if (PyComplex_Check(obj) ||
200 PyIsInstance(obj, &PyComplexFloatingArrType_Type)) { // NumPy
201 state->inferred_dtype = DT_COMPLEX128;
202 } else {
203 return errors::InvalidArgument("Attempt to convert a value (",
204 PyRepr(obj),
205 ") with an unsupported type (",
206 PyRepr(PyType(obj)), ") to a Tensor.");
207 }
208 return OkStatus();
209 }
210 }
211
212 // Error messages
213
214 const char ErrorConverting[] =
215 "Error while converting Python sequence to Tensor.";
216 const char ErrorRectangular[] =
217 "Can't convert non-rectangular Python sequence to Tensor.";
218 const char ErrorMixedTypes[] =
219 "Can't convert Python sequence with mixed types to Tensor.";
220 const char ErrorOutOfRange[] =
221 "Can't convert Python sequence with out-of-range integer to Tensor.";
222 const char ErrorOutOfRangeDouble[] =
223 "Can't convert Python sequence with a value out of range for a "
224 "double-precision float.";
225 const char ErrorConvertingUnicodeString[] =
226 "Error converting unicode string while converting Python sequence to "
227 "Tensor.";
228 const char ErrorFoundInt64[] =
229 "Can't convert Python sequence with out-of-range integer to int32 Tensor.";
230 const char ErrorFoundFloat[] =
231 "Can't convert Python sequence with floating point values to integer "
232 "Tensor.";
233
234 // Defines a converter that recursively converts an object into
235 // an array of type T using the conversion function defined by the
236 // traits class in a ConvertScalar function.
237 //
238 // Note that these helper functions require shape.dims() >= 1.
239 template <class T>
240 struct ConverterTraits {
241 static const tensorflow::DataType kTypeEnum;
242 static const char* ConvertScalar(PyObject* v, T* out);
243 };
244
245 template <class T>
246 struct Converter {
Helpertensorflow::__anonfc08bad90111::Converter247 static const char* Helper(PyObject* obj, int depth, ConverterState* state,
248 T** buf) {
249 if (TF_PREDICT_FALSE(obj == nullptr)) {
250 return ErrorConverting;
251 }
252
253 Safe_PyObjectPtr seq = make_safe(PySequence_Fast(obj, ""));
254 if (TF_PREDICT_FALSE(seq == nullptr)) return ErrorRectangular;
255
256 const int64_t s = state->inferred_shape[depth];
257 if (TF_PREDICT_FALSE(s != PySequence_Fast_GET_SIZE(seq.get()))) {
258 return ErrorRectangular;
259 }
260
261 if (state->inferred_shape.size() - depth > 1) {
262 /* Iterate over outer dim, and recursively convert each element. */
263 for (int64_t i = 0; i < s; ++i) {
264 const char* error = Helper(PySequence_Fast_GET_ITEM(seq.get(), i),
265 depth + 1, state, buf);
266 if (TF_PREDICT_FALSE(error != nullptr)) return error;
267 }
268 } else {
269 PyObject** l = PySequence_Fast_ITEMS(seq.get());
270 for (int64_t i = 0; i < s; ++i) {
271 auto scalar = ZeroDimArrayToScalar(l[i], state);
272 const char* error = ConverterTraits<T>::ConvertScalar(scalar, *buf);
273 Py_DECREF(scalar);
274 if (TF_PREDICT_FALSE(error != nullptr)) return error;
275 ++*buf;
276 }
277 }
278 return nullptr;
279 }
280
Converttensorflow::__anonfc08bad90111::Converter281 static Status Convert(TFE_Context* ctx, PyObject* obj, ConverterState* state,
282 TFE_TensorHandle** h, const char** error) {
283 // TODO(josh11b): Allocator & attributes
284 AbstractTensorInterface* t;
285 if (state->inferred_shape.empty()) { /* Scalar case */
286 T value;
287 auto scalar = ZeroDimArrayToScalar(obj, state);
288 *error = ConverterTraits<T>::ConvertScalar(scalar, &value);
289 Py_DECREF(scalar);
290 if (*error != nullptr) return errors::InvalidArgument(*error);
291 t = ConverterTraits<T>::CreateScalar(ctx, value);
292 if (t == nullptr) {
293 return errors::Internal("Cannot create tensor.");
294 }
295 } else {
296 t = ConverterTraits<T>::CreateTensor(ctx, state->inferred_shape);
297 if (t == nullptr) {
298 return errors::Internal("Cannot create tensor.");
299 }
300 if (t->NumElements() > 0) {
301 T* buf = static_cast<T*>(t->Data());
302 *error = Helper(obj, 0, state, &buf);
303 if (*error != nullptr) {
304 t->Release();
305 return errors::InvalidArgument(*error);
306 }
307 }
308 }
309 *h = tensorflow::wrap(tensorflow::unwrap(ctx)->CreateLocalHandle(t));
310 t->Release();
311 return OkStatus();
312 }
313 };
314
315 // Int support
316
317 template <>
318 struct ConverterTraits<int64_t> {
CreateScalartensorflow::__anonfc08bad90111::ConverterTraits319 static AbstractTensorInterface* CreateScalar(TFE_Context* ctx,
320 int64_t value) {
321 return tensorflow::unwrap(ctx)->CreateInt64Scalar(value);
322 }
323
CreateTensortensorflow::__anonfc08bad90111::ConverterTraits324 static AbstractTensorInterface* CreateTensor(
325 TFE_Context* ctx, absl::Span<const int64_t> dim_sizes) {
326 return tensorflow::unwrap(ctx)->CreateTensor(DT_INT64, dim_sizes);
327 }
328
ConvertScalartensorflow::__anonfc08bad90111::ConverterTraits329 static const char* ConvertScalar(PyObject* v, int64_t* out) {
330 #if PY_MAJOR_VERSION < 3
331 if (TF_PREDICT_TRUE(PyInt_Check(v))) {
332 *out = PyInt_AS_LONG(v);
333 return nullptr;
334 }
335 #endif
336 if (TF_PREDICT_TRUE(PyLong_Check(v) || IsPyDimension(v))) {
337 int overflow = 0;
338 // Have to use LongLong for 64 bits, since long is 32 bits on Windows.
339 *out = PyLong_AsLongLongAndOverflow(v, &overflow);
340 if (TF_PREDICT_FALSE(overflow)) return ErrorOutOfRange;
341 return nullptr;
342 }
343 if (PyIsInstance(v, &PyIntegerArrType_Type)) { // NumPy integers
344 #if PY_MAJOR_VERSION < 3
345 Safe_PyObjectPtr as_int = make_safe(PyNumber_Int(v));
346 #else
347 Safe_PyObjectPtr as_int = make_safe(PyNumber_Long(v));
348 #endif
349 return ConvertScalar(as_int.get(), out);
350 }
351 if (IsPyFloat(v)) return ErrorFoundFloat;
352 return ErrorMixedTypes;
353 }
354 };
355
356 typedef Converter<int64_t> Int64Converter;
357
358 template <>
359 struct ConverterTraits<uint64> {
CreateScalartensorflow::__anonfc08bad90111::ConverterTraits360 static AbstractTensorInterface* CreateScalar(TFE_Context* ctx, uint64 value) {
361 return tensorflow::unwrap(ctx)->CreateUint64Scalar(value);
362 }
363
CreateTensortensorflow::__anonfc08bad90111::ConverterTraits364 static AbstractTensorInterface* CreateTensor(
365 TFE_Context* ctx, absl::Span<const int64_t> dim_sizes) {
366 return tensorflow::unwrap(ctx)->CreateTensor(DT_UINT64, dim_sizes);
367 }
368
ConvertScalartensorflow::__anonfc08bad90111::ConverterTraits369 static const char* ConvertScalar(PyObject* v, uint64* out) {
370 #if PY_MAJOR_VERSION < 3
371 if (TF_PREDICT_TRUE(PyInt_Check(v))) {
372 *out = PyInt_AsUnsignedLongLongMask(v);
373 return nullptr;
374 }
375 #endif
376 if (TF_PREDICT_TRUE(PyLong_Check(v) || IsPyDimension(v))) {
377 *out = PyLong_AsUnsignedLongLong(v);
378 return nullptr;
379 }
380 if (PyIsInstance(v, &PyIntegerArrType_Type)) { // NumPy integers
381 #if PY_MAJOR_VERSION < 3
382 Safe_PyObjectPtr as_int = make_safe(PyNumber_Int(v));
383 #else
384 Safe_PyObjectPtr as_int = make_safe(PyNumber_Long(v));
385 #endif
386 return ConvertScalar(as_int.get(), out);
387 }
388 if (IsPyFloat(v)) return ErrorFoundFloat;
389 return ErrorMixedTypes;
390 }
391 };
392
393 typedef Converter<uint64> UInt64Converter;
394
395 template <>
396 struct ConverterTraits<int32> {
CreateScalartensorflow::__anonfc08bad90111::ConverterTraits397 static AbstractTensorInterface* CreateScalar(TFE_Context* ctx,
398 int32_t value) {
399 return tensorflow::unwrap(ctx)->CreateInt32Scalar(value);
400 }
401
CreateTensortensorflow::__anonfc08bad90111::ConverterTraits402 static AbstractTensorInterface* CreateTensor(
403 TFE_Context* ctx, absl::Span<const int64_t> dim_sizes) {
404 return tensorflow::unwrap(ctx)->CreateTensor(DT_INT32, dim_sizes);
405 }
406
ConvertScalartensorflow::__anonfc08bad90111::ConverterTraits407 static const char* ConvertScalar(PyObject* v, int32* out) {
408 int64_t i;
409 #if PY_MAJOR_VERSION < 3
410 if (TF_PREDICT_TRUE(PyInt_Check(v))) {
411 i = PyInt_AS_LONG(v);
412 } else
413 #endif
414 if (PyLong_Check(v) || IsPyDimension(v)) {
415 int overflow = 0;
416 // Have to use LongLong for 64 bits, since long is 32 bits on Windows.
417 i = PyLong_AsLongLongAndOverflow(v, &overflow);
418 if (TF_PREDICT_FALSE(overflow)) return ErrorOutOfRange;
419 } else if (PyIsInstance(v, &PyIntegerArrType_Type)) { // NumPy integers
420 #if PY_MAJOR_VERSION < 3
421 Safe_PyObjectPtr as_int = make_safe(PyNumber_Int(v));
422 #else
423 Safe_PyObjectPtr as_int = make_safe(PyNumber_Long(v));
424 #endif
425 return ConvertScalar(as_int.get(), out);
426 } else if (IsPyFloat(v)) {
427 return ErrorFoundFloat;
428 } else {
429 return ErrorMixedTypes;
430 }
431 *out = static_cast<uint32>(static_cast<uint64>(i));
432 // Check for 32-bit overflow.
433 if (TF_PREDICT_FALSE(i != *out)) return ErrorFoundInt64;
434 return nullptr;
435 }
436 };
437
438 typedef Converter<int32> Int32Converter;
439
440 // Floating-point support
441
442 // Returns `true` if `out` overflows when converted from `as_double`.
443 template <class T>
CheckForOverflow(double as_double,T * out)444 static inline bool CheckForOverflow(double as_double, T* out) {
445 return (sizeof(T) < sizeof(double) && std::isinf(*out) &&
446 std::isfinite(as_double));
447 }
448
449 // There is no `std::isinf` that takes `Eigen::half` as argument but Eigen
450 // provides `Eigen::numext::isinf` instead.
451 template <>
CheckForOverflow(double as_double,Eigen::half * out)452 inline bool CheckForOverflow<Eigen::half>(double as_double, Eigen::half* out) {
453 return (Eigen::numext::isinf(*out) && std::isfinite(as_double));
454 }
455
456 template <class T>
ConvertOneFloat(PyObject * v,T * out)457 static const char* ConvertOneFloat(PyObject* v, T* out) {
458 if (PyErr_Occurred()) {
459 return nullptr;
460 }
461 if (TF_PREDICT_TRUE(PyFloat_Check(v))) {
462 const double as_double = PyFloat_AS_DOUBLE(v);
463 *out = static_cast<T>(as_double);
464 // Check for overflow
465 if (TF_PREDICT_FALSE(CheckForOverflow<T>(as_double, out))) {
466 return ErrorOutOfRangeDouble;
467 }
468 return nullptr;
469 }
470 #if PY_MAJOR_VERSION < 3
471 if (PyInt_Check(v)) {
472 *out = static_cast<T>(PyInt_AS_LONG(v));
473 return nullptr;
474 }
475 #endif
476 if (PyLong_Check(v)) {
477 *out = static_cast<T>(PyLong_AsDouble(v));
478 if (PyErr_Occurred()) return ErrorOutOfRangeDouble;
479 return nullptr;
480 }
481 if (PyIsInstance(v, &PyFloatingArrType_Type)) { // NumPy float types
482 Safe_PyObjectPtr as_float = make_safe(PyNumber_Float(v));
483 if (PyErr_Occurred()) {
484 return nullptr;
485 }
486 return ConvertOneFloat<T>(as_float.get(), out);
487 }
488 if (PyIsInstance(v, &PyIntegerArrType_Type)) { // NumPy integers
489 #if PY_MAJOR_VERSION < 3
490 Safe_PyObjectPtr as_int = make_safe(PyNumber_Int(v));
491 #else
492 Safe_PyObjectPtr as_int = make_safe(PyNumber_Long(v));
493 #endif
494 if (PyErr_Occurred()) {
495 return nullptr;
496 }
497 return ConvertOneFloat<T>(as_int.get(), out);
498 }
499 return ErrorMixedTypes;
500 }
501
502 template <>
503 struct ConverterTraits<float> {
CreateScalartensorflow::__anonfc08bad90111::ConverterTraits504 static AbstractTensorInterface* CreateScalar(TFE_Context* ctx, float value) {
505 return tensorflow::unwrap(ctx)->CreateFloatScalar(value);
506 }
507
CreateTensortensorflow::__anonfc08bad90111::ConverterTraits508 static AbstractTensorInterface* CreateTensor(
509 TFE_Context* ctx, absl::Span<const int64_t> dim_sizes) {
510 return tensorflow::unwrap(ctx)->CreateTensor(DT_FLOAT, dim_sizes);
511 }
512
ConvertScalartensorflow::__anonfc08bad90111::ConverterTraits513 static const char* ConvertScalar(PyObject* v, float* out) {
514 return ConvertOneFloat<float>(v, out);
515 }
516 };
517
518 template <>
519 struct ConverterTraits<double> {
CreateScalartensorflow::__anonfc08bad90111::ConverterTraits520 static AbstractTensorInterface* CreateScalar(TFE_Context* ctx, double value) {
521 return tensorflow::unwrap(ctx)->CreateDoubleScalar(value);
522 }
523
CreateTensortensorflow::__anonfc08bad90111::ConverterTraits524 static AbstractTensorInterface* CreateTensor(
525 TFE_Context* ctx, absl::Span<const int64_t> dim_sizes) {
526 return tensorflow::unwrap(ctx)->CreateTensor(DT_DOUBLE, dim_sizes);
527 }
528
ConvertScalartensorflow::__anonfc08bad90111::ConverterTraits529 static const char* ConvertScalar(PyObject* v, double* out) {
530 return ConvertOneFloat<double>(v, out);
531 }
532 };
533
534 typedef Converter<double> DoubleConverter;
535 typedef Converter<float> FloatConverter;
536
537 template <>
538 struct ConverterTraits<Eigen::half> {
CreateScalartensorflow::__anonfc08bad90111::ConverterTraits539 static AbstractTensorInterface* CreateScalar(TFE_Context* ctx,
540 Eigen::half value) {
541 return tensorflow::unwrap(ctx)->CreateHalfScalar(value);
542 }
543
CreateTensortensorflow::__anonfc08bad90111::ConverterTraits544 static AbstractTensorInterface* CreateTensor(
545 TFE_Context* ctx, absl::Span<const int64_t> dim_sizes) {
546 return tensorflow::unwrap(ctx)->CreateTensor(DT_HALF, dim_sizes);
547 }
548
ConvertScalartensorflow::__anonfc08bad90111::ConverterTraits549 static const char* ConvertScalar(PyObject* v, Eigen::half* out) {
550 return ConvertOneFloat<Eigen::half>(v, out);
551 }
552 };
553
554 typedef Converter<Eigen::half> NumpyHalfConverter;
555
556 // String support
557
558 template <>
559 struct ConverterTraits<tstring> {
CreateScalartensorflow::__anonfc08bad90111::ConverterTraits560 static AbstractTensorInterface* CreateScalar(TFE_Context* ctx,
561 tstring value) {
562 return tensorflow::unwrap(ctx)->CreateStringScalar(value);
563 }
564
CreateTensortensorflow::__anonfc08bad90111::ConverterTraits565 static AbstractTensorInterface* CreateTensor(
566 TFE_Context* ctx, absl::Span<const int64_t> dim_sizes) {
567 return tensorflow::unwrap(ctx)->CreateTensor(DT_STRING, dim_sizes);
568 }
569
ConvertScalartensorflow::__anonfc08bad90111::ConverterTraits570 static const char* ConvertScalar(PyObject* v, tstring* out) {
571 if (PyBytes_Check(v)) {
572 out->assign(PyBytes_AS_STRING(v), PyBytes_GET_SIZE(v));
573 return nullptr;
574 }
575 if (PyUnicode_Check(v)) {
576 #if PY_MAJOR_VERSION >= 3
577 Py_ssize_t size;
578 const char* str = PyUnicode_AsUTF8AndSize(v, &size);
579 if (str == nullptr) return ErrorConvertingUnicodeString;
580 out->assign(str, size);
581 return nullptr;
582 #else
583 PyObject* py_str = PyUnicode_AsUTF8String(v);
584 if (py_str == nullptr) return ErrorConvertingUnicodeString;
585 out->assign(PyBytes_AS_STRING(py_str), PyBytes_GET_SIZE(py_str));
586 Py_DECREF(py_str);
587 return nullptr;
588 #endif
589 }
590 return ErrorMixedTypes;
591 }
592 };
593
594 typedef Converter<tstring> StringConverter;
595
596 // Converts Python object `c` that should hold a Python string into a
597 // C++ string in *out. Returns nullptr on success, or a message on error.
598 // Defined below, but forward declared here for use in PyRepr.
PyRepr(PyObject * obj)599 tstring PyRepr(PyObject* obj) {
600 if (obj == nullptr) {
601 return "<null>";
602 }
603 Safe_PyObjectPtr repr_obj = make_safe(PyObject_Repr(obj));
604 if (repr_obj) {
605 tstring repr_str;
606 if (ConverterTraits<tstring>::ConvertScalar(repr_obj.get(), &repr_str) ==
607 nullptr) {
608 return repr_str;
609 }
610 }
611 return "<error computing repr()>";
612 }
613
IsPyDimension(PyObject * obj)614 bool IsPyDimension(PyObject* obj) {
615 const char* tp_name = obj->ob_type->tp_name;
616 if (strcmp(tp_name, "Dimension") != 0) return false;
617 bool ret = str_util::EndsWith(
618 PyRepr(PyType(obj)),
619 "tensorflow.python.framework.tensor_shape.Dimension'>");
620 return ret;
621 }
622
623 // Complex support
624
625 template <>
626 struct ConverterTraits<complex128> {
CreateScalartensorflow::__anonfc08bad90111::ConverterTraits627 static AbstractTensorInterface* CreateScalar(TFE_Context* ctx,
628 complex128 value) {
629 return tensorflow::unwrap(ctx)->CreateComplex128Scalar(value);
630 }
631
CreateTensortensorflow::__anonfc08bad90111::ConverterTraits632 static AbstractTensorInterface* CreateTensor(
633 TFE_Context* ctx, absl::Span<const int64_t> dim_sizes) {
634 return tensorflow::unwrap(ctx)->CreateTensor(DT_COMPLEX128, dim_sizes);
635 }
636
ConvertScalartensorflow::__anonfc08bad90111::ConverterTraits637 static const char* ConvertScalar(PyObject* v, complex128* out) {
638 if (PyComplex_Check(v)) {
639 *out = complex128(PyComplex_RealAsDouble(v), PyComplex_ImagAsDouble(v));
640 return nullptr;
641 } else if (PyIsInstance(v, &PyComplexFloatingArrType_Type)) { // NumPy
642 auto as_complex = PyComplex_AsCComplex(v);
643 *out = complex128(as_complex.real, as_complex.imag);
644 return nullptr;
645 }
646 double as_double;
647 auto error = ConvertOneFloat<double>(v, &as_double);
648 if (error != nullptr) return error;
649 *out = complex128(as_double, 0.0);
650 return nullptr;
651 }
652 };
653
654 typedef Converter<complex128> Complex128Converter;
655
656 // Bool support
657
658 template <>
659 struct ConverterTraits<bool> {
CreateScalartensorflow::__anonfc08bad90111::ConverterTraits660 static AbstractTensorInterface* CreateScalar(TFE_Context* ctx, bool value) {
661 return tensorflow::unwrap(ctx)->CreateBoolScalar(value);
662 }
663
CreateTensortensorflow::__anonfc08bad90111::ConverterTraits664 static AbstractTensorInterface* CreateTensor(
665 TFE_Context* ctx, absl::Span<const int64_t> dim_sizes) {
666 return tensorflow::unwrap(ctx)->CreateTensor(DT_BOOL, dim_sizes);
667 }
668
ConvertScalartensorflow::__anonfc08bad90111::ConverterTraits669 static const char* ConvertScalar(PyObject* v, bool* out) {
670 if (v == Py_True) {
671 *out = true;
672 } else if (v == Py_False) {
673 *out = false;
674 } else if (PyIsInstance(v, &PyBoolArrType_Type)) { // NumPy
675 *out = PyObject_IsTrue(v);
676 } else {
677 return ErrorMixedTypes;
678 }
679 return nullptr;
680 }
681 };
682
683 typedef Converter<bool> BoolConverter;
684
685 // Convert a Python numpy.ndarray object to a TFE_TensorHandle.
686 // The two may share underlying storage so changes to one may reflect in the
687 // other.
NumpyToTFE_TensorHandle(TFE_Context * ctx,PyObject * obj)688 TFE_TensorHandle* NumpyToTFE_TensorHandle(TFE_Context* ctx, PyObject* obj) {
689 Safe_TF_TensorPtr tf_tensor = make_safe(static_cast<TF_Tensor*>(nullptr));
690 Status status = tensorflow::NdarrayToTensor(ctx, obj, &tf_tensor);
691
692 if (TF_PREDICT_FALSE(!status.ok())) {
693 PyErr_SetString(PyExc_ValueError,
694 tensorflow::strings::StrCat(
695 "Failed to convert a NumPy array to a Tensor (",
696 status.error_message(), ").")
697 .c_str());
698 return nullptr;
699 }
700
701 return tensorflow::wrap(
702 tensorflow::unwrap(ctx)->CreateLocalHandle(tf_tensor->tensor));
703 }
704
705 } // namespace
706
707 // TODO(b/147743551): This function handles enough conversions to justify
708 // promoting to something like PyObjectToTensorHandle.
709 // TODO(b/147828820): Handle Tensors properly.
PySeqToTFE_TensorHandle(TFE_Context * ctx,PyObject * obj,DataType dtype)710 TFE_TensorHandle* PySeqToTFE_TensorHandle(TFE_Context* ctx, PyObject* obj,
711 DataType dtype) {
712 // Shortcut: __array__ objects (such as Pandas data frames).
713 // These objects are efficiently handled by Numpy. We transform them into
714 // Numpy arrays and handle them in the Numpy case below. Note that Tensors
715 // implement the __array__ function, and will be handled in this shortcut.
716 Safe_PyObjectPtr array =
717 make_safe(PyArray_FromArrayAttr(obj, nullptr, nullptr));
718 if (array == nullptr) {
719 return nullptr;
720 }
721 if (array.get() == Py_NotImplemented) {
722 // The Py_NotImplemented returned from PyArray_FromArrayAttr is not
723 // Py_INCREF'ed, so we don't want the Safe_PyObjectPtr to Py_DECREF it.
724 array.release();
725 } else {
726 // PyArray_FromArrayAttr ensures that `array` is a PyArrayObject, so all
727 // we have to do is replace `obj` with it and continue.
728 obj = array.get();
729 }
730
731 // Shortcut: Numpy arrays.
732 if (PyArray_Check(obj)) {
733 int desired_np_dtype = -1;
734 if (dtype != tensorflow::DT_INVALID) {
735 if (!tensorflow::TF_DataType_to_PyArray_TYPE(
736 static_cast<TF_DataType>(dtype), &desired_np_dtype)
737 .ok()) {
738 PyErr_SetString(
739 PyExc_TypeError,
740 tensorflow::strings::StrCat("Invalid dtype argument value ", dtype)
741 .c_str());
742 return nullptr;
743 }
744 }
745
746 PyArrayObject* array = reinterpret_cast<PyArrayObject*>(obj);
747 int array_dtype = PyArray_TYPE(array);
748
749 Safe_PyObjectPtr safe_value(nullptr);
750 // Use Numpy to convert between types if needed.
751 if ((desired_np_dtype >= 0 && desired_np_dtype != array_dtype) ||
752 !PyArray_ISCARRAY(array)) {
753 int new_dtype = desired_np_dtype >= 0 ? desired_np_dtype : array_dtype;
754 safe_value = tensorflow::make_safe(
755 PyArray_FromAny(obj, PyArray_DescrFromType(new_dtype), 0, 0,
756 NPY_ARRAY_CARRAY_RO | NPY_ARRAY_FORCECAST, nullptr));
757 if (PyErr_Occurred()) return nullptr;
758 if (safe_value == nullptr) {
759 PyErr_SetString(PyExc_ValueError, "Error while casting a numpy value");
760 }
761 obj = safe_value.get();
762 }
763 return NumpyToTFE_TensorHandle(ctx, obj);
764 }
765
766 ConverterState state;
767 Status status = InferShapeAndType(obj, &state);
768 if (!status.ok()) {
769 PyErr_SetString(PyExc_ValueError, status.error_message().c_str());
770 return nullptr;
771 }
772 DataType requested_dtype = DT_INVALID;
773 if (dtype != DT_INVALID) {
774 requested_dtype = dtype;
775 }
776
777 // NOTE(josh11b): If don't successfully convert to the requested type,
778 // we just try instead to create a tensor of the inferred type and
779 // let the caller convert it to the requested type using a cast
780 // operation.
781 const char* error = nullptr;
782 TFE_TensorHandle* handle = nullptr;
783 status = errors::Unimplemented("Missing Python -> Tensor conversion for ",
784 DataTypeString(state.inferred_dtype));
785 switch (requested_dtype) {
786 case DT_FLOAT:
787 status = FloatConverter::Convert(ctx, obj, &state, &handle, &error);
788 break;
789
790 case DT_DOUBLE:
791 status = DoubleConverter::Convert(ctx, obj, &state, &handle, &error);
792 break;
793
794 case DT_HALF:
795 status = NumpyHalfConverter::Convert(ctx, obj, &state, &handle, &error);
796 break;
797
798 case DT_INT64:
799 status = Int64Converter::Convert(ctx, obj, &state, &handle, &error);
800 break;
801
802 case DT_INT32:
803 status = Int32Converter::Convert(ctx, obj, &state, &handle, &error);
804 break;
805
806 case DT_UINT64:
807 status = UInt64Converter::Convert(ctx, obj, &state, &handle, &error);
808 break;
809
810 case DT_COMPLEX128:
811 status = Complex128Converter::Convert(ctx, obj, &state, &handle, &error);
812 break;
813
814 case DT_STRING:
815 status = StringConverter::Convert(ctx, obj, &state, &handle, &error);
816 break;
817
818 case DT_BOOL:
819 status = BoolConverter::Convert(ctx, obj, &state, &handle, &error);
820 break;
821
822 default:
823 break;
824 }
825 if (status.ok()) return handle;
826
827 switch (state.inferred_dtype) {
828 case DT_FLOAT:
829 // TODO(josh11b): Handle mixed floats and complex numbers?
830 if (requested_dtype == DT_INVALID) {
831 // TensorFlow uses float32s to represent floating point numbers
832 // by default (for space and speed over using doubles).
833 status = FloatConverter::Convert(ctx, obj, &state, &handle, &error);
834 } else {
835 // We are going to do a cast to the user's requested dtype
836 // after this. We use doubles for this intermediate result so
837 // we don't lose precision that might be representable in the
838 // final type.
839 status = DoubleConverter::Convert(ctx, obj, &state, &handle, &error);
840 }
841 break;
842
843 case DT_DOUBLE:
844 status = DoubleConverter::Convert(ctx, obj, &state, &handle, &error);
845 break;
846
847 case DT_HALF:
848 status = NumpyHalfConverter::Convert(ctx, obj, &state, &handle, &error);
849 break;
850
851 case DT_INT64:
852 if (requested_dtype == DT_INVALID) {
853 status = Int32Converter::Convert(ctx, obj, &state, &handle, &error);
854 if (error == ErrorFoundInt64) {
855 status = Int64Converter::Convert(ctx, obj, &state, &handle, &error);
856 }
857 if (error == ErrorFoundFloat) {
858 status = FloatConverter::Convert(ctx, obj, &state, &handle, &error);
859 }
860 // TODO(josh11b): May also want to fall back to using doubles if
861 // error == ErrorOutOfRange?
862 } else {
863 status = Int64Converter::Convert(ctx, obj, &state, &handle, &error);
864 if (error == ErrorFoundFloat) {
865 status = DoubleConverter::Convert(ctx, obj, &state, &handle, &error);
866 }
867 }
868 break;
869
870 case DT_STRING:
871 status = StringConverter::Convert(ctx, obj, &state, &handle, &error);
872 break;
873
874 case DT_COMPLEX128:
875 status = Complex128Converter::Convert(ctx, obj, &state, &handle, &error);
876 break;
877
878 case DT_BOOL:
879 status = BoolConverter::Convert(ctx, obj, &state, &handle, &error);
880 break;
881
882 case DT_INVALID: // Only occurs for empty tensors.
883 {
884 AbstractTensorInterface* t = tensorflow::unwrap(ctx)->CreateTensor(
885 requested_dtype == DT_INVALID ? DT_FLOAT : requested_dtype,
886 state.inferred_shape);
887 auto* result =
888 tensorflow::wrap(tensorflow::unwrap(ctx)->CreateLocalHandle(t));
889 t->Release();
890 return result;
891 }
892
893 default:
894 break;
895 }
896
897 if (!status.ok()) {
898 PyErr_SetString(PyExc_ValueError, status.error_message().c_str());
899 return nullptr;
900 }
901
902 return handle;
903 }
904
905 } // namespace tensorflow
906