Implements `getargspec` for `functools.partial` objects. Args: obj: The `functools.partial` obeject Returns: An `inspect.ArgSpec` Raises: ValueError: When callable's signature can not be expressed with ArgSpec.
(obj)
| 148 | |
| 149 | |
| 150 | def _get_argspec_for_partial(obj): |
| 151 | """Implements `getargspec` for `functools.partial` objects. |
| 152 | |
| 153 | Args: |
| 154 | obj: The `functools.partial` obeject |
| 155 | Returns: |
| 156 | An `inspect.ArgSpec` |
| 157 | Raises: |
| 158 | ValueError: When callable's signature can not be expressed with |
| 159 | ArgSpec. |
| 160 | """ |
| 161 | # When callable is a functools.partial object, we construct its ArgSpec with |
| 162 | # following strategy: |
| 163 | # - If callable partial contains default value for positional arguments (ie. |
| 164 | # object.args), then final ArgSpec doesn't contain those positional arguments. |
| 165 | # - If callable partial contains default value for keyword arguments (ie. |
| 166 | # object.keywords), then we merge them with wrapped target. Default values |
| 167 | # from callable partial takes precedence over those from wrapped target. |
| 168 | # |
| 169 | # However, there is a case where it is impossible to construct a valid |
| 170 | # ArgSpec. Python requires arguments that have no default values must be |
| 171 | # defined before those with default values. ArgSpec structure is only valid |
| 172 | # when this presumption holds true because default values are expressed as a |
| 173 | # tuple of values without keywords and they are always assumed to belong to |
| 174 | # last K arguments where K is number of default values present. |
| 175 | # |
| 176 | # Since functools.partial can give default value to any argument, this |
| 177 | # presumption may no longer hold in some cases. For example: |
| 178 | # |
| 179 | # def func(m, n): |
| 180 | # return 2 * m + n |
| 181 | # partialed = functools.partial(func, m=1) |
| 182 | # |
| 183 | # This example will result in m having a default value but n doesn't. This is |
| 184 | # usually not allowed in Python and can not be expressed in ArgSpec correctly. |
| 185 | # |
| 186 | # Thus, we must detect cases like this by finding first argument with default |
| 187 | # value and ensures all following arguments also have default values. When |
| 188 | # this is not true, a ValueError is raised. |
| 189 | |
| 190 | n_prune_args = len(obj.args) |
| 191 | partial_keywords = obj.keywords or {} |
| 192 | |
| 193 | args, varargs, keywords, defaults = getargspec(obj.func) |
| 194 | |
| 195 | # Pruning first n_prune_args arguments. |
| 196 | args = args[n_prune_args:] |
| 197 | |
| 198 | # Partial function may give default value to any argument, therefore length |
| 199 | # of default value list must be len(args) to allow each argument to |
| 200 | # potentially be given a default value. |
| 201 | no_default = object() |
| 202 | all_defaults = [no_default] * len(args) |
| 203 | |
| 204 | if defaults: |
| 205 | all_defaults[-len(defaults):] = defaults |
| 206 | |
| 207 | # Fill in default values provided by partial function in all_defaults. |
no test coverage detected