@@ -322,6 +322,21 @@ public struct ThrowingPythonObject {
322
322
public func dynamicallyCall(
323
323
withKeywordArguments args:
324
324
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 ) {
325
340
try throwPythonErrorIfPresent ( )
326
341
327
342
// An array containing positional arguments.
@@ -615,6 +630,15 @@ public extension PythonObject {
615
630
KeyValuePairs < String , PythonConvertible > = [ : ] ) -> PythonObject {
616
631
return try ! throwing. dynamicallyCall ( withKeywordArguments: args)
617
632
}
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
+ }
618
642
}
619
643
620
644
//===----------------------------------------------------------------------===//
@@ -705,14 +729,13 @@ public struct PythonInterface {
705
729
706
730
// Create a Python tuple object with the specified elements.
707
731
private 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
716
739
}
717
740
718
741
public extension PythonObject {
@@ -723,13 +746,13 @@ public extension PythonObject {
723
746
}
724
747
725
748
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 } ) )
728
751
}
729
752
730
753
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) )
733
756
}
734
757
}
735
758
@@ -1149,7 +1172,7 @@ where Bound : ConvertibleFromPython {
1149
1172
private typealias PythonBinaryOp =
1150
1173
( OwnedPyObjectPointer ? , OwnedPyObjectPointer ? ) -> OwnedPyObjectPointer ?
1151
1174
private typealias PythonUnaryOp =
1152
- ( OwnedPyObjectPointer ? ) -> OwnedPyObjectPointer ?
1175
+ ( OwnedPyObjectPointer ? ) -> OwnedPyObjectPointer ?
1153
1176
1154
1177
private func performBinaryOp(
1155
1178
_ op: PythonBinaryOp , lhs: PythonObject , rhs: PythonObject ) -> PythonObject {
@@ -1409,7 +1432,7 @@ extension PythonObject : Sequence {
1409
1432
}
1410
1433
1411
1434
extension PythonObject {
1412
- public var count : Int {
1435
+ public var count : Int {
1413
1436
checking. count!
1414
1437
}
1415
1438
}
@@ -1545,31 +1568,89 @@ public struct PythonBytes : PythonConvertible, ConvertibleFromPython, Hashable {
1545
1568
/// Python.map(PythonFunction { x in x * 2 }, [10, 12, 14]) // [20, 24, 28]
1546
1569
///
1547
1570
final 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) " )
1551
1608
}
1609
+
1552
1610
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)
1554
1620
}
1555
1621
}
1556
1622
1557
1623
public struct PythonFunction {
1558
1624
/// Called directly by the Python C API
1559
1625
private var function : PyFunction
1560
-
1626
+
1561
1627
public init ( _ fn: @escaping ( PythonObject ) throws -> PythonConvertible ) {
1562
1628
function = PyFunction { argumentsAsTuple in
1563
1629
return try fn ( argumentsAsTuple [ 0 ] )
1564
1630
}
1565
1631
}
1566
-
1567
- /// For cases where the Swift function should accept more (or less) than one parameter, accept an ordered array of all arguments instead
1632
+
1633
+ /// For cases where the Swift function should accept more (or less) than one parameter, accept an ordered array of all arguments instead.
1568
1634
public init ( _ fn: @escaping ( [ PythonObject ] ) throws -> PythonConvertible ) {
1569
1635
function = PyFunction { argumentsAsTuple in
1570
1636
return try fn ( argumentsAsTuple. map { $0 } )
1571
1637
}
1572
1638
}
1639
+
1640
+ /// For cases where the Swift function should accept keyword arguments as `**kwargs` in Python.
1641
+ /// `**kwargs` must preserve order from Python 3.6 onward, similarly to
1642
+ /// Swift `KeyValuePairs` and unlike `Dictionary`. `KeyValuePairs` cannot be
1643
+ /// mutated, so the next best solution is to use `[KeyValuePairs.Element]`.
1644
+ public init ( _ fn: @escaping ( [ PythonObject ] , [ ( key: String , value: PythonObject ) ] ) throws -> PythonConvertible ) {
1645
+ function = PyFunction { argumentsAsTuple, keywordArgumentsAsDictionary in
1646
+ var kwargs : [ ( String , PythonObject ) ] = [ ]
1647
+ for keyAndValue in keywordArgumentsAsDictionary. items ( ) {
1648
+ let ( key, value) = keyAndValue. tuple2
1649
+ kwargs. append ( ( String ( key) !, value) )
1650
+ }
1651
+ return try fn ( argumentsAsTuple. map { $0 } , kwargs)
1652
+ }
1653
+ }
1573
1654
}
1574
1655
1575
1656
extension PythonFunction : PythonConvertible {
@@ -1581,14 +1662,26 @@ extension PythonFunction : PythonConvertible {
1581
1662
fatalError ( " PythonFunction only supports Python 3.1 and above. " )
1582
1663
}
1583
1664
1584
- let funcPointer = Unmanaged . passRetained ( function) . toOpaque ( )
1585
- let capsulePointer = PyCapsule_New ( funcPointer, nil , { capsulePointer in
1665
+ let destructor : @convention ( c) ( PyObjectPointer ? ) -> Void = { capsulePointer in
1586
1666
let funcPointer = PyCapsule_GetPointer ( capsulePointer, nil )
1587
1667
Unmanaged < PyFunction > . fromOpaque ( funcPointer) . release ( )
1588
- } )
1668
+ }
1669
+ let funcPointer = Unmanaged . passRetained ( function) . toOpaque ( )
1670
+ let capsulePointer = PyCapsule_New (
1671
+ funcPointer,
1672
+ nil ,
1673
+ unsafeBitCast ( destructor, to: OpaquePointer . self)
1674
+ )
1589
1675
1676
+ var methodDefinition : UnsafeMutablePointer < PyMethodDef >
1677
+ switch function. callingConvention {
1678
+ case . varArgs:
1679
+ methodDefinition = PythonFunction . sharedMethodDefinition
1680
+ case . varArgsWithKeywords:
1681
+ methodDefinition = PythonFunction . sharedMethodWithKeywordsDefinition
1682
+ }
1590
1683
let pyFuncPointer = PyCFunction_NewEx (
1591
- PythonFunction . sharedMethodDefinition ,
1684
+ methodDefinition ,
1592
1685
capsulePointer,
1593
1686
nil
1594
1687
)
@@ -1598,28 +1691,54 @@ extension PythonFunction : PythonConvertible {
1598
1691
}
1599
1692
1600
1693
fileprivate extension PythonFunction {
1601
- static let sharedFunctionName : UnsafePointer < Int8 > = {
1694
+ static let sharedMethodDefinition : UnsafeMutablePointer < PyMethodDef > = {
1602
1695
let name : StaticString = " pythonkit_swift_function "
1603
1696
// `utf8Start` is a property of StaticString, thus, it has a stable pointer.
1604
- return UnsafeRawPointer ( name. utf8Start) . assumingMemoryBound ( to: Int8 . self)
1605
- } ( )
1697
+ let namePointer = UnsafeRawPointer ( name. utf8Start) . assumingMemoryBound ( to: Int8 . self)
1698
+
1699
+ let methodImplementationPointer = unsafeBitCast (
1700
+ PythonFunction . sharedMethodImplementation, to: OpaquePointer . self)
1606
1701
1607
- static let sharedMethodDefinition : UnsafeMutablePointer < PyMethodDef > = {
1608
1702
/// The standard calling convention. See Python C API docs
1609
- let METH_VARARGS = 1 as Int32
1703
+ let METH_VARARGS = 0x0001 as Int32
1610
1704
1611
1705
let pointer = UnsafeMutablePointer< PyMethodDef> . allocate( capacity: 1 )
1612
1706
pointer. pointee = PyMethodDef (
1613
- ml_name: PythonFunction . sharedFunctionName ,
1614
- ml_meth: PythonFunction . sharedMethodImplementation ,
1707
+ ml_name: namePointer ,
1708
+ ml_meth: methodImplementationPointer ,
1615
1709
ml_flags: METH_VARARGS,
1616
1710
ml_doc: nil
1617
1711
)
1618
1712
1619
1713
return pointer
1620
1714
} ( )
1715
+
1716
+ static let sharedMethodWithKeywordsDefinition : UnsafeMutablePointer < PyMethodDef > = {
1717
+ let name : StaticString = " pythonkit_swift_function_with_keywords "
1718
+ // `utf8Start` is a property of StaticString, thus, it has a stable pointer.
1719
+ let namePointer = UnsafeRawPointer ( name. utf8Start) . assumingMemoryBound ( to: Int8 . self)
1720
+
1721
+ let methodImplementationPointer = unsafeBitCast (
1722
+ PythonFunction . sharedMethodWithKeywordsImplementation, to: OpaquePointer . self)
1723
+
1724
+ /// A combination of flags that supports `**kwargs`. See Python C API docs
1725
+ let METH_VARARGS = 0x0001 as Int32
1726
+ let METH_KEYWORDS = 0x0002 as Int32
1727
+
1728
+ let pointer = UnsafeMutablePointer< PyMethodDef> . allocate( capacity: 1 )
1729
+ pointer. pointee = PyMethodDef (
1730
+ ml_name: namePointer,
1731
+ ml_meth: methodImplementationPointer,
1732
+ ml_flags: METH_VARARGS | METH_KEYWORDS,
1733
+ ml_doc: nil
1734
+ )
1735
+
1736
+ return pointer
1737
+ } ( )
1621
1738
1622
- private static let sharedMethodImplementation : @convention ( c) ( PyObjectPointer ? , PyObjectPointer ? ) -> PyObjectPointer ? = { context, argumentsPointer in
1739
+ private static let sharedMethodImplementation : @convention ( c) (
1740
+ PyObjectPointer ? , PyObjectPointer ?
1741
+ ) -> PyObjectPointer ? = { context, argumentsPointer in
1623
1742
guard let argumentsPointer = argumentsPointer, let capsulePointer = context else {
1624
1743
return nil
1625
1744
}
@@ -1635,6 +1754,31 @@ fileprivate extension PythonFunction {
1635
1754
return nil // This must only be `nil` if an exception has been set
1636
1755
}
1637
1756
}
1757
+
1758
+ private static let sharedMethodWithKeywordsImplementation : @convention ( c) (
1759
+ PyObjectPointer ? , PyObjectPointer ? , PyObjectPointer ?
1760
+ ) -> PyObjectPointer ? = { context, argumentsPointer, keywordArgumentsPointer in
1761
+ guard let argumentsPointer = argumentsPointer, let capsulePointer = context else {
1762
+ return nil
1763
+ }
1764
+
1765
+ let funcPointer = PyCapsule_GetPointer ( capsulePointer, nil )
1766
+ let function = Unmanaged < PyFunction > . fromOpaque ( funcPointer) . takeUnretainedValue ( )
1767
+
1768
+ do {
1769
+ let argumentsAsTuple = PythonObject ( consuming: argumentsPointer)
1770
+ var keywordArgumentsAsDictionary : PythonObject
1771
+ if let keywordArgumentsPointer = keywordArgumentsPointer {
1772
+ keywordArgumentsAsDictionary = PythonObject ( consuming: keywordArgumentsPointer)
1773
+ } else {
1774
+ keywordArgumentsAsDictionary = [ : ]
1775
+ }
1776
+ return try function ( argumentsAsTuple, keywordArgumentsAsDictionary) . ownedPyObject
1777
+ } catch {
1778
+ PythonFunction . setPythonError ( swiftError: error)
1779
+ return nil // This must only be `nil` if an exception has been set
1780
+ }
1781
+ }
1638
1782
1639
1783
private static func setPythonError( swiftError: Error ) {
1640
1784
if let pythonObject = swiftError as? PythonObject {
@@ -1664,8 +1808,9 @@ struct PyMethodDef {
1664
1808
/// The name of the built-in function/method
1665
1809
var ml_name : UnsafePointer < Int8 >
1666
1810
1667
- /// The C function that implements it
1668
- var ml_meth : @convention ( c) ( PyObjectPointer ? , PyObjectPointer ? ) -> PyObjectPointer ?
1811
+ /// The C function that implements it.
1812
+ /// Since this accepts multiple function signatures, the Swift type must be opaque here.
1813
+ var ml_meth : OpaquePointer
1669
1814
1670
1815
/// Combination of METH_xxx flags, which mostly describe the args expected by the C func
1671
1816
var ml_flags : Int32
@@ -1688,6 +1833,10 @@ public struct PythonInstanceMethod {
1688
1833
public init ( _ fn: @escaping ( [ PythonObject ] ) throws -> PythonConvertible ) {
1689
1834
function = PythonFunction ( fn)
1690
1835
}
1836
+
1837
+ public init ( _ fn: @escaping ( [ PythonObject ] , [ ( key: String , value: PythonObject ) ] ) throws -> PythonConvertible ) {
1838
+ function = PythonFunction ( fn)
1839
+ }
1691
1840
}
1692
1841
1693
1842
extension PythonInstanceMethod : PythonConvertible {
0 commit comments