diff --git a/stdlib/public/Python/Python.swift b/stdlib/public/Python/Python.swift index 62253e68752db..4ac12a86d8a0c 100644 --- a/stdlib/public/Python/Python.swift +++ b/stdlib/public/Python/Python.swift @@ -147,14 +147,20 @@ extension PythonObject : CustomReflectable { //===----------------------------------------------------------------------===// public protocol PythonConvertible { + /// A `PythonObject` instance representing this value. + var pythonObject: PythonObject { get } +} + +//===----------------------------------------------------------------------===// +// `PythonConvertible` protocol +//===----------------------------------------------------------------------===// + +public protocol ConvertibleFromPython { /// Creates a new instance from the given `PythonObject`, if possible. /// - Note: Conversion may fail if the given `PythonObject` instance is /// incompatible (e.g. a Python `string` object cannot be converted into an /// `Int`). init?(_ object: PythonObject) - - /// A `PythonObject` instance representing this value. - var pythonObject: PythonObject { get } } public extension PythonObject { @@ -177,7 +183,7 @@ fileprivate extension PythonConvertible { } // `PythonObject` is trivially `PythonConvertible`. -extension PythonObject : PythonConvertible { +extension PythonObject : PythonConvertible, ConvertibleFromPython { public init(_ object: PythonObject) { self.init(consuming: object.ownedPyObject) } @@ -747,7 +753,7 @@ private func isType(_ object: PythonObject, return pyObject != _Py_ZeroStruct } -extension Bool : PythonConvertible { +extension Bool : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard isType(pythonObject, type: PyBool_Type) else { return nil } @@ -763,7 +769,7 @@ extension Bool : PythonConvertible { } } -extension String : PythonConvertible { +extension String : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { let pyObject = pythonObject.ownedPyObject defer { Py_DecRef(pyObject) } @@ -808,7 +814,7 @@ fileprivate extension PythonObject { } } -extension Int : PythonConvertible { +extension Int : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { // `PyInt_AsLong` return -1 and sets an error if the Python object is not // integer compatible. @@ -825,7 +831,7 @@ extension Int : PythonConvertible { } } -extension UInt : PythonConvertible { +extension UInt : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { // `PyInt_AsUnsignedLongMask` isn't documented as such, but in fact it does // return -1 and set an error if the Python object is not integer @@ -843,7 +849,7 @@ extension UInt : PythonConvertible { } } -extension Double : PythonConvertible { +extension Double : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { // `PyFloat_AsDouble` return -1 and sets an error if the Python object is // not float compatible. @@ -867,7 +873,7 @@ extension Double : PythonConvertible { // Any `FixedWidthInteger` type is `PythonConvertible` via the `Int`/`UInt` // implementation. -extension Int8 : PythonConvertible { +extension Int8 : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard let i = Int(pythonObject) else { return nil } self.init(i) @@ -878,7 +884,7 @@ extension Int8 : PythonConvertible { } } -extension Int16 : PythonConvertible { +extension Int16 : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard let i = Int(pythonObject) else { return nil } self.init(i) @@ -889,7 +895,7 @@ extension Int16 : PythonConvertible { } } -extension Int32 : PythonConvertible { +extension Int32 : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard let i = Int(pythonObject) else { return nil } self.init(i) @@ -900,7 +906,7 @@ extension Int32 : PythonConvertible { } } -extension Int64 : PythonConvertible { +extension Int64 : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard let i = Int(pythonObject) else { return nil } self.init(i) @@ -911,7 +917,7 @@ extension Int64 : PythonConvertible { } } -extension UInt8 : PythonConvertible { +extension UInt8 : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard let i = UInt(pythonObject) else { return nil } self.init(i) @@ -922,7 +928,7 @@ extension UInt8 : PythonConvertible { } } -extension UInt16 : PythonConvertible { +extension UInt16 : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard let i = UInt(pythonObject) else { return nil } self.init(i) @@ -933,7 +939,7 @@ extension UInt16 : PythonConvertible { } } -extension UInt32 : PythonConvertible { +extension UInt32 : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard let i = UInt(pythonObject) else { return nil } self.init(i) @@ -944,7 +950,7 @@ extension UInt32 : PythonConvertible { } } -extension UInt64 : PythonConvertible { +extension UInt64 : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard let i = UInt(pythonObject) else { return nil } self.init(i) @@ -957,7 +963,7 @@ extension UInt64 : PythonConvertible { // `Float` is `PythonConvertible` via the `Double` implementation. -extension Float : PythonConvertible { +extension Float : PythonConvertible, ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard let v = Double(pythonObject) else { return nil } self.init(v) @@ -973,6 +979,17 @@ extension Float : PythonConvertible { //===----------------------------------------------------------------------===// extension Optional : PythonConvertible where Wrapped : PythonConvertible { + public var pythonObject: PythonObject { + return self?.pythonObject ?? Python.None + } +} + +//===----------------------------------------------------------------------===// +// `ConvertibleFromPython` conformance for `Optional` +//===----------------------------------------------------------------------===// + +extension Optional : ConvertibleFromPython + where Wrapped : ConvertibleFromPython { public init?(_ object: PythonObject) { if object == Python.None { self = .none @@ -983,27 +1000,16 @@ extension Optional : PythonConvertible where Wrapped : PythonConvertible { self = .some(converted) } } - - public var pythonObject: PythonObject { - return self?.pythonObject ?? Python.None - } } //===----------------------------------------------------------------------===// -// `PythonConvertible` conformance for `Array` and `Dictionary` +// `PythonConvertible` and `ConvertibleFromPython conformance for +// `Array` and `Dictionary` //===----------------------------------------------------------------------===// // `Array` conditionally conforms to `PythonConvertible` if the `Element` // associated type does. extension Array : PythonConvertible where Element : PythonConvertible { - public init?(_ pythonObject: PythonObject) { - self = [] - for elementObject in pythonObject { - guard let element = Element(elementObject) else { return nil } - append(element) - } - } - public var pythonObject: PythonObject { _ = Python // Ensure Python is initialized. let list = PyList_New(count)! @@ -1015,10 +1021,36 @@ extension Array : PythonConvertible where Element : PythonConvertible { } } +extension Array : ConvertibleFromPython where Element : ConvertibleFromPython { + public init?(_ pythonObject: PythonObject) { + self = [] + for elementObject in pythonObject { + guard let element = Element(elementObject) else { return nil } + append(element) + } + } +} + // `Dictionary` conditionally conforms to `PythonConvertible` if the `Key` and // `Value` associated types do. extension Dictionary : PythonConvertible where Key : PythonConvertible, Value : PythonConvertible { + public var pythonObject: PythonObject { + _ = Python // Ensure Python is initialized. + let dict = PyDict_New()! + for (key, value) in self { + let k = key.ownedPyObject + let v = value.ownedPyObject + PyDict_SetItem(dict, k, v) + Py_DecRef(k) + Py_DecRef(v) + } + return PythonObject(consuming: dict) + } +} + +extension Dictionary : ConvertibleFromPython + where Key : ConvertibleFromPython, Value : ConvertibleFromPython { public init?(_ pythonDict: PythonObject) { self = [:] @@ -1042,26 +1074,21 @@ extension Dictionary : PythonConvertible } } } - - public var pythonObject: PythonObject { - _ = Python // Ensure Python is initialized. - let dict = PyDict_New()! - for (key, value) in self { - let k = key.ownedPyObject - let v = value.ownedPyObject - PyDict_SetItem(dict, k, v) - Py_DecRef(k) - Py_DecRef(v) - } - return PythonObject(consuming: dict) - } } //===----------------------------------------------------------------------===// -// `PythonConvertible` conformance for `Range` types +// `PythonConvertible` and `ConvertibleFromPython` conformances +// for `Range` types //===----------------------------------------------------------------------===// extension Range : PythonConvertible where Bound : PythonConvertible { + public var pythonObject: PythonObject { + _ = Python // Ensure Python is initialized. + return Python.slice(lowerBound, upperBound, Python.None) + } +} + +extension Range : ConvertibleFromPython where Bound : ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard isType(pythonObject, type: PySlice_Type) else { return nil } guard let lowerBound = Bound(pythonObject.start), @@ -1071,14 +1098,17 @@ extension Range : PythonConvertible where Bound : PythonConvertible { guard pythonObject.step == Python.None else { return nil } self.init(uncheckedBounds: (lowerBound, upperBound)) } +} +extension PartialRangeFrom : PythonConvertible where Bound : PythonConvertible { public var pythonObject: PythonObject { _ = Python // Ensure Python is initialized. - return Python.slice(lowerBound, upperBound, Python.None) + return Python.slice(lowerBound, Python.None, Python.None) } } -extension PartialRangeFrom : PythonConvertible where Bound : PythonConvertible { +extension PartialRangeFrom : ConvertibleFromPython + where Bound : ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard isType(pythonObject, type: PySlice_Type) else { return nil } guard let lowerBound = Bound(pythonObject.start) else { return nil } @@ -1088,14 +1118,17 @@ extension PartialRangeFrom : PythonConvertible where Bound : PythonConvertible { } self.init(lowerBound) } +} +extension PartialRangeUpTo : PythonConvertible where Bound : PythonConvertible { public var pythonObject: PythonObject { _ = Python // Ensure Python is initialized. - return Python.slice(lowerBound, Python.None, Python.None) + return Python.slice(Python.None, upperBound, Python.None) } } -extension PartialRangeUpTo : PythonConvertible where Bound : PythonConvertible { +extension PartialRangeUpTo : ConvertibleFromPython + where Bound : ConvertibleFromPython { public init?(_ pythonObject: PythonObject) { guard isType(pythonObject, type: PySlice_Type) else { return nil } guard let upperBound = Bound(pythonObject.stop) else { return nil } @@ -1105,11 +1138,6 @@ extension PartialRangeUpTo : PythonConvertible where Bound : PythonConvertible { } self.init(upperBound) } - - public var pythonObject: PythonObject { - _ = Python // Ensure Python is initialized. - return Python.slice(Python.None, upperBound, Python.None) - } } //===----------------------------------------------------------------------===// diff --git a/stdlib/public/TensorFlow/CMakeLists.txt b/stdlib/public/TensorFlow/CMakeLists.txt index f099d82a44859..0c363c4bcfc1d 100644 --- a/stdlib/public/TensorFlow/CMakeLists.txt +++ b/stdlib/public/TensorFlow/CMakeLists.txt @@ -50,7 +50,7 @@ set(SOURCES Utilities.swift Threading.swift # NumPy bridging for `ShapedArray` and `Tensor`. - NumpyConversion.swift) + PythonConversion.swift) # Copy TensorFlow bindings file, if it exists. if (TENSORFLOW_SWIFT_BINDINGS) @@ -65,7 +65,7 @@ if (TENSORFLOW_SWIFT_APIS) list(APPEND SOURCES "${TENSORFLOW_SWIFT_API_SOURCES}") endif() -# When Python exists, NumpyConversion.swift imports it, so it must be +# When Python exists, PythonConversion.swift imports it, so it must be # available at link time. set(TENSORFLOW_DEPENDS_PYTHON) if (SWIFT_PYTHON_EXISTS) diff --git a/stdlib/public/TensorFlow/NumpyConversion.swift b/stdlib/public/TensorFlow/PythonConversion.swift similarity index 95% rename from stdlib/public/TensorFlow/NumpyConversion.swift rename to stdlib/public/TensorFlow/PythonConversion.swift index f9dd0fd8fde2c..3a32b6e86cc2c 100644 --- a/stdlib/public/TensorFlow/NumpyConversion.swift +++ b/stdlib/public/TensorFlow/PythonConversion.swift @@ -1,4 +1,4 @@ -//===-- NumpyConversion.swift ---------------------------------*- swift -*-===// +//===-- PythonConversion.swift --------------------------------*- swift -*-===// // // This source file is part of the Swift.org open source project // @@ -10,8 +10,7 @@ // //===----------------------------------------------------------------------===// // -// This file defines conversion initializers from `numpy.ndarray` to -// `ShapedArray` and `Tensor`. +// This file defines conversions between Python types & custom TensorFlow types. // //===----------------------------------------------------------------------===// @@ -164,4 +163,10 @@ extension Tensor where Scalar : NumpyScalarCompatible { public func makeNumpyArray() -> PythonObject { return array.makeNumpyArray() } } +extension TensorShape : PythonConvertible { + public var pythonObject: PythonObject { + return dimensions.pythonObject + } +} + #endif // canImport(Python) diff --git a/stdlib/public/TensorFlow/TensorShape.swift b/stdlib/public/TensorFlow/TensorShape.swift index 68a16136f30bd..7209e4d01a275 100644 --- a/stdlib/public/TensorFlow/TensorShape.swift +++ b/stdlib/public/TensorFlow/TensorShape.swift @@ -10,8 +10,6 @@ // //===----------------------------------------------------------------------===// -import Python - // NOTE: it may be possible to edit `TensorShape` to support "labeled tensors". // Dimensions may be either an Int32 or an enum representing a label. @@ -160,20 +158,3 @@ extension TensorShape : Codable { self.init(dimensions) } } - -extension TensorShape : PythonConvertible { - public var pythonObject: PythonObject { - return dimensions.pythonObject - } - - public init?(_ pythonObject: PythonObject) { - let hasLen = Bool(Python.hasattr(pythonObject, "__len__")) - if(hasLen == true) { - guard let array = [Int32](pythonObject) else { return nil } - self.init(array) - } else { - guard let num = Int32(pythonObject) else { return nil } - self.init(num) - } - } -} diff --git a/test/TensorFlow/integration.swift b/test/TensorFlow/integration.swift index ed1fd83063c1a..b8714b8825547 100644 --- a/test/TensorFlow/integration.swift +++ b/test/TensorFlow/integration.swift @@ -377,7 +377,7 @@ public func testResourceAndVariants() { // expected-error @+1 {{op named 'TensorDataSet' is not registered in TensorFlow}} #tfop("TensorDataSet", values, Toutput_types$dtype: [Float.tensorFlowDataType], - output_shapes: [TensorShape([1])]) + output_shapes: [TensorShape(1)]) // REGISTER_OP("Iterator") // .Output("handle: resource") diff --git a/test/TensorFlowRuntime/numpy_conversion.swift b/test/TensorFlowRuntime/python_conversion.swift similarity index 91% rename from test/TensorFlowRuntime/numpy_conversion.swift rename to test/TensorFlowRuntime/python_conversion.swift index b27b7002d3009..fbba5e496e2b2 100644 --- a/test/TensorFlowRuntime/numpy_conversion.swift +++ b/test/TensorFlowRuntime/python_conversion.swift @@ -1,15 +1,14 @@ // RUN: %target-run-eager-swift %swift-tensorflow-test-run-extra-options // RUN: %target-run-gpe-swift %swift-tensorflow-test-run-extra-options // REQUIRES: executable_test -// REQUIRES: swift_test_mode_optimize // REQUIRES: tensorflow // -// `numpy.ndarray` conversion tests. +// Python conversion and `numpy.ndarray` tests. import TensorFlow import StdlibUnittest -var NumpyConversionTests = TestSuite("NumpyConversion") +var PythonConversionTests = TestSuite("PythonConversion") // TODO: Add `python` as a lit feature so this test can use "REQUIRE: python" // instead of `#if canImport(Python)`. @@ -20,7 +19,7 @@ import Python let numpyModule = try? Python.attemptImport("numpy") let ctypesModule = try? Python.attemptImport("ctypes") -NumpyConversionTests.test("shaped-array-conversion") { +PythonConversionTests.test("shaped-array-conversion") { guard let np = numpyModule else { return } let numpyArrayEmpty = np.array([[]] as [[Float]], dtype: np.float32) @@ -60,9 +59,9 @@ NumpyConversionTests.test("shaped-array-conversion") { let numpyArray1D = np.ones(28) let reshaped3D = np.reshape(numpyArray1D, [2, 7, 2] as TensorShape) - expectEqual(TensorShape(reshaped3D.shape), [2, 7, 2]) + expectEqual(reshaped3D.shape, Python.tuple([2, 7, 2])) let reshaped2D = np.reshape(reshaped3D, [14, 2] as TensorShape) - expectEqual(TensorShape(reshaped2D.shape), [14, 2]) + expectEqual(reshaped2D.shape, Python.tuple([14, 2])) let numpyArrayStrided = np.array([[1, 2], [1, 2]], dtype: np.int32)[ Python.slice(Python.None), 1] @@ -75,7 +74,7 @@ NumpyConversionTests.test("shaped-array-conversion") { } } -NumpyConversionTests.test("tensor-conversion") { +PythonConversionTests.test("tensor-conversion") { guard let np = numpyModule else { return } let numpyArrayEmpty = np.array([[]] as [[Float]], dtype: np.float32) @@ -123,7 +122,7 @@ NumpyConversionTests.test("tensor-conversion") { } } -NumpyConversionTests.test("tensor-round-trip") { +PythonConversionTests.test("tensor-round-trip") { guard numpyModule != nil else { return } guard ctypesModule != nil else { return } @@ -137,10 +136,9 @@ NumpyConversionTests.test("tensor-round-trip") { expectEqual(t3, Tensor(numpy: t3.makeNumpyArray())!) } -NumpyConversionTests.test("tensor-shape") { +PythonConversionTests.test("tensor-shape") { let pyArray = [2, 3].pythonObject expectEqual(pyArray, TensorShape(2, 3).pythonObject) - expectEqual(TensorShape(2, 3), TensorShape(pyArray)) } #endif