@@ -246,28 +246,32 @@ def __repr__(self):
246
246
return f"{ type (self ).__qualname__ } ({ self .name_start !r} )"
247
247
248
248
def __call__ (self , receiver , first_arg = _sentinel , ** kwargs ):
249
+ # Ignore parts of argument names after "__".
250
+ order = tuple (argname .split ("__" )[0 ] for argname in kwargs )
251
+ args = [arg for arg in kwargs .values ()]
252
+
249
253
if first_arg is ObjCPartialMethod ._sentinel :
250
254
if kwargs :
251
255
raise TypeError ("Missing first (positional) argument" )
252
-
253
- args = []
254
- rest = frozenset ()
256
+ rest = order
255
257
else :
256
- args = [first_arg ]
257
- # Add "" to rest to indicate that the method takes arguments
258
- rest = frozenset (kwargs ) | frozenset (("" ,))
258
+ args .insert (0 , first_arg )
259
+ rest = ("" ,) + order
259
260
260
261
try :
261
- name , order = self .methods [rest ]
262
+ name = self .methods [rest ]
262
263
except KeyError :
264
+ if first_arg is self ._sentinel :
265
+ specified_sel = self .name_start
266
+ else :
267
+ specified_sel = f"{ self .name_start } :{ ':' .join (kwargs .keys ())} :"
263
268
raise ValueError (
264
- f"No method was found starting with { self .name_start !r} and with keywords { set (kwargs )} \n "
265
- f"Known keywords are:\n "
266
- + "\n " .join (repr (keywords ) for keywords in self .methods )
267
- )
269
+ f"Invalid selector { specified_sel } . Available selectors are: "
270
+ f"{ ', ' .join (sel for sel in self .methods .values ())} "
271
+ ) from None
268
272
269
273
meth = receiver .objc_class ._cache_method (name )
270
- args += [ kwargs [ name ] for name in order ]
274
+
271
275
return meth (receiver , * args )
272
276
273
277
@@ -1035,28 +1039,11 @@ def __getattr__(self, name):
1035
1039
1036
1040
The "interleaved" syntax is usually preferred, since it looks more
1037
1041
similar to normal Objective-C syntax. However, the "flat" syntax is also
1038
- fully supported. Certain method names require the "flat" syntax, for
1039
- example if two arguments have the same label (e.g.
1040
- ``performSelector:withObject:withObject:``), which is not supported by
1041
- Python's keyword argument syntax.
1042
-
1043
- .. warning::
1044
-
1045
- The "interleaved" syntax currently ignores the ordering of its
1046
- keyword arguments. However, in the interest of readability, the
1047
- keyword arguments should always be passed in the same order as they
1048
- appear in the method name.
1049
-
1050
- This also means that two methods whose names which differ only in
1051
- the ordering of their keywords will conflict with each other, and
1052
- can only be called reliably using "flat" syntax.
1053
-
1054
- As of Python 3.6, the order of keyword arguments passed to functions
1055
- is preserved (:pep:`468`). In the future, once Rubicon requires
1056
- Python 3.6 or newer, "interleaved" method calls will respect keyword
1057
- argument order. This will fix the kind of conflict described above,
1058
- but will also disallow specifying the keyword arguments out of
1059
- order.
1042
+ fully supported. If two arguments have the same name (e.g.
1043
+ ``performSelector:withObject:withObject:``), you can use ``__`` in the
1044
+ keywords to disambiguate (e.g., ``performSelector(..., withObject__1=...,
1045
+ withObject__2=...)``. Any content after and including the ``__`` in an argument
1046
+ will be ignored.
1060
1047
"""
1061
1048
# Search for named instance method in the class object and if it
1062
1049
# exists, return callable object with self as hidden argument.
@@ -1090,7 +1077,7 @@ def __getattr__(self, name):
1090
1077
else :
1091
1078
method = None
1092
1079
1093
- if method is None or set (method .methods ) == {frozenset ()}:
1080
+ if method is None or set (method .methods ) == {()}:
1094
1081
# Find a method whose full name matches the given name if no partial
1095
1082
# method was found, or the partial method can only resolve to a
1096
1083
# single method that takes no arguments. The latter case avoids
@@ -1654,20 +1641,23 @@ def _load_methods(self):
1654
1641
name = libobjc .method_getName (method ).name .decode ("utf-8" )
1655
1642
self .instance_method_ptrs [name ] = method
1656
1643
1657
- first , * rest = name .split (":" )
1658
- # Selectors end in a colon iff the method takes arguments.
1659
- # Because of this, rest must either be empty (method takes no arguments)
1660
- # or the last element must be an empty string (method takes arguments).
1661
- assert not rest or rest [- 1 ] == ""
1644
+ # Selectors end with a colon if the method takes arguments.
1645
+ if name .endswith (":" ):
1646
+ first , * rest , _ = name .split (":" )
1647
+ # Insert an empty string in order to indicate that the method
1648
+ # takes a first argument as a positional argument.
1649
+ rest .insert (0 , "" )
1650
+ rest = tuple (rest )
1651
+ else :
1652
+ first = name
1653
+ rest = ()
1662
1654
1663
1655
try :
1664
1656
partial = self .partial_methods [first ]
1665
1657
except KeyError :
1666
1658
partial = self .partial_methods [first ] = ObjCPartialMethod (first )
1667
1659
1668
- # order is rest without the dummy "" part
1669
- order = rest [:- 1 ]
1670
- partial .methods [frozenset (rest )] = (name , order )
1660
+ partial .methods [rest ] = name
1671
1661
1672
1662
# Set the list of methods for the class to the computed list.
1673
1663
self .methods_ptr = methods_ptr
0 commit comments