@@ -1400,6 +1400,7 @@ extension PythonObject : Sequence {
14001400 }
14011401}
14021402
1403+
14031404//===----------------------------------------------------------------------===//
14041405// `ExpressibleByLiteral` conformances
14051406//===----------------------------------------------------------------------===//
@@ -1517,3 +1518,139 @@ public struct PythonBytes : PythonConvertible, ConvertibleFromPython, Hashable {
15171518 }
15181519 }
15191520}
1521+
1522+ //===----------------------------------------------------------------------===//
1523+ // PythonFunction - create functions in Swift that can be called from Python
1524+ //===----------------------------------------------------------------------===//
1525+
1526+ /// Create functions in Swift that can be called from Python
1527+ ///
1528+ /// Example:
1529+ ///
1530+ /// The Python code `map(lambda(x: x * 2), [10, 12, 14])` would be written as:
1531+ ///
1532+ /// Python.map(PythonFunction { x in x * 2 }, [10, 12, 14]) // [20, 24, 28]
1533+ ///
1534+ final public class PythonFunction {
1535+ /// Called directly by the Python C API
1536+ private let callSwiftFunction : ( _ argumentsTuple: PythonObject ) throws -> PythonConvertible
1537+
1538+ public init ( _ fn: @escaping ( PythonObject ) throws -> PythonConvertible ) {
1539+ self . callSwiftFunction = { argumentsAsTuple in
1540+ return try fn ( argumentsAsTuple [ 0 ] )
1541+ }
1542+ }
1543+
1544+ /// For cases where the Swift function should accept more (or less) than one parameter, accept an ordered array of all arguments instead
1545+ public init ( _ fn: @escaping ( [ PythonObject ] ) throws -> PythonConvertible ) {
1546+ self . callSwiftFunction = { argumentsAsTuple in
1547+ return try fn ( argumentsAsTuple. map { $0 } )
1548+ }
1549+ }
1550+ }
1551+
1552+ extension PythonFunction : PythonConvertible {
1553+ public var pythonObject : PythonObject {
1554+ _ = Python // Ensure Python is initialized.
1555+
1556+ // FIXME: Memory management issue:
1557+ // It is necessary to pass a retained reference to `PythonFunction` so that it
1558+ // outlives the `PyReference` of the PyCFunction we create below. If we don't,
1559+ // Python tries to access what then has become a garbage pointer when it cleans
1560+ // up the CFunction. This means the entire `PythonFunction` currently leaks.
1561+ let selfPointer = Unmanaged . passRetained ( self ) . toOpaque ( )
1562+
1563+ let fnPointer = PyCFunction_New (
1564+ PythonFunction . sharedMethodDefinition,
1565+ selfPointer
1566+ )
1567+
1568+ // FIXME: Another potential memory management issue.
1569+ // I can't see how to stop these functions from being prematurely
1570+ // garbage collected unless we untrack it from the Python GC.
1571+ PyObject_GC_UnTrack ( fnPointer)
1572+
1573+ return PythonObject ( fnPointer)
1574+ }
1575+ }
1576+
1577+ // The pointers here technically constitute a leak, but no more than
1578+ // a static string or a static struct definition at top level.
1579+ fileprivate extension PythonFunction {
1580+ static let sharedFunctionName : UnsafePointer < Int8 > = {
1581+ let name = " pythonkit_swift_function "
1582+ let cString = name. utf8CString
1583+ let copy = UnsafeMutableBufferPointer< Int8> . allocate( capacity: cString. count)
1584+ _ = copy. initialize ( from: cString)
1585+ return UnsafePointer ( copy. baseAddress!)
1586+ } ( )
1587+
1588+ static let sharedMethodDefinition : UnsafeMutablePointer < PyMethodDef > = {
1589+ /// The standard calling convention. See Python C API docs
1590+ let METH_VARARGS = 1 as Int32
1591+
1592+ let pointer = UnsafeMutablePointer< PyMethodDef> . allocate( capacity: 1 )
1593+ pointer. pointee = PyMethodDef (
1594+ ml_name: PythonFunction . sharedFunctionName,
1595+ ml_meth: PythonFunction . sharedMethodImplementation,
1596+ ml_flags: METH_VARARGS,
1597+ ml_doc: nil
1598+ )
1599+
1600+ return pointer
1601+ } ( )
1602+
1603+ private static let sharedMethodImplementation : @convention ( c) ( PyObjectPointer ? , PyObjectPointer ? ) -> PyObjectPointer ? = { context, argumentsPointer in
1604+ guard let argumentsPointer = argumentsPointer, let selfPointer = context else {
1605+ return nil
1606+ }
1607+
1608+ let `self` = Unmanaged < PythonFunction > . fromOpaque ( selfPointer) . takeUnretainedValue ( )
1609+
1610+ do {
1611+ let argumentsAsTuple = PythonObject ( consuming: argumentsPointer)
1612+ return try self . callSwiftFunction ( argumentsAsTuple) . ownedPyObject
1613+ } catch {
1614+ PythonFunction . setPythonError ( swiftError: error)
1615+ return nil // This must only be `nil` if an exception has been set
1616+ }
1617+ }
1618+
1619+ private static func setPythonError( swiftError: Error ) {
1620+ if let pythonObject = swiftError as? PythonObject {
1621+ if Bool ( Python . isinstance ( pythonObject, Python . BaseException) ) ! {
1622+ // We are an instance of an Exception class type. Set the exception class to the object's type:
1623+ PyErr_SetString ( Python . type ( pythonObject) . ownedPyObject, pythonObject. description)
1624+ } else {
1625+ // Assume an actual class type was thrown (rather than an instance)
1626+ // Crashes if it was neither a subclass of BaseException nor an instance of one.
1627+ //
1628+ // We *could* check to see whether `pythonObject` is a class here and fall back
1629+ // to the default case of setting a generic Exception, below, but we also want
1630+ // people to write valid code.
1631+ PyErr_SetString ( pythonObject. ownedPyObject, pythonObject. description)
1632+ }
1633+ } else {
1634+ // Make a generic Python Exception based on the Swift Error:
1635+ PyErr_SetString ( Python . Exception. ownedPyObject, " \( type ( of: swiftError) ) raised in Swift: \( swiftError) " )
1636+ }
1637+ }
1638+ }
1639+
1640+ extension PythonObject : Error { }
1641+
1642+ // From Python's C Headers:
1643+ struct PyMethodDef {
1644+ /// The name of the built-in function/method
1645+ public var ml_name : UnsafePointer < Int8 >
1646+
1647+ /// The C function that implements it
1648+ public var ml_meth : @convention ( c) ( PyObjectPointer ? , PyObjectPointer ? ) -> PyObjectPointer ?
1649+
1650+ /// Combination of METH_xxx flags, which mostly describe the args expected by the C func
1651+ public var ml_flags : Int32
1652+
1653+ /// The __doc__ attribute, or NULL
1654+ public var ml_doc : UnsafePointer < Int8 > ?
1655+ }
1656+ >>>>>>> Squashed commit of the following:
0 commit comments