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