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