@@ -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,32 +1568,90 @@ 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
@_disfavoredOverload
1562
1628
public init ( _ fn: @escaping ( PythonObject ) throws -> PythonConvertible ) {
1563
1629
function = PyFunction { argumentsAsTuple in
1564
1630
return try fn ( argumentsAsTuple [ 0 ] )
1565
1631
}
1566
1632
}
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.
1569
1635
public init ( _ fn: @escaping ( [ PythonObject ] ) throws -> PythonConvertible ) {
1570
1636
function = PyFunction { argumentsAsTuple in
1571
1637
return try fn ( argumentsAsTuple. map { $0 } )
1572
1638
}
1573
1639
}
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
+ }
1574
1655
}
1575
1656
1576
1657
extension PythonFunction : PythonConvertible {
@@ -1582,14 +1663,26 @@ extension PythonFunction : PythonConvertible {
1582
1663
fatalError ( " PythonFunction only supports Python 3.1 and above. " )
1583
1664
}
1584
1665
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
1587
1667
let funcPointer = PyCapsule_GetPointer ( capsulePointer, nil )
1588
1668
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
+ )
1590
1676
1677
+ var methodDefinition : UnsafeMutablePointer < PyMethodDef >
1678
+ switch function. callingConvention {
1679
+ case . varArgs:
1680
+ methodDefinition = PythonFunction . sharedMethodDefinition
1681
+ case . varArgsWithKeywords:
1682
+ methodDefinition = PythonFunction . sharedMethodWithKeywordsDefinition
1683
+ }
1591
1684
let pyFuncPointer = PyCFunction_NewEx (
1592
- PythonFunction . sharedMethodDefinition ,
1685
+ methodDefinition ,
1593
1686
capsulePointer,
1594
1687
nil
1595
1688
)
@@ -1599,28 +1692,54 @@ extension PythonFunction : PythonConvertible {
1599
1692
}
1600
1693
1601
1694
fileprivate extension PythonFunction {
1602
- static let sharedFunctionName : UnsafePointer < Int8 > = {
1695
+ static let sharedMethodDefinition : UnsafeMutablePointer < PyMethodDef > = {
1603
1696
let name : StaticString = " pythonkit_swift_function "
1604
1697
// `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)
1607
1702
1608
- static let sharedMethodDefinition : UnsafeMutablePointer < PyMethodDef > = {
1609
1703
/// The standard calling convention. See Python C API docs
1610
- let METH_VARARGS = 1 as Int32
1704
+ let METH_VARARGS = 0x0001 as Int32
1611
1705
1612
1706
let pointer = UnsafeMutablePointer< PyMethodDef> . allocate( capacity: 1 )
1613
1707
pointer. pointee = PyMethodDef (
1614
- ml_name: PythonFunction . sharedFunctionName ,
1615
- ml_meth: PythonFunction . sharedMethodImplementation ,
1708
+ ml_name: namePointer ,
1709
+ ml_meth: methodImplementationPointer ,
1616
1710
ml_flags: METH_VARARGS,
1617
1711
ml_doc: nil
1618
1712
)
1619
1713
1620
1714
return pointer
1621
1715
} ( )
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
+ } ( )
1622
1739
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
1624
1743
guard let argumentsPointer = argumentsPointer, let capsulePointer = context else {
1625
1744
return nil
1626
1745
}
@@ -1636,6 +1755,31 @@ fileprivate extension PythonFunction {
1636
1755
return nil // This must only be `nil` if an exception has been set
1637
1756
}
1638
1757
}
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
+ }
1639
1783
1640
1784
private static func setPythonError( swiftError: Error ) {
1641
1785
if let pythonObject = swiftError as? PythonObject {
@@ -1665,8 +1809,9 @@ struct PyMethodDef {
1665
1809
/// The name of the built-in function/method
1666
1810
var ml_name : UnsafePointer < Int8 >
1667
1811
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
1670
1815
1671
1816
/// Combination of METH_xxx flags, which mostly describe the args expected by the C func
1672
1817
var ml_flags : Int32
@@ -1690,6 +1835,10 @@ public struct PythonInstanceMethod {
1690
1835
public init ( _ fn: @escaping ( [ PythonObject ] ) throws -> PythonConvertible ) {
1691
1836
function = PythonFunction ( fn)
1692
1837
}
1838
+
1839
+ public init ( _ fn: @escaping ( [ PythonObject ] , [ ( key: String , value: PythonObject ) ] ) throws -> PythonConvertible ) {
1840
+ function = PythonFunction ( fn)
1841
+ }
1693
1842
}
1694
1843
1695
1844
extension PythonInstanceMethod : PythonConvertible {
0 commit comments