##// END OF EJS Templates
Fix signature with unbound methods
Thomas Kluyver -
Show More
@@ -1,813 +1,816 b''
1 """Function signature objects for callables.
1 """Function signature objects for callables.
2
2
3 Back port of Python 3.3's function signature tools from the inspect module,
3 Back port of Python 3.3's function signature tools from the inspect module,
4 modified to be compatible with Python 2.7 and 3.2+.
4 modified to be compatible with Python 2.7 and 3.2+.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Python 3.3 stdlib inspect.py is public domain
8 # Python 3.3 stdlib inspect.py is public domain
9 #
9 #
10 # Backports Copyright (C) 2013 Aaron Iles
10 # Backports Copyright (C) 2013 Aaron Iles
11 # Used under Apache License Version 2.0
11 # Used under Apache License Version 2.0
12 #
12 #
13 # Further Changes are Copyright (C) 2013 The IPython Development Team
13 # Further Changes are Copyright (C) 2013 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from __future__ import absolute_import, division, print_function
19 from __future__ import absolute_import, division, print_function
20 import itertools
20 import itertools
21 import functools
21 import functools
22 import re
22 import re
23 import types
23 import types
24
24
25
25
26 # patch for single-file
26 # patch for single-file
27 # we don't support 2.6, so we can just import OrderedDict
27 # we don't support 2.6, so we can just import OrderedDict
28 from collections import OrderedDict
28 from collections import OrderedDict
29
29
30 __version__ = '0.3'
30 __version__ = '0.3'
31 # end patch
31 # end patch
32
32
33 __all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature']
33 __all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature']
34
34
35
35
36 _WrapperDescriptor = type(type.__call__)
36 _WrapperDescriptor = type(type.__call__)
37 _MethodWrapper = type(all.__call__)
37 _MethodWrapper = type(all.__call__)
38
38
39 _NonUserDefinedCallables = (_WrapperDescriptor,
39 _NonUserDefinedCallables = (_WrapperDescriptor,
40 _MethodWrapper,
40 _MethodWrapper,
41 types.BuiltinFunctionType)
41 types.BuiltinFunctionType)
42
42
43
43
44 def formatannotation(annotation, base_module=None):
44 def formatannotation(annotation, base_module=None):
45 if isinstance(annotation, type):
45 if isinstance(annotation, type):
46 if annotation.__module__ in ('builtins', '__builtin__', base_module):
46 if annotation.__module__ in ('builtins', '__builtin__', base_module):
47 return annotation.__name__
47 return annotation.__name__
48 return annotation.__module__+'.'+annotation.__name__
48 return annotation.__module__+'.'+annotation.__name__
49 return repr(annotation)
49 return repr(annotation)
50
50
51
51
52 def _get_user_defined_method(cls, method_name, *nested):
52 def _get_user_defined_method(cls, method_name, *nested):
53 try:
53 try:
54 if cls is type:
54 if cls is type:
55 return
55 return
56 meth = getattr(cls, method_name)
56 meth = getattr(cls, method_name)
57 for name in nested:
57 for name in nested:
58 meth = getattr(meth, name, meth)
58 meth = getattr(meth, name, meth)
59 except AttributeError:
59 except AttributeError:
60 return
60 return
61 else:
61 else:
62 if not isinstance(meth, _NonUserDefinedCallables):
62 if not isinstance(meth, _NonUserDefinedCallables):
63 # Once '__signature__' will be added to 'C'-level
63 # Once '__signature__' will be added to 'C'-level
64 # callables, this check won't be necessary
64 # callables, this check won't be necessary
65 return meth
65 return meth
66
66
67
67
68 def signature(obj):
68 def signature(obj):
69 '''Get a signature object for the passed callable.'''
69 '''Get a signature object for the passed callable.'''
70
70
71 if not callable(obj):
71 if not callable(obj):
72 raise TypeError('{0!r} is not a callable object'.format(obj))
72 raise TypeError('{0!r} is not a callable object'.format(obj))
73
73
74 if isinstance(obj, types.MethodType):
74 if isinstance(obj, types.MethodType):
75 # In this case we skip the first parameter of the underlying
75 if obj.__self__ is None:
76 # function (usually `self` or `cls`).
76 # Unbound method - treat it as a function (no distinction in Py 3)
77 sig = signature(obj.__func__)
77 obj = obj.__func__
78 return sig.replace(parameters=tuple(sig.parameters.values())[1:])
78 else:
79 # Bound method: trim off the first parameter (typically self or cls)
80 sig = signature(obj.__func__)
81 return sig.replace(parameters=tuple(sig.parameters.values())[1:])
79
82
80 try:
83 try:
81 sig = obj.__signature__
84 sig = obj.__signature__
82 except AttributeError:
85 except AttributeError:
83 pass
86 pass
84 else:
87 else:
85 if sig is not None:
88 if sig is not None:
86 return sig
89 return sig
87
90
88 try:
91 try:
89 # Was this function wrapped by a decorator?
92 # Was this function wrapped by a decorator?
90 wrapped = obj.__wrapped__
93 wrapped = obj.__wrapped__
91 except AttributeError:
94 except AttributeError:
92 pass
95 pass
93 else:
96 else:
94 return signature(wrapped)
97 return signature(wrapped)
95
98
96 if isinstance(obj, types.FunctionType):
99 if isinstance(obj, types.FunctionType):
97 return Signature.from_function(obj)
100 return Signature.from_function(obj)
98
101
99 if isinstance(obj, functools.partial):
102 if isinstance(obj, functools.partial):
100 sig = signature(obj.func)
103 sig = signature(obj.func)
101
104
102 new_params = OrderedDict(sig.parameters.items())
105 new_params = OrderedDict(sig.parameters.items())
103
106
104 partial_args = obj.args or ()
107 partial_args = obj.args or ()
105 partial_keywords = obj.keywords or {}
108 partial_keywords = obj.keywords or {}
106 try:
109 try:
107 ba = sig.bind_partial(*partial_args, **partial_keywords)
110 ba = sig.bind_partial(*partial_args, **partial_keywords)
108 except TypeError as ex:
111 except TypeError as ex:
109 msg = 'partial object {0!r} has incorrect arguments'.format(obj)
112 msg = 'partial object {0!r} has incorrect arguments'.format(obj)
110 raise ValueError(msg)
113 raise ValueError(msg)
111
114
112 for arg_name, arg_value in ba.arguments.items():
115 for arg_name, arg_value in ba.arguments.items():
113 param = new_params[arg_name]
116 param = new_params[arg_name]
114 if arg_name in partial_keywords:
117 if arg_name in partial_keywords:
115 # We set a new default value, because the following code
118 # We set a new default value, because the following code
116 # is correct:
119 # is correct:
117 #
120 #
118 # >>> def foo(a): print(a)
121 # >>> def foo(a): print(a)
119 # >>> print(partial(partial(foo, a=10), a=20)())
122 # >>> print(partial(partial(foo, a=10), a=20)())
120 # 20
123 # 20
121 # >>> print(partial(partial(foo, a=10), a=20)(a=30))
124 # >>> print(partial(partial(foo, a=10), a=20)(a=30))
122 # 30
125 # 30
123 #
126 #
124 # So, with 'partial' objects, passing a keyword argument is
127 # So, with 'partial' objects, passing a keyword argument is
125 # like setting a new default value for the corresponding
128 # like setting a new default value for the corresponding
126 # parameter
129 # parameter
127 #
130 #
128 # We also mark this parameter with '_partial_kwarg'
131 # We also mark this parameter with '_partial_kwarg'
129 # flag. Later, in '_bind', the 'default' value of this
132 # flag. Later, in '_bind', the 'default' value of this
130 # parameter will be added to 'kwargs', to simulate
133 # parameter will be added to 'kwargs', to simulate
131 # the 'functools.partial' real call.
134 # the 'functools.partial' real call.
132 new_params[arg_name] = param.replace(default=arg_value,
135 new_params[arg_name] = param.replace(default=arg_value,
133 _partial_kwarg=True)
136 _partial_kwarg=True)
134
137
135 elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and
138 elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and
136 not param._partial_kwarg):
139 not param._partial_kwarg):
137 new_params.pop(arg_name)
140 new_params.pop(arg_name)
138
141
139 return sig.replace(parameters=new_params.values())
142 return sig.replace(parameters=new_params.values())
140
143
141 sig = None
144 sig = None
142 if isinstance(obj, type):
145 if isinstance(obj, type):
143 # obj is a class or a metaclass
146 # obj is a class or a metaclass
144
147
145 # First, let's see if it has an overloaded __call__ defined
148 # First, let's see if it has an overloaded __call__ defined
146 # in its metaclass
149 # in its metaclass
147 call = _get_user_defined_method(type(obj), '__call__')
150 call = _get_user_defined_method(type(obj), '__call__')
148 if call is not None:
151 if call is not None:
149 sig = signature(call)
152 sig = signature(call)
150 else:
153 else:
151 # Now we check if the 'obj' class has a '__new__' method
154 # Now we check if the 'obj' class has a '__new__' method
152 new = _get_user_defined_method(obj, '__new__')
155 new = _get_user_defined_method(obj, '__new__')
153 if new is not None:
156 if new is not None:
154 sig = signature(new)
157 sig = signature(new)
155 else:
158 else:
156 # Finally, we should have at least __init__ implemented
159 # Finally, we should have at least __init__ implemented
157 init = _get_user_defined_method(obj, '__init__')
160 init = _get_user_defined_method(obj, '__init__')
158 if init is not None:
161 if init is not None:
159 sig = signature(init)
162 sig = signature(init)
160 elif not isinstance(obj, _NonUserDefinedCallables):
163 elif not isinstance(obj, _NonUserDefinedCallables):
161 # An object with __call__
164 # An object with __call__
162 # We also check that the 'obj' is not an instance of
165 # We also check that the 'obj' is not an instance of
163 # _WrapperDescriptor or _MethodWrapper to avoid
166 # _WrapperDescriptor or _MethodWrapper to avoid
164 # infinite recursion (and even potential segfault)
167 # infinite recursion (and even potential segfault)
165 call = _get_user_defined_method(type(obj), '__call__', 'im_func')
168 call = _get_user_defined_method(type(obj), '__call__', 'im_func')
166 if call is not None:
169 if call is not None:
167 sig = signature(call)
170 sig = signature(call)
168
171
169 if sig is not None:
172 if sig is not None:
170 return sig
173 return sig
171
174
172 if isinstance(obj, types.BuiltinFunctionType):
175 if isinstance(obj, types.BuiltinFunctionType):
173 # Raise a nicer error message for builtins
176 # Raise a nicer error message for builtins
174 msg = 'no signature found for builtin function {0!r}'.format(obj)
177 msg = 'no signature found for builtin function {0!r}'.format(obj)
175 raise ValueError(msg)
178 raise ValueError(msg)
176
179
177 raise ValueError('callable {0!r} is not supported by signature'.format(obj))
180 raise ValueError('callable {0!r} is not supported by signature'.format(obj))
178
181
179
182
180 class _void(object):
183 class _void(object):
181 '''A private marker - used in Parameter & Signature'''
184 '''A private marker - used in Parameter & Signature'''
182
185
183
186
184 class _empty(object):
187 class _empty(object):
185 pass
188 pass
186
189
187
190
188 class _ParameterKind(int):
191 class _ParameterKind(int):
189 def __new__(self, *args, **kwargs):
192 def __new__(self, *args, **kwargs):
190 obj = int.__new__(self, *args)
193 obj = int.__new__(self, *args)
191 obj._name = kwargs['name']
194 obj._name = kwargs['name']
192 return obj
195 return obj
193
196
194 def __str__(self):
197 def __str__(self):
195 return self._name
198 return self._name
196
199
197 def __repr__(self):
200 def __repr__(self):
198 return '<_ParameterKind: {0!r}>'.format(self._name)
201 return '<_ParameterKind: {0!r}>'.format(self._name)
199
202
200
203
201 _POSITIONAL_ONLY = _ParameterKind(0, name='POSITIONAL_ONLY')
204 _POSITIONAL_ONLY = _ParameterKind(0, name='POSITIONAL_ONLY')
202 _POSITIONAL_OR_KEYWORD = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD')
205 _POSITIONAL_OR_KEYWORD = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD')
203 _VAR_POSITIONAL = _ParameterKind(2, name='VAR_POSITIONAL')
206 _VAR_POSITIONAL = _ParameterKind(2, name='VAR_POSITIONAL')
204 _KEYWORD_ONLY = _ParameterKind(3, name='KEYWORD_ONLY')
207 _KEYWORD_ONLY = _ParameterKind(3, name='KEYWORD_ONLY')
205 _VAR_KEYWORD = _ParameterKind(4, name='VAR_KEYWORD')
208 _VAR_KEYWORD = _ParameterKind(4, name='VAR_KEYWORD')
206
209
207
210
208 class Parameter(object):
211 class Parameter(object):
209 '''Represents a parameter in a function signature.
212 '''Represents a parameter in a function signature.
210
213
211 Has the following public attributes:
214 Has the following public attributes:
212
215
213 * name : str
216 * name : str
214 The name of the parameter as a string.
217 The name of the parameter as a string.
215 * default : object
218 * default : object
216 The default value for the parameter if specified. If the
219 The default value for the parameter if specified. If the
217 parameter has no default value, this attribute is not set.
220 parameter has no default value, this attribute is not set.
218 * annotation
221 * annotation
219 The annotation for the parameter if specified. If the
222 The annotation for the parameter if specified. If the
220 parameter has no annotation, this attribute is not set.
223 parameter has no annotation, this attribute is not set.
221 * kind : str
224 * kind : str
222 Describes how argument values are bound to the parameter.
225 Describes how argument values are bound to the parameter.
223 Possible values: `Parameter.POSITIONAL_ONLY`,
226 Possible values: `Parameter.POSITIONAL_ONLY`,
224 `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
227 `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`,
225 `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
228 `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
226 '''
229 '''
227
230
228 __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg')
231 __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg')
229
232
230 POSITIONAL_ONLY = _POSITIONAL_ONLY
233 POSITIONAL_ONLY = _POSITIONAL_ONLY
231 POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD
234 POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD
232 VAR_POSITIONAL = _VAR_POSITIONAL
235 VAR_POSITIONAL = _VAR_POSITIONAL
233 KEYWORD_ONLY = _KEYWORD_ONLY
236 KEYWORD_ONLY = _KEYWORD_ONLY
234 VAR_KEYWORD = _VAR_KEYWORD
237 VAR_KEYWORD = _VAR_KEYWORD
235
238
236 empty = _empty
239 empty = _empty
237
240
238 def __init__(self, name, kind, default=_empty, annotation=_empty,
241 def __init__(self, name, kind, default=_empty, annotation=_empty,
239 _partial_kwarg=False):
242 _partial_kwarg=False):
240
243
241 if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD,
244 if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD,
242 _VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD):
245 _VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD):
243 raise ValueError("invalid value for 'Parameter.kind' attribute")
246 raise ValueError("invalid value for 'Parameter.kind' attribute")
244 self._kind = kind
247 self._kind = kind
245
248
246 if default is not _empty:
249 if default is not _empty:
247 if kind in (_VAR_POSITIONAL, _VAR_KEYWORD):
250 if kind in (_VAR_POSITIONAL, _VAR_KEYWORD):
248 msg = '{0} parameters cannot have default values'.format(kind)
251 msg = '{0} parameters cannot have default values'.format(kind)
249 raise ValueError(msg)
252 raise ValueError(msg)
250 self._default = default
253 self._default = default
251 self._annotation = annotation
254 self._annotation = annotation
252
255
253 if name is None:
256 if name is None:
254 if kind != _POSITIONAL_ONLY:
257 if kind != _POSITIONAL_ONLY:
255 raise ValueError("None is not a valid name for a "
258 raise ValueError("None is not a valid name for a "
256 "non-positional-only parameter")
259 "non-positional-only parameter")
257 self._name = name
260 self._name = name
258 else:
261 else:
259 name = str(name)
262 name = str(name)
260 if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I):
263 if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I):
261 msg = '{0!r} is not a valid parameter name'.format(name)
264 msg = '{0!r} is not a valid parameter name'.format(name)
262 raise ValueError(msg)
265 raise ValueError(msg)
263 self._name = name
266 self._name = name
264
267
265 self._partial_kwarg = _partial_kwarg
268 self._partial_kwarg = _partial_kwarg
266
269
267 @property
270 @property
268 def name(self):
271 def name(self):
269 return self._name
272 return self._name
270
273
271 @property
274 @property
272 def default(self):
275 def default(self):
273 return self._default
276 return self._default
274
277
275 @property
278 @property
276 def annotation(self):
279 def annotation(self):
277 return self._annotation
280 return self._annotation
278
281
279 @property
282 @property
280 def kind(self):
283 def kind(self):
281 return self._kind
284 return self._kind
282
285
283 def replace(self, name=_void, kind=_void, annotation=_void,
286 def replace(self, name=_void, kind=_void, annotation=_void,
284 default=_void, _partial_kwarg=_void):
287 default=_void, _partial_kwarg=_void):
285 '''Creates a customized copy of the Parameter.'''
288 '''Creates a customized copy of the Parameter.'''
286
289
287 if name is _void:
290 if name is _void:
288 name = self._name
291 name = self._name
289
292
290 if kind is _void:
293 if kind is _void:
291 kind = self._kind
294 kind = self._kind
292
295
293 if annotation is _void:
296 if annotation is _void:
294 annotation = self._annotation
297 annotation = self._annotation
295
298
296 if default is _void:
299 if default is _void:
297 default = self._default
300 default = self._default
298
301
299 if _partial_kwarg is _void:
302 if _partial_kwarg is _void:
300 _partial_kwarg = self._partial_kwarg
303 _partial_kwarg = self._partial_kwarg
301
304
302 return type(self)(name, kind, default=default, annotation=annotation,
305 return type(self)(name, kind, default=default, annotation=annotation,
303 _partial_kwarg=_partial_kwarg)
306 _partial_kwarg=_partial_kwarg)
304
307
305 def __str__(self):
308 def __str__(self):
306 kind = self.kind
309 kind = self.kind
307
310
308 formatted = self._name
311 formatted = self._name
309 if kind == _POSITIONAL_ONLY:
312 if kind == _POSITIONAL_ONLY:
310 if formatted is None:
313 if formatted is None:
311 formatted = ''
314 formatted = ''
312 formatted = '<{0}>'.format(formatted)
315 formatted = '<{0}>'.format(formatted)
313
316
314 # Add annotation and default value
317 # Add annotation and default value
315 if self._annotation is not _empty:
318 if self._annotation is not _empty:
316 formatted = '{0}:{1}'.format(formatted,
319 formatted = '{0}:{1}'.format(formatted,
317 formatannotation(self._annotation))
320 formatannotation(self._annotation))
318
321
319 if self._default is not _empty:
322 if self._default is not _empty:
320 formatted = '{0}={1}'.format(formatted, repr(self._default))
323 formatted = '{0}={1}'.format(formatted, repr(self._default))
321
324
322 if kind == _VAR_POSITIONAL:
325 if kind == _VAR_POSITIONAL:
323 formatted = '*' + formatted
326 formatted = '*' + formatted
324 elif kind == _VAR_KEYWORD:
327 elif kind == _VAR_KEYWORD:
325 formatted = '**' + formatted
328 formatted = '**' + formatted
326
329
327 return formatted
330 return formatted
328
331
329 def __repr__(self):
332 def __repr__(self):
330 return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__,
333 return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__,
331 id(self), self.name)
334 id(self), self.name)
332
335
333 def __hash__(self):
336 def __hash__(self):
334 msg = "unhashable type: '{0}'".format(self.__class__.__name__)
337 msg = "unhashable type: '{0}'".format(self.__class__.__name__)
335 raise TypeError(msg)
338 raise TypeError(msg)
336
339
337 def __eq__(self, other):
340 def __eq__(self, other):
338 return (issubclass(other.__class__, Parameter) and
341 return (issubclass(other.__class__, Parameter) and
339 self._name == other._name and
342 self._name == other._name and
340 self._kind == other._kind and
343 self._kind == other._kind and
341 self._default == other._default and
344 self._default == other._default and
342 self._annotation == other._annotation)
345 self._annotation == other._annotation)
343
346
344 def __ne__(self, other):
347 def __ne__(self, other):
345 return not self.__eq__(other)
348 return not self.__eq__(other)
346
349
347
350
348 class BoundArguments(object):
351 class BoundArguments(object):
349 '''Result of :meth:`Signature.bind` call. Holds the mapping of arguments
352 '''Result of :meth:`Signature.bind` call. Holds the mapping of arguments
350 to the function's parameters.
353 to the function's parameters.
351
354
352 Has the following public attributes:
355 Has the following public attributes:
353
356
354 arguments : :class:`collections.OrderedDict`
357 arguments : :class:`collections.OrderedDict`
355 An ordered mutable mapping of parameters' names to arguments' values.
358 An ordered mutable mapping of parameters' names to arguments' values.
356 Does not contain arguments' default values.
359 Does not contain arguments' default values.
357 signature : :class:`Signature`
360 signature : :class:`Signature`
358 The Signature object that created this instance.
361 The Signature object that created this instance.
359 args : tuple
362 args : tuple
360 Tuple of positional arguments values.
363 Tuple of positional arguments values.
361 kwargs : dict
364 kwargs : dict
362 Dict of keyword arguments values.
365 Dict of keyword arguments values.
363 '''
366 '''
364
367
365 def __init__(self, signature, arguments):
368 def __init__(self, signature, arguments):
366 self.arguments = arguments
369 self.arguments = arguments
367 self._signature = signature
370 self._signature = signature
368
371
369 @property
372 @property
370 def signature(self):
373 def signature(self):
371 return self._signature
374 return self._signature
372
375
373 @property
376 @property
374 def args(self):
377 def args(self):
375 args = []
378 args = []
376 for param_name, param in self._signature.parameters.items():
379 for param_name, param in self._signature.parameters.items():
377 if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
380 if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
378 param._partial_kwarg):
381 param._partial_kwarg):
379 # Keyword arguments mapped by 'functools.partial'
382 # Keyword arguments mapped by 'functools.partial'
380 # (Parameter._partial_kwarg is True) are mapped
383 # (Parameter._partial_kwarg is True) are mapped
381 # in 'BoundArguments.kwargs', along with VAR_KEYWORD &
384 # in 'BoundArguments.kwargs', along with VAR_KEYWORD &
382 # KEYWORD_ONLY
385 # KEYWORD_ONLY
383 break
386 break
384
387
385 try:
388 try:
386 arg = self.arguments[param_name]
389 arg = self.arguments[param_name]
387 except KeyError:
390 except KeyError:
388 # We're done here. Other arguments
391 # We're done here. Other arguments
389 # will be mapped in 'BoundArguments.kwargs'
392 # will be mapped in 'BoundArguments.kwargs'
390 break
393 break
391 else:
394 else:
392 if param.kind == _VAR_POSITIONAL:
395 if param.kind == _VAR_POSITIONAL:
393 # *args
396 # *args
394 args.extend(arg)
397 args.extend(arg)
395 else:
398 else:
396 # plain argument
399 # plain argument
397 args.append(arg)
400 args.append(arg)
398
401
399 return tuple(args)
402 return tuple(args)
400
403
401 @property
404 @property
402 def kwargs(self):
405 def kwargs(self):
403 kwargs = {}
406 kwargs = {}
404 kwargs_started = False
407 kwargs_started = False
405 for param_name, param in self._signature.parameters.items():
408 for param_name, param in self._signature.parameters.items():
406 if not kwargs_started:
409 if not kwargs_started:
407 if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
410 if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
408 param._partial_kwarg):
411 param._partial_kwarg):
409 kwargs_started = True
412 kwargs_started = True
410 else:
413 else:
411 if param_name not in self.arguments:
414 if param_name not in self.arguments:
412 kwargs_started = True
415 kwargs_started = True
413 continue
416 continue
414
417
415 if not kwargs_started:
418 if not kwargs_started:
416 continue
419 continue
417
420
418 try:
421 try:
419 arg = self.arguments[param_name]
422 arg = self.arguments[param_name]
420 except KeyError:
423 except KeyError:
421 pass
424 pass
422 else:
425 else:
423 if param.kind == _VAR_KEYWORD:
426 if param.kind == _VAR_KEYWORD:
424 # **kwargs
427 # **kwargs
425 kwargs.update(arg)
428 kwargs.update(arg)
426 else:
429 else:
427 # plain keyword argument
430 # plain keyword argument
428 kwargs[param_name] = arg
431 kwargs[param_name] = arg
429
432
430 return kwargs
433 return kwargs
431
434
432 def __hash__(self):
435 def __hash__(self):
433 msg = "unhashable type: '{0}'".format(self.__class__.__name__)
436 msg = "unhashable type: '{0}'".format(self.__class__.__name__)
434 raise TypeError(msg)
437 raise TypeError(msg)
435
438
436 def __eq__(self, other):
439 def __eq__(self, other):
437 return (issubclass(other.__class__, BoundArguments) and
440 return (issubclass(other.__class__, BoundArguments) and
438 self.signature == other.signature and
441 self.signature == other.signature and
439 self.arguments == other.arguments)
442 self.arguments == other.arguments)
440
443
441 def __ne__(self, other):
444 def __ne__(self, other):
442 return not self.__eq__(other)
445 return not self.__eq__(other)
443
446
444
447
445 class Signature(object):
448 class Signature(object):
446 '''A Signature object represents the overall signature of a function.
449 '''A Signature object represents the overall signature of a function.
447 It stores a Parameter object for each parameter accepted by the
450 It stores a Parameter object for each parameter accepted by the
448 function, as well as information specific to the function itself.
451 function, as well as information specific to the function itself.
449
452
450 A Signature object has the following public attributes:
453 A Signature object has the following public attributes:
451
454
452 parameters : :class:`collections.OrderedDict`
455 parameters : :class:`collections.OrderedDict`
453 An ordered mapping of parameters' names to the corresponding
456 An ordered mapping of parameters' names to the corresponding
454 Parameter objects (keyword-only arguments are in the same order
457 Parameter objects (keyword-only arguments are in the same order
455 as listed in `code.co_varnames`).
458 as listed in `code.co_varnames`).
456 return_annotation
459 return_annotation
457 The annotation for the return type of the function if specified.
460 The annotation for the return type of the function if specified.
458 If the function has no annotation for its return type, this
461 If the function has no annotation for its return type, this
459 attribute is not set.
462 attribute is not set.
460 '''
463 '''
461
464
462 __slots__ = ('_return_annotation', '_parameters')
465 __slots__ = ('_return_annotation', '_parameters')
463
466
464 _parameter_cls = Parameter
467 _parameter_cls = Parameter
465 _bound_arguments_cls = BoundArguments
468 _bound_arguments_cls = BoundArguments
466
469
467 empty = _empty
470 empty = _empty
468
471
469 def __init__(self, parameters=None, return_annotation=_empty,
472 def __init__(self, parameters=None, return_annotation=_empty,
470 __validate_parameters__=True):
473 __validate_parameters__=True):
471 '''Constructs Signature from the given list of Parameter
474 '''Constructs Signature from the given list of Parameter
472 objects and 'return_annotation'. All arguments are optional.
475 objects and 'return_annotation'. All arguments are optional.
473 '''
476 '''
474
477
475 if parameters is None:
478 if parameters is None:
476 params = OrderedDict()
479 params = OrderedDict()
477 else:
480 else:
478 if __validate_parameters__:
481 if __validate_parameters__:
479 params = OrderedDict()
482 params = OrderedDict()
480 top_kind = _POSITIONAL_ONLY
483 top_kind = _POSITIONAL_ONLY
481
484
482 for idx, param in enumerate(parameters):
485 for idx, param in enumerate(parameters):
483 kind = param.kind
486 kind = param.kind
484 if kind < top_kind:
487 if kind < top_kind:
485 msg = 'wrong parameter order: {0} before {1}'
488 msg = 'wrong parameter order: {0} before {1}'
486 msg = msg.format(top_kind, param.kind)
489 msg = msg.format(top_kind, param.kind)
487 raise ValueError(msg)
490 raise ValueError(msg)
488 else:
491 else:
489 top_kind = kind
492 top_kind = kind
490
493
491 name = param.name
494 name = param.name
492 if name is None:
495 if name is None:
493 name = str(idx)
496 name = str(idx)
494 param = param.replace(name=name)
497 param = param.replace(name=name)
495
498
496 if name in params:
499 if name in params:
497 msg = 'duplicate parameter name: {0!r}'.format(name)
500 msg = 'duplicate parameter name: {0!r}'.format(name)
498 raise ValueError(msg)
501 raise ValueError(msg)
499 params[name] = param
502 params[name] = param
500 else:
503 else:
501 params = OrderedDict(((param.name, param)
504 params = OrderedDict(((param.name, param)
502 for param in parameters))
505 for param in parameters))
503
506
504 self._parameters = params
507 self._parameters = params
505 self._return_annotation = return_annotation
508 self._return_annotation = return_annotation
506
509
507 @classmethod
510 @classmethod
508 def from_function(cls, func):
511 def from_function(cls, func):
509 '''Constructs Signature for the given python function'''
512 '''Constructs Signature for the given python function'''
510
513
511 if not isinstance(func, types.FunctionType):
514 if not isinstance(func, types.FunctionType):
512 raise TypeError('{0!r} is not a Python function'.format(func))
515 raise TypeError('{0!r} is not a Python function'.format(func))
513
516
514 Parameter = cls._parameter_cls
517 Parameter = cls._parameter_cls
515
518
516 # Parameter information.
519 # Parameter information.
517 func_code = func.__code__
520 func_code = func.__code__
518 pos_count = func_code.co_argcount
521 pos_count = func_code.co_argcount
519 arg_names = func_code.co_varnames
522 arg_names = func_code.co_varnames
520 positional = tuple(arg_names[:pos_count])
523 positional = tuple(arg_names[:pos_count])
521 keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0)
524 keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0)
522 keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
525 keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)]
523 annotations = getattr(func, '__annotations__', {})
526 annotations = getattr(func, '__annotations__', {})
524 defaults = func.__defaults__
527 defaults = func.__defaults__
525 kwdefaults = getattr(func, '__kwdefaults__', None)
528 kwdefaults = getattr(func, '__kwdefaults__', None)
526
529
527 if defaults:
530 if defaults:
528 pos_default_count = len(defaults)
531 pos_default_count = len(defaults)
529 else:
532 else:
530 pos_default_count = 0
533 pos_default_count = 0
531
534
532 parameters = []
535 parameters = []
533
536
534 # Non-keyword-only parameters w/o defaults.
537 # Non-keyword-only parameters w/o defaults.
535 non_default_count = pos_count - pos_default_count
538 non_default_count = pos_count - pos_default_count
536 for name in positional[:non_default_count]:
539 for name in positional[:non_default_count]:
537 annotation = annotations.get(name, _empty)
540 annotation = annotations.get(name, _empty)
538 parameters.append(Parameter(name, annotation=annotation,
541 parameters.append(Parameter(name, annotation=annotation,
539 kind=_POSITIONAL_OR_KEYWORD))
542 kind=_POSITIONAL_OR_KEYWORD))
540
543
541 # ... w/ defaults.
544 # ... w/ defaults.
542 for offset, name in enumerate(positional[non_default_count:]):
545 for offset, name in enumerate(positional[non_default_count:]):
543 annotation = annotations.get(name, _empty)
546 annotation = annotations.get(name, _empty)
544 parameters.append(Parameter(name, annotation=annotation,
547 parameters.append(Parameter(name, annotation=annotation,
545 kind=_POSITIONAL_OR_KEYWORD,
548 kind=_POSITIONAL_OR_KEYWORD,
546 default=defaults[offset]))
549 default=defaults[offset]))
547
550
548 # *args
551 # *args
549 if func_code.co_flags & 0x04:
552 if func_code.co_flags & 0x04:
550 name = arg_names[pos_count + keyword_only_count]
553 name = arg_names[pos_count + keyword_only_count]
551 annotation = annotations.get(name, _empty)
554 annotation = annotations.get(name, _empty)
552 parameters.append(Parameter(name, annotation=annotation,
555 parameters.append(Parameter(name, annotation=annotation,
553 kind=_VAR_POSITIONAL))
556 kind=_VAR_POSITIONAL))
554
557
555 # Keyword-only parameters.
558 # Keyword-only parameters.
556 for name in keyword_only:
559 for name in keyword_only:
557 default = _empty
560 default = _empty
558 if kwdefaults is not None:
561 if kwdefaults is not None:
559 default = kwdefaults.get(name, _empty)
562 default = kwdefaults.get(name, _empty)
560
563
561 annotation = annotations.get(name, _empty)
564 annotation = annotations.get(name, _empty)
562 parameters.append(Parameter(name, annotation=annotation,
565 parameters.append(Parameter(name, annotation=annotation,
563 kind=_KEYWORD_ONLY,
566 kind=_KEYWORD_ONLY,
564 default=default))
567 default=default))
565 # **kwargs
568 # **kwargs
566 if func_code.co_flags & 0x08:
569 if func_code.co_flags & 0x08:
567 index = pos_count + keyword_only_count
570 index = pos_count + keyword_only_count
568 if func_code.co_flags & 0x04:
571 if func_code.co_flags & 0x04:
569 index += 1
572 index += 1
570
573
571 name = arg_names[index]
574 name = arg_names[index]
572 annotation = annotations.get(name, _empty)
575 annotation = annotations.get(name, _empty)
573 parameters.append(Parameter(name, annotation=annotation,
576 parameters.append(Parameter(name, annotation=annotation,
574 kind=_VAR_KEYWORD))
577 kind=_VAR_KEYWORD))
575
578
576 return cls(parameters,
579 return cls(parameters,
577 return_annotation=annotations.get('return', _empty),
580 return_annotation=annotations.get('return', _empty),
578 __validate_parameters__=False)
581 __validate_parameters__=False)
579
582
580 @property
583 @property
581 def parameters(self):
584 def parameters(self):
582 try:
585 try:
583 return types.MappingProxyType(self._parameters)
586 return types.MappingProxyType(self._parameters)
584 except AttributeError:
587 except AttributeError:
585 return OrderedDict(self._parameters.items())
588 return OrderedDict(self._parameters.items())
586
589
587 @property
590 @property
588 def return_annotation(self):
591 def return_annotation(self):
589 return self._return_annotation
592 return self._return_annotation
590
593
591 def replace(self, parameters=_void, return_annotation=_void):
594 def replace(self, parameters=_void, return_annotation=_void):
592 '''Creates a customized copy of the Signature.
595 '''Creates a customized copy of the Signature.
593 Pass 'parameters' and/or 'return_annotation' arguments
596 Pass 'parameters' and/or 'return_annotation' arguments
594 to override them in the new copy.
597 to override them in the new copy.
595 '''
598 '''
596
599
597 if parameters is _void:
600 if parameters is _void:
598 parameters = self.parameters.values()
601 parameters = self.parameters.values()
599
602
600 if return_annotation is _void:
603 if return_annotation is _void:
601 return_annotation = self._return_annotation
604 return_annotation = self._return_annotation
602
605
603 return type(self)(parameters,
606 return type(self)(parameters,
604 return_annotation=return_annotation)
607 return_annotation=return_annotation)
605
608
606 def __hash__(self):
609 def __hash__(self):
607 msg = "unhashable type: '{0}'".format(self.__class__.__name__)
610 msg = "unhashable type: '{0}'".format(self.__class__.__name__)
608 raise TypeError(msg)
611 raise TypeError(msg)
609
612
610 def __eq__(self, other):
613 def __eq__(self, other):
611 if (not issubclass(type(other), Signature) or
614 if (not issubclass(type(other), Signature) or
612 self.return_annotation != other.return_annotation or
615 self.return_annotation != other.return_annotation or
613 len(self.parameters) != len(other.parameters)):
616 len(self.parameters) != len(other.parameters)):
614 return False
617 return False
615
618
616 other_positions = dict((param, idx)
619 other_positions = dict((param, idx)
617 for idx, param in enumerate(other.parameters.keys()))
620 for idx, param in enumerate(other.parameters.keys()))
618
621
619 for idx, (param_name, param) in enumerate(self.parameters.items()):
622 for idx, (param_name, param) in enumerate(self.parameters.items()):
620 if param.kind == _KEYWORD_ONLY:
623 if param.kind == _KEYWORD_ONLY:
621 try:
624 try:
622 other_param = other.parameters[param_name]
625 other_param = other.parameters[param_name]
623 except KeyError:
626 except KeyError:
624 return False
627 return False
625 else:
628 else:
626 if param != other_param:
629 if param != other_param:
627 return False
630 return False
628 else:
631 else:
629 try:
632 try:
630 other_idx = other_positions[param_name]
633 other_idx = other_positions[param_name]
631 except KeyError:
634 except KeyError:
632 return False
635 return False
633 else:
636 else:
634 if (idx != other_idx or
637 if (idx != other_idx or
635 param != other.parameters[param_name]):
638 param != other.parameters[param_name]):
636 return False
639 return False
637
640
638 return True
641 return True
639
642
640 def __ne__(self, other):
643 def __ne__(self, other):
641 return not self.__eq__(other)
644 return not self.__eq__(other)
642
645
643 def _bind(self, args, kwargs, partial=False):
646 def _bind(self, args, kwargs, partial=False):
644 '''Private method. Don't use directly.'''
647 '''Private method. Don't use directly.'''
645
648
646 arguments = OrderedDict()
649 arguments = OrderedDict()
647
650
648 parameters = iter(self.parameters.values())
651 parameters = iter(self.parameters.values())
649 parameters_ex = ()
652 parameters_ex = ()
650 arg_vals = iter(args)
653 arg_vals = iter(args)
651
654
652 if partial:
655 if partial:
653 # Support for binding arguments to 'functools.partial' objects.
656 # Support for binding arguments to 'functools.partial' objects.
654 # See 'functools.partial' case in 'signature()' implementation
657 # See 'functools.partial' case in 'signature()' implementation
655 # for details.
658 # for details.
656 for param_name, param in self.parameters.items():
659 for param_name, param in self.parameters.items():
657 if (param._partial_kwarg and param_name not in kwargs):
660 if (param._partial_kwarg and param_name not in kwargs):
658 # Simulating 'functools.partial' behavior
661 # Simulating 'functools.partial' behavior
659 kwargs[param_name] = param.default
662 kwargs[param_name] = param.default
660
663
661 while True:
664 while True:
662 # Let's iterate through the positional arguments and corresponding
665 # Let's iterate through the positional arguments and corresponding
663 # parameters
666 # parameters
664 try:
667 try:
665 arg_val = next(arg_vals)
668 arg_val = next(arg_vals)
666 except StopIteration:
669 except StopIteration:
667 # No more positional arguments
670 # No more positional arguments
668 try:
671 try:
669 param = next(parameters)
672 param = next(parameters)
670 except StopIteration:
673 except StopIteration:
671 # No more parameters. That's it. Just need to check that
674 # No more parameters. That's it. Just need to check that
672 # we have no `kwargs` after this while loop
675 # we have no `kwargs` after this while loop
673 break
676 break
674 else:
677 else:
675 if param.kind == _VAR_POSITIONAL:
678 if param.kind == _VAR_POSITIONAL:
676 # That's OK, just empty *args. Let's start parsing
679 # That's OK, just empty *args. Let's start parsing
677 # kwargs
680 # kwargs
678 break
681 break
679 elif param.name in kwargs:
682 elif param.name in kwargs:
680 if param.kind == _POSITIONAL_ONLY:
683 if param.kind == _POSITIONAL_ONLY:
681 msg = '{arg!r} parameter is positional only, ' \
684 msg = '{arg!r} parameter is positional only, ' \
682 'but was passed as a keyword'
685 'but was passed as a keyword'
683 msg = msg.format(arg=param.name)
686 msg = msg.format(arg=param.name)
684 raise TypeError(msg)
687 raise TypeError(msg)
685 parameters_ex = (param,)
688 parameters_ex = (param,)
686 break
689 break
687 elif (param.kind == _VAR_KEYWORD or
690 elif (param.kind == _VAR_KEYWORD or
688 param.default is not _empty):
691 param.default is not _empty):
689 # That's fine too - we have a default value for this
692 # That's fine too - we have a default value for this
690 # parameter. So, lets start parsing `kwargs`, starting
693 # parameter. So, lets start parsing `kwargs`, starting
691 # with the current parameter
694 # with the current parameter
692 parameters_ex = (param,)
695 parameters_ex = (param,)
693 break
696 break
694 else:
697 else:
695 if partial:
698 if partial:
696 parameters_ex = (param,)
699 parameters_ex = (param,)
697 break
700 break
698 else:
701 else:
699 msg = '{arg!r} parameter lacking default value'
702 msg = '{arg!r} parameter lacking default value'
700 msg = msg.format(arg=param.name)
703 msg = msg.format(arg=param.name)
701 raise TypeError(msg)
704 raise TypeError(msg)
702 else:
705 else:
703 # We have a positional argument to process
706 # We have a positional argument to process
704 try:
707 try:
705 param = next(parameters)
708 param = next(parameters)
706 except StopIteration:
709 except StopIteration:
707 raise TypeError('too many positional arguments')
710 raise TypeError('too many positional arguments')
708 else:
711 else:
709 if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
712 if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
710 # Looks like we have no parameter for this positional
713 # Looks like we have no parameter for this positional
711 # argument
714 # argument
712 raise TypeError('too many positional arguments')
715 raise TypeError('too many positional arguments')
713
716
714 if param.kind == _VAR_POSITIONAL:
717 if param.kind == _VAR_POSITIONAL:
715 # We have an '*args'-like argument, let's fill it with
718 # We have an '*args'-like argument, let's fill it with
716 # all positional arguments we have left and move on to
719 # all positional arguments we have left and move on to
717 # the next phase
720 # the next phase
718 values = [arg_val]
721 values = [arg_val]
719 values.extend(arg_vals)
722 values.extend(arg_vals)
720 arguments[param.name] = tuple(values)
723 arguments[param.name] = tuple(values)
721 break
724 break
722
725
723 if param.name in kwargs:
726 if param.name in kwargs:
724 raise TypeError('multiple values for argument '
727 raise TypeError('multiple values for argument '
725 '{arg!r}'.format(arg=param.name))
728 '{arg!r}'.format(arg=param.name))
726
729
727 arguments[param.name] = arg_val
730 arguments[param.name] = arg_val
728
731
729 # Now, we iterate through the remaining parameters to process
732 # Now, we iterate through the remaining parameters to process
730 # keyword arguments
733 # keyword arguments
731 kwargs_param = None
734 kwargs_param = None
732 for param in itertools.chain(parameters_ex, parameters):
735 for param in itertools.chain(parameters_ex, parameters):
733 if param.kind == _POSITIONAL_ONLY:
736 if param.kind == _POSITIONAL_ONLY:
734 # This should never happen in case of a properly built
737 # This should never happen in case of a properly built
735 # Signature object (but let's have this check here
738 # Signature object (but let's have this check here
736 # to ensure correct behaviour just in case)
739 # to ensure correct behaviour just in case)
737 raise TypeError('{arg!r} parameter is positional only, '
740 raise TypeError('{arg!r} parameter is positional only, '
738 'but was passed as a keyword'. \
741 'but was passed as a keyword'. \
739 format(arg=param.name))
742 format(arg=param.name))
740
743
741 if param.kind == _VAR_KEYWORD:
744 if param.kind == _VAR_KEYWORD:
742 # Memorize that we have a '**kwargs'-like parameter
745 # Memorize that we have a '**kwargs'-like parameter
743 kwargs_param = param
746 kwargs_param = param
744 continue
747 continue
745
748
746 param_name = param.name
749 param_name = param.name
747 try:
750 try:
748 arg_val = kwargs.pop(param_name)
751 arg_val = kwargs.pop(param_name)
749 except KeyError:
752 except KeyError:
750 # We have no value for this parameter. It's fine though,
753 # We have no value for this parameter. It's fine though,
751 # if it has a default value, or it is an '*args'-like
754 # if it has a default value, or it is an '*args'-like
752 # parameter, left alone by the processing of positional
755 # parameter, left alone by the processing of positional
753 # arguments.
756 # arguments.
754 if (not partial and param.kind != _VAR_POSITIONAL and
757 if (not partial and param.kind != _VAR_POSITIONAL and
755 param.default is _empty):
758 param.default is _empty):
756 raise TypeError('{arg!r} parameter lacking default value'. \
759 raise TypeError('{arg!r} parameter lacking default value'. \
757 format(arg=param_name))
760 format(arg=param_name))
758
761
759 else:
762 else:
760 arguments[param_name] = arg_val
763 arguments[param_name] = arg_val
761
764
762 if kwargs:
765 if kwargs:
763 if kwargs_param is not None:
766 if kwargs_param is not None:
764 # Process our '**kwargs'-like parameter
767 # Process our '**kwargs'-like parameter
765 arguments[kwargs_param.name] = kwargs
768 arguments[kwargs_param.name] = kwargs
766 else:
769 else:
767 raise TypeError('too many keyword arguments')
770 raise TypeError('too many keyword arguments')
768
771
769 return self._bound_arguments_cls(self, arguments)
772 return self._bound_arguments_cls(self, arguments)
770
773
771 def bind(self, *args, **kwargs):
774 def bind(self, *args, **kwargs):
772 '''Get a :class:`BoundArguments` object, that maps the passed `args`
775 '''Get a :class:`BoundArguments` object, that maps the passed `args`
773 and `kwargs` to the function's signature. Raises :exc:`TypeError`
776 and `kwargs` to the function's signature. Raises :exc:`TypeError`
774 if the passed arguments can not be bound.
777 if the passed arguments can not be bound.
775 '''
778 '''
776 return self._bind(args, kwargs)
779 return self._bind(args, kwargs)
777
780
778 def bind_partial(self, *args, **kwargs):
781 def bind_partial(self, *args, **kwargs):
779 '''Get a :class:`BoundArguments` object, that partially maps the
782 '''Get a :class:`BoundArguments` object, that partially maps the
780 passed `args` and `kwargs` to the function's signature.
783 passed `args` and `kwargs` to the function's signature.
781 Raises :exc:`TypeError` if the passed arguments can not be bound.
784 Raises :exc:`TypeError` if the passed arguments can not be bound.
782 '''
785 '''
783 return self._bind(args, kwargs, partial=True)
786 return self._bind(args, kwargs, partial=True)
784
787
785 def __str__(self):
788 def __str__(self):
786 result = []
789 result = []
787 render_kw_only_separator = True
790 render_kw_only_separator = True
788 for idx, param in enumerate(self.parameters.values()):
791 for idx, param in enumerate(self.parameters.values()):
789 formatted = str(param)
792 formatted = str(param)
790
793
791 kind = param.kind
794 kind = param.kind
792 if kind == _VAR_POSITIONAL:
795 if kind == _VAR_POSITIONAL:
793 # OK, we have an '*args'-like parameter, so we won't need
796 # OK, we have an '*args'-like parameter, so we won't need
794 # a '*' to separate keyword-only arguments
797 # a '*' to separate keyword-only arguments
795 render_kw_only_separator = False
798 render_kw_only_separator = False
796 elif kind == _KEYWORD_ONLY and render_kw_only_separator:
799 elif kind == _KEYWORD_ONLY and render_kw_only_separator:
797 # We have a keyword-only parameter to render and we haven't
800 # We have a keyword-only parameter to render and we haven't
798 # rendered an '*args'-like parameter before, so add a '*'
801 # rendered an '*args'-like parameter before, so add a '*'
799 # separator to the parameters list ("foo(arg1, *, arg2)" case)
802 # separator to the parameters list ("foo(arg1, *, arg2)" case)
800 result.append('*')
803 result.append('*')
801 # This condition should be only triggered once, so
804 # This condition should be only triggered once, so
802 # reset the flag
805 # reset the flag
803 render_kw_only_separator = False
806 render_kw_only_separator = False
804
807
805 result.append(formatted)
808 result.append(formatted)
806
809
807 rendered = '({0})'.format(', '.join(result))
810 rendered = '({0})'.format(', '.join(result))
808
811
809 if self.return_annotation is not _empty:
812 if self.return_annotation is not _empty:
810 anno = formatannotation(self.return_annotation)
813 anno = formatannotation(self.return_annotation)
811 rendered += ' -> {0}'.format(anno)
814 rendered += ' -> {0}'.format(anno)
812
815
813 return rendered
816 return rendered
General Comments 0
You need to be logged in to leave comments. Login now