@@ -322,6 +322,21 @@ public struct ThrowingPythonObject {
322322 public func dynamicallyCall(
323323 withKeywordArguments args:
324324 KeyValuePairs < String , PythonConvertible > = [ : ] ) throws -> PythonObject {
325+ return try _dynamicallyCall ( args)
326+ }
327+
328+ /// Alias for the function above that lets the caller dynamically construct the argument list, without using a dictionary literal.
329+ /// This function must be called explicitly on a `PythonObject` because `@dynamicCallable` does not recognize it.
330+ @discardableResult
331+ public func dynamicallyCall(
332+ withKeywordArguments args:
333+ [ ( key: String , value: PythonConvertible ) ] = [ ] ) throws -> PythonObject {
334+ return try _dynamicallyCall ( args)
335+ }
336+
337+ /// Implementation of `dynamicallyCall(withKeywordArguments)`.
338+ private func _dynamicallyCall< T : Collection > ( _ args: T ) throws -> PythonObject
339+ where T. Element == ( key: String , value: PythonConvertible ) {
325340 try throwPythonErrorIfPresent ( )
326341
327342 // An array containing positional arguments.
@@ -615,6 +630,15 @@ public extension PythonObject {
615630 KeyValuePairs < String , PythonConvertible > = [ : ] ) -> PythonObject {
616631 return try ! throwing. dynamicallyCall ( withKeywordArguments: args)
617632 }
633+
634+ /// Alias for the function above that lets the caller dynamically construct the argument list, without using a dictionary literal.
635+ /// This function must be called explicitly on a `PythonObject` because `@dynamicCallable` does not recognize it.
636+ @discardableResult
637+ func dynamicallyCall(
638+ withKeywordArguments args:
639+ [ ( key: String , value: PythonConvertible ) ] = [ ] ) -> PythonObject {
640+ return try ! throwing. dynamicallyCall ( withKeywordArguments: args)
641+ }
618642}
619643
620644//===----------------------------------------------------------------------===//
@@ -705,14 +729,13 @@ public struct PythonInterface {
705729
706730// Create a Python tuple object with the specified elements.
707731private func pyTuple< T : Collection > ( _ vals: T ) -> OwnedPyObjectPointer
708- where T. Element : PythonConvertible {
709-
710- let tuple = PyTuple_New ( vals. count) !
711- for (index, element) in vals. enumerated ( ) {
712- // `PyTuple_SetItem` steals the reference of the object stored.
713- PyTuple_SetItem ( tuple, index, element. ownedPyObject)
714- }
715- return tuple
732+ where T. Element : PythonConvertible {
733+ let tuple = PyTuple_New ( vals. count) !
734+ for (index, element) in vals. enumerated ( ) {
735+ // `PyTuple_SetItem` steals the reference of the object stored.
736+ PyTuple_SetItem ( tuple, index, element. ownedPyObject)
737+ }
738+ return tuple
716739}
717740
718741public extension PythonObject {
@@ -723,13 +746,13 @@ public extension PythonObject {
723746 }
724747
725748 init < T : Collection > ( tupleContentsOf elements: T )
726- where T. Element == PythonConvertible {
727- self . init ( consuming: pyTuple ( elements. map { $0. pythonObject } ) )
749+ where T. Element == PythonConvertible {
750+ self . init ( consuming: pyTuple ( elements. map { $0. pythonObject } ) )
728751 }
729752
730753 init < T : Collection > ( tupleContentsOf elements: T )
731- where T. Element : PythonConvertible {
732- self . init ( consuming: pyTuple ( elements) )
754+ where T. Element : PythonConvertible {
755+ self . init ( consuming: pyTuple ( elements) )
733756 }
734757}
735758
@@ -1149,7 +1172,7 @@ where Bound : ConvertibleFromPython {
11491172private typealias PythonBinaryOp =
11501173 ( OwnedPyObjectPointer ? , OwnedPyObjectPointer ? ) -> OwnedPyObjectPointer ?
11511174private typealias PythonUnaryOp =
1152- ( OwnedPyObjectPointer ? ) -> OwnedPyObjectPointer ?
1175+ ( OwnedPyObjectPointer ? ) -> OwnedPyObjectPointer ?
11531176
11541177private func performBinaryOp(
11551178 _ op: PythonBinaryOp , lhs: PythonObject , rhs: PythonObject ) -> PythonObject {
@@ -1409,7 +1432,7 @@ extension PythonObject : Sequence {
14091432}
14101433
14111434extension PythonObject {
1412- public var count : Int {
1435+ public var count : Int {
14131436 checking. count!
14141437 }
14151438}
@@ -1545,32 +1568,90 @@ public struct PythonBytes : PythonConvertible, ConvertibleFromPython, Hashable {
15451568/// Python.map(PythonFunction { x in x * 2 }, [10, 12, 14]) // [20, 24, 28]
15461569///
15471570final class PyFunction {
1548- private var callSwiftFunction : ( _ argumentsTuple: PythonObject ) throws -> PythonConvertible
1549- init ( _ callSwiftFunction: @escaping ( _ argumentsTuple: PythonObject ) throws -> PythonConvertible ) {
1550- self . callSwiftFunction = callSwiftFunction
1571+ enum CallingConvention {
1572+ case varArgs
1573+ case varArgsWithKeywords
1574+ }
1575+
1576+ /// Allows `PyFunction` to store Python functions with more than one possible calling convention
1577+ var callingConvention : CallingConvention
1578+
1579+ /// `arguments` is a Python tuple.
1580+ typealias VarArgsFunction = (
1581+ _ arguments: PythonObject ) throws -> PythonConvertible
1582+
1583+ /// `arguments` is a Python tuple.
1584+ /// `keywordArguments` is an OrderedDict in Python 3.6 and later, or a dict otherwise.
1585+ typealias VarArgsWithKeywordsFunction = (
1586+ _ arguments: PythonObject ,
1587+ _ keywordArguments: PythonObject ) throws -> PythonConvertible
1588+
1589+ /// Has the same memory layout as any other function with the Swift calling convention
1590+ private typealias Storage = ( ) throws -> PythonConvertible
1591+
1592+ /// Stores all function pointers in the same stored property. `callAsFunction` casts this into the desired type.
1593+ private var callSwiftFunction : Storage
1594+
1595+ init ( _ callSwiftFunction: @escaping VarArgsFunction ) {
1596+ self . callingConvention = . varArgs
1597+ self . callSwiftFunction = unsafeBitCast ( callSwiftFunction, to: Storage . self)
1598+ }
1599+
1600+ init ( _ callSwiftFunction: @escaping VarArgsWithKeywordsFunction ) {
1601+ self . callingConvention = . varArgsWithKeywords
1602+ self . callSwiftFunction = unsafeBitCast ( callSwiftFunction, to: Storage . self)
1603+ }
1604+
1605+ private func checkConvention( _ calledConvention: CallingConvention ) {
1606+ precondition ( callingConvention == calledConvention,
1607+ " Called PyFunction with convention \( calledConvention) , but expected \( callingConvention) " )
15511608 }
1609+
15521610 func callAsFunction( _ argumentsTuple: PythonObject ) throws -> PythonConvertible {
1553- try callSwiftFunction ( argumentsTuple)
1611+ checkConvention ( . varArgs)
1612+ let callSwiftFunction = unsafeBitCast ( self . callSwiftFunction, to: VarArgsFunction . self)
1613+ return try callSwiftFunction ( argumentsTuple)
1614+ }
1615+
1616+ func callAsFunction( _ argumentsTuple: PythonObject , _ keywordArguments: PythonObject ) throws -> PythonConvertible {
1617+ checkConvention ( . varArgsWithKeywords)
1618+ let callSwiftFunction = unsafeBitCast ( self . callSwiftFunction, to: VarArgsWithKeywordsFunction . self)
1619+ return try callSwiftFunction ( argumentsTuple, keywordArguments)
15541620 }
15551621}
15561622
15571623public struct PythonFunction {
15581624 /// Called directly by the Python C API
15591625 private var function : PyFunction
1560-
1626+
15611627 @_disfavoredOverload
15621628 public init ( _ fn: @escaping ( PythonObject ) throws -> PythonConvertible ) {
15631629 function = PyFunction { argumentsAsTuple in
15641630 return try fn ( argumentsAsTuple [ 0 ] )
15651631 }
15661632 }
1567-
1568- /// For cases where the Swift function should accept more (or less) than one parameter, accept an ordered array of all arguments instead
1633+
1634+ /// For cases where the Swift function should accept more (or less) than one parameter, accept an ordered array of all arguments instead.
15691635 public init ( _ fn: @escaping ( [ PythonObject ] ) throws -> PythonConvertible ) {
15701636 function = PyFunction { argumentsAsTuple in
15711637 return try fn ( argumentsAsTuple. map { $0 } )
15721638 }
15731639 }
1640+
1641+ /// For cases where the Swift function should accept keyword arguments as `**kwargs` in Python.
1642+ /// `**kwargs` must preserve order from Python 3.6 onward, similarly to
1643+ /// Swift `KeyValuePairs` and unlike `Dictionary`. `KeyValuePairs` cannot be
1644+ /// mutated, so the next best solution is to use `[KeyValuePairs.Element]`.
1645+ public init ( _ fn: @escaping ( [ PythonObject ] , [ ( key: String , value: PythonObject ) ] ) throws -> PythonConvertible ) {
1646+ function = PyFunction { argumentsAsTuple, keywordArgumentsAsDictionary in
1647+ var kwargs : [ ( String , PythonObject ) ] = [ ]
1648+ for keyAndValue in keywordArgumentsAsDictionary. items ( ) {
1649+ let ( key, value) = keyAndValue. tuple2
1650+ kwargs. append ( ( String ( key) !, value) )
1651+ }
1652+ return try fn ( argumentsAsTuple. map { $0 } , kwargs)
1653+ }
1654+ }
15741655}
15751656
15761657extension PythonFunction : PythonConvertible {
@@ -1582,14 +1663,26 @@ extension PythonFunction : PythonConvertible {
15821663 fatalError ( " PythonFunction only supports Python 3.1 and above. " )
15831664 }
15841665
1585- let funcPointer = Unmanaged . passRetained ( function) . toOpaque ( )
1586- let capsulePointer = PyCapsule_New ( funcPointer, nil , { capsulePointer in
1666+ let destructor : @convention ( c) ( PyObjectPointer ? ) -> Void = { capsulePointer in
15871667 let funcPointer = PyCapsule_GetPointer ( capsulePointer, nil )
15881668 Unmanaged < PyFunction > . fromOpaque ( funcPointer) . release ( )
1589- } )
1669+ }
1670+ let funcPointer = Unmanaged . passRetained ( function) . toOpaque ( )
1671+ let capsulePointer = PyCapsule_New (
1672+ funcPointer,
1673+ nil ,
1674+ unsafeBitCast ( destructor, to: OpaquePointer . self)
1675+ )
15901676
1677+ var methodDefinition : UnsafeMutablePointer < PyMethodDef >
1678+ switch function. callingConvention {
1679+ case . varArgs:
1680+ methodDefinition = PythonFunction . sharedMethodDefinition
1681+ case . varArgsWithKeywords:
1682+ methodDefinition = PythonFunction . sharedMethodWithKeywordsDefinition
1683+ }
15911684 let pyFuncPointer = PyCFunction_NewEx (
1592- PythonFunction . sharedMethodDefinition ,
1685+ methodDefinition ,
15931686 capsulePointer,
15941687 nil
15951688 )
@@ -1599,28 +1692,54 @@ extension PythonFunction : PythonConvertible {
15991692}
16001693
16011694fileprivate extension PythonFunction {
1602- static let sharedFunctionName : UnsafePointer < Int8 > = {
1695+ static let sharedMethodDefinition : UnsafeMutablePointer < PyMethodDef > = {
16031696 let name : StaticString = " pythonkit_swift_function "
16041697 // `utf8Start` is a property of StaticString, thus, it has a stable pointer.
1605- return UnsafeRawPointer ( name. utf8Start) . assumingMemoryBound ( to: Int8 . self)
1606- } ( )
1698+ let namePointer = UnsafeRawPointer ( name. utf8Start) . assumingMemoryBound ( to: Int8 . self)
1699+
1700+ let methodImplementationPointer = unsafeBitCast (
1701+ PythonFunction . sharedMethodImplementation, to: OpaquePointer . self)
16071702
1608- static let sharedMethodDefinition : UnsafeMutablePointer < PyMethodDef > = {
16091703 /// The standard calling convention. See Python C API docs
1610- let METH_VARARGS = 1 as Int32
1704+ let METH_VARARGS = 0x0001 as Int32
16111705
16121706 let pointer = UnsafeMutablePointer< PyMethodDef> . allocate( capacity: 1 )
16131707 pointer. pointee = PyMethodDef (
1614- ml_name: PythonFunction . sharedFunctionName ,
1615- ml_meth: PythonFunction . sharedMethodImplementation ,
1708+ ml_name: namePointer ,
1709+ ml_meth: methodImplementationPointer ,
16161710 ml_flags: METH_VARARGS,
16171711 ml_doc: nil
16181712 )
16191713
16201714 return pointer
16211715 } ( )
1716+
1717+ static let sharedMethodWithKeywordsDefinition : UnsafeMutablePointer < PyMethodDef > = {
1718+ let name : StaticString = " pythonkit_swift_function_with_keywords "
1719+ // `utf8Start` is a property of StaticString, thus, it has a stable pointer.
1720+ let namePointer = UnsafeRawPointer ( name. utf8Start) . assumingMemoryBound ( to: Int8 . self)
1721+
1722+ let methodImplementationPointer = unsafeBitCast (
1723+ PythonFunction . sharedMethodWithKeywordsImplementation, to: OpaquePointer . self)
1724+
1725+ /// A combination of flags that supports `**kwargs`. See Python C API docs
1726+ let METH_VARARGS = 0x0001 as Int32
1727+ let METH_KEYWORDS = 0x0002 as Int32
1728+
1729+ let pointer = UnsafeMutablePointer< PyMethodDef> . allocate( capacity: 1 )
1730+ pointer. pointee = PyMethodDef (
1731+ ml_name: namePointer,
1732+ ml_meth: methodImplementationPointer,
1733+ ml_flags: METH_VARARGS | METH_KEYWORDS,
1734+ ml_doc: nil
1735+ )
1736+
1737+ return pointer
1738+ } ( )
16221739
1623- private static let sharedMethodImplementation : @convention ( c) ( PyObjectPointer ? , PyObjectPointer ? ) -> PyObjectPointer ? = { context, argumentsPointer in
1740+ private static let sharedMethodImplementation : @convention ( c) (
1741+ PyObjectPointer ? , PyObjectPointer ?
1742+ ) -> PyObjectPointer ? = { context, argumentsPointer in
16241743 guard let argumentsPointer = argumentsPointer, let capsulePointer = context else {
16251744 return nil
16261745 }
@@ -1636,6 +1755,31 @@ fileprivate extension PythonFunction {
16361755 return nil // This must only be `nil` if an exception has been set
16371756 }
16381757 }
1758+
1759+ private static let sharedMethodWithKeywordsImplementation : @convention ( c) (
1760+ PyObjectPointer ? , PyObjectPointer ? , PyObjectPointer ?
1761+ ) -> PyObjectPointer ? = { context, argumentsPointer, keywordArgumentsPointer in
1762+ guard let argumentsPointer = argumentsPointer, let capsulePointer = context else {
1763+ return nil
1764+ }
1765+
1766+ let funcPointer = PyCapsule_GetPointer ( capsulePointer, nil )
1767+ let function = Unmanaged < PyFunction > . fromOpaque ( funcPointer) . takeUnretainedValue ( )
1768+
1769+ do {
1770+ let argumentsAsTuple = PythonObject ( consuming: argumentsPointer)
1771+ var keywordArgumentsAsDictionary : PythonObject
1772+ if let keywordArgumentsPointer = keywordArgumentsPointer {
1773+ keywordArgumentsAsDictionary = PythonObject ( consuming: keywordArgumentsPointer)
1774+ } else {
1775+ keywordArgumentsAsDictionary = [ : ]
1776+ }
1777+ return try function ( argumentsAsTuple, keywordArgumentsAsDictionary) . ownedPyObject
1778+ } catch {
1779+ PythonFunction . setPythonError ( swiftError: error)
1780+ return nil // This must only be `nil` if an exception has been set
1781+ }
1782+ }
16391783
16401784 private static func setPythonError( swiftError: Error ) {
16411785 if let pythonObject = swiftError as? PythonObject {
@@ -1665,8 +1809,9 @@ struct PyMethodDef {
16651809 /// The name of the built-in function/method
16661810 var ml_name : UnsafePointer < Int8 >
16671811
1668- /// The C function that implements it
1669- var ml_meth : @convention ( c) ( PyObjectPointer ? , PyObjectPointer ? ) -> PyObjectPointer ?
1812+ /// The C function that implements it.
1813+ /// Since this accepts multiple function signatures, the Swift type must be opaque here.
1814+ var ml_meth : OpaquePointer
16701815
16711816 /// Combination of METH_xxx flags, which mostly describe the args expected by the C func
16721817 var ml_flags : Int32
@@ -1690,6 +1835,10 @@ public struct PythonInstanceMethod {
16901835 public init ( _ fn: @escaping ( [ PythonObject ] ) throws -> PythonConvertible ) {
16911836 function = PythonFunction ( fn)
16921837 }
1838+
1839+ public init ( _ fn: @escaping ( [ PythonObject ] , [ ( key: String , value: PythonObject ) ] ) throws -> PythonConvertible ) {
1840+ function = PythonFunction ( fn)
1841+ }
16931842}
16941843
16951844extension PythonInstanceMethod : PythonConvertible {
0 commit comments