1*bcb5dc79SHONG Yifan# Copyright 2018 The Bazel Authors. All rights reserved. 2*bcb5dc79SHONG Yifan# 3*bcb5dc79SHONG Yifan# Licensed under the Apache License, Version 2.0 (the "License"); 4*bcb5dc79SHONG Yifan# you may not use this file except in compliance with the License. 5*bcb5dc79SHONG Yifan# You may obtain a copy of the License at 6*bcb5dc79SHONG Yifan# 7*bcb5dc79SHONG Yifan# http://www.apache.org/licenses/LICENSE-2.0 8*bcb5dc79SHONG Yifan# 9*bcb5dc79SHONG Yifan# Unless required by applicable law or agreed to in writing, software 10*bcb5dc79SHONG Yifan# distributed under the License is distributed on an "AS IS" BASIS, 11*bcb5dc79SHONG Yifan# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*bcb5dc79SHONG Yifan# See the License for the specific language governing permissions and 13*bcb5dc79SHONG Yifan# limitations under the License. 14*bcb5dc79SHONG Yifan 15*bcb5dc79SHONG Yifan"""Starlark module for working with partial function objects. 16*bcb5dc79SHONG Yifan 17*bcb5dc79SHONG YifanPartial function objects allow some parameters are bound before the call. 18*bcb5dc79SHONG Yifan 19*bcb5dc79SHONG YifanSimilar to https://docs.python.org/3/library/functools.html#functools.partial. 20*bcb5dc79SHONG Yifan""" 21*bcb5dc79SHONG Yifan 22*bcb5dc79SHONG Yifan# create instance singletons to avoid unnecessary allocations 23*bcb5dc79SHONG Yifan_a_dict_type = type({}) 24*bcb5dc79SHONG Yifan_a_tuple_type = type(()) 25*bcb5dc79SHONG Yifan_a_struct_type = type(struct()) 26*bcb5dc79SHONG Yifan 27*bcb5dc79SHONG Yifandef _call(partial, *args, **kwargs): 28*bcb5dc79SHONG Yifan """Calls a partial created using `make`. 29*bcb5dc79SHONG Yifan 30*bcb5dc79SHONG Yifan Args: 31*bcb5dc79SHONG Yifan partial: The partial to be called. 32*bcb5dc79SHONG Yifan *args: Additional positional arguments to be appended to the ones given to 33*bcb5dc79SHONG Yifan make. 34*bcb5dc79SHONG Yifan **kwargs: Additional keyword arguments to augment and override the ones 35*bcb5dc79SHONG Yifan given to make. 36*bcb5dc79SHONG Yifan 37*bcb5dc79SHONG Yifan Returns: 38*bcb5dc79SHONG Yifan Whatever the function in the partial returns. 39*bcb5dc79SHONG Yifan """ 40*bcb5dc79SHONG Yifan function_args = partial.args + args 41*bcb5dc79SHONG Yifan function_kwargs = dict(partial.kwargs) 42*bcb5dc79SHONG Yifan function_kwargs.update(kwargs) 43*bcb5dc79SHONG Yifan return partial.function(*function_args, **function_kwargs) 44*bcb5dc79SHONG Yifan 45*bcb5dc79SHONG Yifandef _make(func, *args, **kwargs): 46*bcb5dc79SHONG Yifan """Creates a partial that can be called using `call`. 47*bcb5dc79SHONG Yifan 48*bcb5dc79SHONG Yifan A partial can have args assigned to it at the make site, and can have args 49*bcb5dc79SHONG Yifan passed to it at the call sites. 50*bcb5dc79SHONG Yifan 51*bcb5dc79SHONG Yifan A partial 'function' can be defined with positional args and kwargs: 52*bcb5dc79SHONG Yifan 53*bcb5dc79SHONG Yifan # function with no args 54*bcb5dc79SHONG Yifan ``` 55*bcb5dc79SHONG Yifan def function1(): 56*bcb5dc79SHONG Yifan ... 57*bcb5dc79SHONG Yifan ``` 58*bcb5dc79SHONG Yifan 59*bcb5dc79SHONG Yifan # function with 2 args 60*bcb5dc79SHONG Yifan ``` 61*bcb5dc79SHONG Yifan def function2(arg1, arg2): 62*bcb5dc79SHONG Yifan ... 63*bcb5dc79SHONG Yifan ``` 64*bcb5dc79SHONG Yifan 65*bcb5dc79SHONG Yifan # function with 2 args and keyword args 66*bcb5dc79SHONG Yifan ``` 67*bcb5dc79SHONG Yifan def function3(arg1, arg2, x, y): 68*bcb5dc79SHONG Yifan ... 69*bcb5dc79SHONG Yifan ``` 70*bcb5dc79SHONG Yifan 71*bcb5dc79SHONG Yifan The positional args passed to the function are the args passed into make 72*bcb5dc79SHONG Yifan followed by any additional positional args given to call. The below example 73*bcb5dc79SHONG Yifan illustrates a function with two positional arguments where one is supplied by 74*bcb5dc79SHONG Yifan make and the other by call: 75*bcb5dc79SHONG Yifan 76*bcb5dc79SHONG Yifan # function demonstrating 1 arg at make site, and 1 arg at call site 77*bcb5dc79SHONG Yifan ``` 78*bcb5dc79SHONG Yifan def _foo(make_arg1, func_arg1): 79*bcb5dc79SHONG Yifan print(make_arg1 + " " + func_arg1 + "!") 80*bcb5dc79SHONG Yifan ``` 81*bcb5dc79SHONG Yifan 82*bcb5dc79SHONG Yifan For example: 83*bcb5dc79SHONG Yifan 84*bcb5dc79SHONG Yifan ``` 85*bcb5dc79SHONG Yifan hi_func = partial.make(_foo, "Hello") 86*bcb5dc79SHONG Yifan bye_func = partial.make(_foo, "Goodbye") 87*bcb5dc79SHONG Yifan partial.call(hi_func, "Jennifer") 88*bcb5dc79SHONG Yifan partial.call(hi_func, "Dave") 89*bcb5dc79SHONG Yifan partial.call(bye_func, "Jennifer") 90*bcb5dc79SHONG Yifan partial.call(bye_func, "Dave") 91*bcb5dc79SHONG Yifan ``` 92*bcb5dc79SHONG Yifan 93*bcb5dc79SHONG Yifan prints: 94*bcb5dc79SHONG Yifan 95*bcb5dc79SHONG Yifan ``` 96*bcb5dc79SHONG Yifan "Hello, Jennifer!" 97*bcb5dc79SHONG Yifan "Hello, Dave!" 98*bcb5dc79SHONG Yifan "Goodbye, Jennifer!" 99*bcb5dc79SHONG Yifan "Goodbye, Dave!" 100*bcb5dc79SHONG Yifan ``` 101*bcb5dc79SHONG Yifan 102*bcb5dc79SHONG Yifan The keyword args given to the function are the kwargs passed into make 103*bcb5dc79SHONG Yifan unioned with the keyword args given to call. In case of a conflict, the 104*bcb5dc79SHONG Yifan keyword args given to call take precedence. This allows you to set a default 105*bcb5dc79SHONG Yifan value for keyword arguments and override it at the call site. 106*bcb5dc79SHONG Yifan 107*bcb5dc79SHONG Yifan Example with a make site arg, a call site arg, a make site kwarg and a 108*bcb5dc79SHONG Yifan call site kwarg: 109*bcb5dc79SHONG Yifan 110*bcb5dc79SHONG Yifan ``` 111*bcb5dc79SHONG Yifan def _foo(make_arg1, call_arg1, make_location, call_location): 112*bcb5dc79SHONG Yifan print(make_arg1 + " is from " + make_location + " and " + 113*bcb5dc79SHONG Yifan call_arg1 + " is from " + call_location + "!") 114*bcb5dc79SHONG Yifan 115*bcb5dc79SHONG Yifan func = partial.make(_foo, "Ben", make_location="Hollywood") 116*bcb5dc79SHONG Yifan partial.call(func, "Jennifer", call_location="Denver") 117*bcb5dc79SHONG Yifan ``` 118*bcb5dc79SHONG Yifan 119*bcb5dc79SHONG Yifan Prints "Ben is from Hollywood and Jennifer is from Denver!". 120*bcb5dc79SHONG Yifan 121*bcb5dc79SHONG Yifan ``` 122*bcb5dc79SHONG Yifan partial.call(func, "Jennifer", make_location="LA", call_location="Denver") 123*bcb5dc79SHONG Yifan ``` 124*bcb5dc79SHONG Yifan 125*bcb5dc79SHONG Yifan Prints "Ben is from LA and Jennifer is from Denver!". 126*bcb5dc79SHONG Yifan 127*bcb5dc79SHONG Yifan Note that keyword args may not overlap with positional args, regardless of 128*bcb5dc79SHONG Yifan whether they are given during the make or call step. For instance, you can't 129*bcb5dc79SHONG Yifan do: 130*bcb5dc79SHONG Yifan 131*bcb5dc79SHONG Yifan ``` 132*bcb5dc79SHONG Yifan def foo(x): 133*bcb5dc79SHONG Yifan pass 134*bcb5dc79SHONG Yifan 135*bcb5dc79SHONG Yifan func = partial.make(foo, 1) 136*bcb5dc79SHONG Yifan partial.call(func, x=2) 137*bcb5dc79SHONG Yifan ``` 138*bcb5dc79SHONG Yifan 139*bcb5dc79SHONG Yifan Args: 140*bcb5dc79SHONG Yifan func: The function to be called. 141*bcb5dc79SHONG Yifan *args: Positional arguments to be passed to function. 142*bcb5dc79SHONG Yifan **kwargs: Keyword arguments to be passed to function. Note that these can 143*bcb5dc79SHONG Yifan be overridden at the call sites. 144*bcb5dc79SHONG Yifan 145*bcb5dc79SHONG Yifan Returns: 146*bcb5dc79SHONG Yifan A new `partial` that can be called using `call` 147*bcb5dc79SHONG Yifan """ 148*bcb5dc79SHONG Yifan return struct(function = func, args = args, kwargs = kwargs) 149*bcb5dc79SHONG Yifan 150*bcb5dc79SHONG Yifandef _is_instance(v): 151*bcb5dc79SHONG Yifan """Returns True if v is a partial created using `make`. 152*bcb5dc79SHONG Yifan 153*bcb5dc79SHONG Yifan Args: 154*bcb5dc79SHONG Yifan v: The value to check. 155*bcb5dc79SHONG Yifan 156*bcb5dc79SHONG Yifan Returns: 157*bcb5dc79SHONG Yifan True if v was created by `make`, False otherwise. 158*bcb5dc79SHONG Yifan """ 159*bcb5dc79SHONG Yifan 160*bcb5dc79SHONG Yifan # Note that in bazel 3.7.0 and earlier, type(v.function) is the same 161*bcb5dc79SHONG Yifan # as the type of a function even if v.function is a rule. But we 162*bcb5dc79SHONG Yifan # cannot rely on this in later bazels due to breaking change 163*bcb5dc79SHONG Yifan # https://github.com/bazelbuild/bazel/commit/e379ece1908aafc852f9227175dd3283312b4b82 164*bcb5dc79SHONG Yifan # 165*bcb5dc79SHONG Yifan # Since this check is heuristic anyway, we simply check for the 166*bcb5dc79SHONG Yifan # presence of a "function" attribute without checking its type. 167*bcb5dc79SHONG Yifan return type(v) == _a_struct_type and \ 168*bcb5dc79SHONG Yifan hasattr(v, "function") and \ 169*bcb5dc79SHONG Yifan hasattr(v, "args") and type(v.args) == _a_tuple_type and \ 170*bcb5dc79SHONG Yifan hasattr(v, "kwargs") and type(v.kwargs) == _a_dict_type 171*bcb5dc79SHONG Yifan 172*bcb5dc79SHONG Yifanpartial = struct( 173*bcb5dc79SHONG Yifan make = _make, 174*bcb5dc79SHONG Yifan call = _call, 175*bcb5dc79SHONG Yifan is_instance = _is_instance, 176*bcb5dc79SHONG Yifan) 177