Add support for 'functools.partial' like functionality. (#34)
* Add support for 'functools.partial' like functionality. https://docs.python.org/3/library/functools.html#functools.partial
This commit is contained in:
parent
a5e23fd4c0
commit
d46b607d98
1
BUILD
1
BUILD
|
@ -24,6 +24,7 @@ skylark_library(
|
|||
deps = [
|
||||
"//lib:collections",
|
||||
"//lib:dicts",
|
||||
"//lib:partial",
|
||||
"//lib:paths",
|
||||
"//lib:selects",
|
||||
"//lib:sets",
|
||||
|
|
|
@ -15,3 +15,4 @@ Nathan Herring <nherring@google.com>
|
|||
Laurent Le Brun <laurentlb@google.com>
|
||||
Dmitry Lomov <dslomov@google.com>
|
||||
Jingwen Chen <jingwen@google.com>
|
||||
Dave MacLachlan <dmaclach@google.com>
|
||||
|
|
1
lib.bzl
1
lib.bzl
|
@ -16,6 +16,7 @@
|
|||
|
||||
load("//lib:collections.bzl", "collections")
|
||||
load("//lib:dicts.bzl", "dicts")
|
||||
load("//lib:partial.bzl", "partial")
|
||||
load("//lib:paths.bzl", "paths")
|
||||
load("//lib:selects.bzl", "selects")
|
||||
load("//lib:sets.bzl", "sets")
|
||||
|
|
|
@ -21,6 +21,11 @@ skylark_library(
|
|||
srcs = ["dicts.bzl"],
|
||||
)
|
||||
|
||||
skylark_library(
|
||||
name = "partial",
|
||||
srcs = ["partial.bzl"],
|
||||
)
|
||||
|
||||
skylark_library(
|
||||
name = "paths",
|
||||
srcs = ["paths.bzl"],
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
# Copyright 2018 The Bazel Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Skylark module for working with function objects where some parameters are
|
||||
bound before the call.
|
||||
|
||||
Similar to https://docs.python.org/3/library/functools.html#functools.partial.
|
||||
"""
|
||||
|
||||
def _call(partial, *args, **kwargs):
|
||||
"""Calls a partial created using `make`.
|
||||
|
||||
Args:
|
||||
partial: The partial to be called.
|
||||
*args: Additional positional arguments to be appended to the ones given to
|
||||
make.
|
||||
**kwargs: Additional keyword arguments to augment and override the ones
|
||||
given to make.
|
||||
|
||||
Returns:
|
||||
Whatever the function in the partial returns.
|
||||
"""
|
||||
function_args = partial.args + args
|
||||
function_kwargs = dict(partial.kwargs)
|
||||
function_kwargs.update(kwargs)
|
||||
return partial.function(*function_args, **function_kwargs)
|
||||
|
||||
def _make(func, *args, **kwargs):
|
||||
"""Creates a partial that can be called using `call`.
|
||||
|
||||
A partial can have args assigned to it at the make site, and can have args
|
||||
passed to it at the call sites.
|
||||
|
||||
A partial 'function' can be defined with positional args and kwargs:
|
||||
|
||||
# function with no args
|
||||
def function1():
|
||||
...
|
||||
|
||||
# function with 2 args
|
||||
def function2(arg1, arg2):
|
||||
...
|
||||
|
||||
# function with 2 args and keyword args
|
||||
def function3(arg1, arg2, x, y):
|
||||
...
|
||||
|
||||
The positional args passed to the function are the args passed into make
|
||||
followed by any additional positional args given to call. The below example
|
||||
illustrates a function with two positional arguments where one is supplied by
|
||||
make and the other by call:
|
||||
|
||||
# function demonstrating 1 arg at make site, and 1 arg at call site
|
||||
def _foo(make_arg1, func_arg1):
|
||||
print(make_arg1 + " " + func_arg1 + "!")
|
||||
|
||||
For example:
|
||||
|
||||
hi_func = partial.make(_foo, "Hello")
|
||||
bye_func = partial.make(_foo, "Goodbye")
|
||||
partial.call(hi_func, "Jennifer")
|
||||
partial.call(hi_func, "Dave")
|
||||
partial.call(bye_func, "Jennifer")
|
||||
partial.call(bye_func, "Dave")
|
||||
|
||||
prints:
|
||||
|
||||
"Hello, Jennifer!"
|
||||
"Hello, Dave!"
|
||||
"Goodbye, Jennifer!"
|
||||
"Goodbye, Dave!"
|
||||
|
||||
The keyword args given to the function are the kwargs passed into make
|
||||
unioned with the keyword args given to call. In case of a conflict, the
|
||||
keyword args given to call take precedence. This allows you to set a default
|
||||
value for keyword arguments and override it at the call site.
|
||||
|
||||
Example with a make site arg, a call site arg, a make site kwarg and a
|
||||
call site kwarg:
|
||||
|
||||
def _foo(make_arg1, call_arg1, make_location, call_location):
|
||||
print(make_arg1 + " is from " + make_location + " and " +
|
||||
call_arg1 + " is from " + call_location + "!")
|
||||
|
||||
func = partial.make(_foo, "Ben", make_location="Hollywood")
|
||||
partial.call(func, "Jennifer", call_location="Denver")
|
||||
|
||||
Prints "Ben is from Hollywood and Jennifer is from Denver!".
|
||||
|
||||
partial.call(func, "Jennifer", make_location="LA", call_location="Denver")
|
||||
|
||||
Prints "Ben is from LA and Jennifer is from Denver!".
|
||||
|
||||
Note that keyword args may not overlap with positional args, regardless of
|
||||
whether they are given during the make or call step. For instance, you can't
|
||||
do:
|
||||
|
||||
def foo(x):
|
||||
pass
|
||||
|
||||
func = partial.make(foo, 1)
|
||||
partial.call(func, x=2)
|
||||
|
||||
|
||||
Args:
|
||||
func: The function to be called.
|
||||
*args: Positional arguments to be passed to function.
|
||||
**kwargs: Keyword arguments to be passed to function. Note that these can
|
||||
be overridden at the call sites.
|
||||
|
||||
Returns:
|
||||
A new `partial` that can be called using `call`
|
||||
"""
|
||||
return struct(function=func, args=args, kwargs=kwargs)
|
||||
|
||||
partial = struct(
|
||||
make=_make,
|
||||
call=_call,
|
||||
)
|
|
@ -1,5 +1,6 @@
|
|||
load(":collections_tests.bzl", "collections_test_suite")
|
||||
load(":dicts_tests.bzl", "dicts_test_suite")
|
||||
load(":partial_tests.bzl", "partial_test_suite")
|
||||
load(":paths_tests.bzl", "paths_test_suite")
|
||||
load(":selects_tests.bzl", "selects_test_suite")
|
||||
load(":sets_tests.bzl", "sets_test_suite")
|
||||
|
@ -13,6 +14,8 @@ collections_test_suite()
|
|||
|
||||
dicts_test_suite()
|
||||
|
||||
partial_test_suite()
|
||||
|
||||
paths_test_suite()
|
||||
|
||||
selects_test_suite()
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
# Copyright 2018 The Bazel Authors. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Unit tests for partial.bzl."""
|
||||
|
||||
load("//:lib.bzl", "partial", "asserts", "unittest")
|
||||
|
||||
|
||||
def _make_noargs_nokwargs():
|
||||
"""Test utility for no args no kwargs case"""
|
||||
return 1
|
||||
|
||||
def _make_args_nokwargs(arg1, arg2, arg3):
|
||||
"""Test utility for args no kwargs case"""
|
||||
return arg1 + arg2 + arg3
|
||||
|
||||
def _make_args_kwargs(arg1, arg2, arg3, **kwargs):
|
||||
"""Test utility for args and kwargs case"""
|
||||
return arg1 + arg2 + arg3 + kwargs["x"] + kwargs["y"]
|
||||
|
||||
def _call_noargs_nokwargs(call_arg1):
|
||||
"""Test utility no args no kwargs case where values passed from call site"""
|
||||
return call_arg1;
|
||||
|
||||
def _call_args_nokwargs(func_arg1, call_arg1):
|
||||
"""Test utility for args no kwargs case where values passed from call site"""
|
||||
return func_arg1 + call_arg1;
|
||||
|
||||
def _call_args_kwargs(func_arg1, call_arg1, func_mult, call_mult):
|
||||
"""Test utility for args and kwargs case where values passed from call site"""
|
||||
return (func_arg1 + call_arg1) * func_mult * call_mult;
|
||||
|
||||
def _make_call_test(ctx):
|
||||
"""Unit tests for partial.make and partial.call."""
|
||||
env = unittest.begin(ctx)
|
||||
|
||||
# Test cases where there are no args (or kwargs) at the make site, only
|
||||
# at the call site.
|
||||
foo = partial.make(_make_noargs_nokwargs)
|
||||
asserts.equals(env, 1, partial.call(foo))
|
||||
|
||||
foo = partial.make(_make_args_nokwargs)
|
||||
asserts.equals(env, 6, partial.call(foo, 1, 2, 3))
|
||||
|
||||
foo = partial.make(_make_args_kwargs)
|
||||
asserts.equals(env, 15, partial.call(foo, 1, 2, 3, x=4, y=5))
|
||||
|
||||
# Test cases where there are args (and/or kwargs) at the make site and the
|
||||
# call site.
|
||||
foo = partial.make(_call_noargs_nokwargs, 100)
|
||||
asserts.equals(env, 100, partial.call(foo))
|
||||
|
||||
foo = partial.make(_call_args_nokwargs, 100)
|
||||
asserts.equals(env, 112, partial.call(foo, 12))
|
||||
|
||||
foo = partial.make(_call_args_kwargs, 100, func_mult=10)
|
||||
asserts.equals(env, 2240, partial.call(foo, 12, call_mult=2))
|
||||
|
||||
# Test case where there are args and kwargs ath the make site, and the call
|
||||
# site overrides some make site args.
|
||||
foo = partial.make(_call_args_kwargs, 100, func_mult=10)
|
||||
asserts.equals(env, 1120, partial.call(foo, 12, func_mult=5, call_mult=2))
|
||||
|
||||
unittest.end(env)
|
||||
|
||||
make_call_test = unittest.make(_make_call_test)
|
||||
|
||||
|
||||
def partial_test_suite():
|
||||
"""Creates the test targets and test suite for partial.bzl tests."""
|
||||
unittest.suite(
|
||||
"partial_tests",
|
||||
make_call_test,
|
||||
)
|
Loading…
Reference in New Issue