Merge pull request #781 from zackmdavis/kwonly_connect
keyword-only arguments
This commit is contained in:
commit
73a29fd377
@ -441,6 +441,41 @@ Parameters may have the following keywords in front of them:
|
||||
=> (zig-zag-sum 1 2 3 4 5 6)
|
||||
-3
|
||||
|
||||
&kwonly
|
||||
.. versionadded:: 0.12.0
|
||||
|
||||
Parameters that can only be called as keywords. Mandatory
|
||||
keyword-only arguments are declared with the argument's name;
|
||||
optional keyword-only arguments are declared as a two-element list
|
||||
containing the argument name followed by the default value (as
|
||||
with `&optional` above).
|
||||
|
||||
.. code-block:: clj
|
||||
|
||||
=> (defn compare [a b &kwonly keyfn [reverse false]]
|
||||
... (let [[result (keyfn a b)]]
|
||||
... (if (not reverse)
|
||||
... result
|
||||
... (- result))))
|
||||
=> (apply compare ["lisp" "python"]
|
||||
... {"keyfn" (fn [x y]
|
||||
... (reduce - (map (fn [s] (ord (first s))) [x y])))})
|
||||
-4
|
||||
=> (apply compare ["lisp" "python"]
|
||||
... {"keyfn" (fn [x y]
|
||||
... (reduce - (map (fn [s] (ord (first s))) [x y])))
|
||||
... "reverse" true})
|
||||
4
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
=> (compare "lisp" "python")
|
||||
Traceback (most recent call last):
|
||||
File "<input>", line 1, in <module>
|
||||
TypeError: compare() missing 1 required keyword-only argument: 'keyfn'
|
||||
|
||||
Availability: Python 3.
|
||||
|
||||
.. _defn-alias / defun-alias:
|
||||
|
||||
defn-alias / defun-alias
|
||||
|
@ -485,11 +485,13 @@ class HyASTCompiler(object):
|
||||
|
||||
def _parse_lambda_list(self, exprs):
|
||||
""" Return FunctionDef parameter values from lambda list."""
|
||||
ll_keywords = ("&rest", "&optional", "&key", "&kwargs")
|
||||
ll_keywords = ("&rest", "&optional", "&key", "&kwonly", "&kwargs")
|
||||
ret = Result()
|
||||
args = []
|
||||
defaults = []
|
||||
varargs = None
|
||||
kwonlyargs = []
|
||||
kwonlydefaults = []
|
||||
kwargs = None
|
||||
lambda_keyword = None
|
||||
|
||||
@ -506,6 +508,8 @@ class HyASTCompiler(object):
|
||||
lambda_keyword = expr
|
||||
elif expr == "&key":
|
||||
lambda_keyword = expr
|
||||
elif expr == "&kwonly":
|
||||
lambda_keyword = expr
|
||||
elif expr == "&kwargs":
|
||||
lambda_keyword = expr
|
||||
else:
|
||||
@ -554,6 +558,24 @@ class HyASTCompiler(object):
|
||||
args.append(k)
|
||||
ret += self.compile(v)
|
||||
defaults.append(ret.force_expr)
|
||||
elif lambda_keyword == "&kwonly":
|
||||
if not PY3:
|
||||
raise HyTypeError(expr,
|
||||
"keyword-only arguments are only "
|
||||
"available under Python 3")
|
||||
if isinstance(expr, HyList):
|
||||
if len(expr) != 2:
|
||||
raise HyTypeError(expr,
|
||||
"keyword-only args should be bare "
|
||||
"names or 2-item lists")
|
||||
k, v = expr
|
||||
kwonlyargs.append(k)
|
||||
ret += self.compile(v)
|
||||
kwonlydefaults.append(ret.force_expr)
|
||||
else:
|
||||
k = expr
|
||||
kwonlyargs.append(k)
|
||||
kwonlydefaults.append(None)
|
||||
elif lambda_keyword == "&kwargs":
|
||||
if kwargs:
|
||||
raise HyTypeError(expr,
|
||||
@ -561,7 +583,7 @@ class HyASTCompiler(object):
|
||||
"&kwargs argument")
|
||||
kwargs = str(expr)
|
||||
|
||||
return ret, args, defaults, varargs, kwargs
|
||||
return ret, args, defaults, varargs, kwonlyargs, kwonlydefaults, kwargs
|
||||
|
||||
def _storeize(self, name, func=None):
|
||||
"""Return a new `name` object with an ast.Store() context"""
|
||||
@ -2026,7 +2048,9 @@ class HyASTCompiler(object):
|
||||
if not isinstance(arglist, HyList):
|
||||
raise HyTypeError(expression,
|
||||
"First argument to (fn) must be a list")
|
||||
ret, args, defaults, stararg, kwargs = self._parse_lambda_list(arglist)
|
||||
|
||||
(ret, args, defaults, stararg,
|
||||
kwonlyargs, kwonlydefaults, kwargs) = self._parse_lambda_list(arglist)
|
||||
for i, arg in enumerate(args):
|
||||
if isinstance(arg, HyList):
|
||||
# Destructuring argument
|
||||
@ -2049,6 +2073,11 @@ class HyASTCompiler(object):
|
||||
lineno=x.start_line,
|
||||
col_offset=x.start_column) for x in args]
|
||||
|
||||
kwonlyargs = [ast.arg(arg=ast_str(x), annotation=None,
|
||||
lineno=x.start_line,
|
||||
col_offset=x.start_column)
|
||||
for x in kwonlyargs]
|
||||
|
||||
# XXX: Beware. Beware. This wasn't put into the parse lambda
|
||||
# list because it's really just an internal parsing thing.
|
||||
|
||||
@ -2065,12 +2094,18 @@ class HyASTCompiler(object):
|
||||
lineno=x.start_line,
|
||||
col_offset=x.start_column) for x in args]
|
||||
|
||||
if PY3:
|
||||
kwonlyargs = [ast.Name(arg=ast_str(x), id=ast_str(x),
|
||||
ctx=ast.Param(), lineno=x.start_line,
|
||||
col_offset=x.start_column)
|
||||
for x in kwonlyargs]
|
||||
|
||||
args = ast.arguments(
|
||||
args=args,
|
||||
vararg=stararg,
|
||||
kwarg=kwargs,
|
||||
kwonlyargs=[],
|
||||
kw_defaults=[],
|
||||
kwonlyargs=kwonlyargs,
|
||||
kw_defaults=kwonlydefaults,
|
||||
defaults=defaults)
|
||||
|
||||
body = self._compile_branch(expression)
|
||||
|
@ -54,11 +54,13 @@ def cant_compile(expr):
|
||||
# error, otherwise it's a compiler bug.
|
||||
assert isinstance(e.expression, HyObject)
|
||||
assert e.message
|
||||
return e
|
||||
except HyCompileError as e:
|
||||
# Anything that can't be compiled should raise a user friendly
|
||||
# error, otherwise it's a compiler bug.
|
||||
assert isinstance(e.exception, HyTypeError)
|
||||
assert e.traceback
|
||||
return e
|
||||
|
||||
|
||||
def test_ast_bad_type():
|
||||
@ -441,10 +443,31 @@ def test_lambda_list_keywords_kwargs():
|
||||
cant_compile("(fn (x &kwargs xs &kwargs ys) (list x xs ys))")
|
||||
|
||||
|
||||
def test_lambda_list_keywords_kwonly():
|
||||
"""Ensure we can compile functions with &kwonly if we're on Python
|
||||
3, or fail with an informative message on Python 2."""
|
||||
kwonly_demo = "(fn [&kwonly a [b 2]] (print a b))"
|
||||
if PY3:
|
||||
code = can_compile(kwonly_demo)
|
||||
for i, kwonlyarg_name in enumerate(('a', 'b')):
|
||||
assert kwonlyarg_name == code.body[0].args.kwonlyargs[i].arg
|
||||
assert code.body[0].args.kw_defaults[0] is None
|
||||
assert code.body[0].args.kw_defaults[1].n == 2
|
||||
else:
|
||||
exception = cant_compile(kwonly_demo)
|
||||
assert isinstance(exception, HyTypeError)
|
||||
message, = exception.args
|
||||
assert message == ("keyword-only arguments are only "
|
||||
"available under Python 3")
|
||||
|
||||
|
||||
def test_lambda_list_keywords_mixed():
|
||||
""" Ensure we can mix them up."""
|
||||
can_compile("(fn (x &rest xs &kwargs kw) (list x xs kw))")
|
||||
cant_compile("(fn (x &rest xs &fasfkey {bar \"baz\"}))")
|
||||
if PY3:
|
||||
can_compile("(fn [x &rest xs &kwargs kwxs &kwonly kwoxs]"
|
||||
" (list x xs kwxs kwoxs))")
|
||||
|
||||
|
||||
def test_ast_unicode_strings():
|
||||
|
@ -10,3 +10,29 @@
|
||||
(try (raise ValueError :from NameError)
|
||||
(except [e [ValueError]]
|
||||
(assert (= (type (. e __cause__)) NameError)))))
|
||||
|
||||
|
||||
(defn test-kwonly []
|
||||
"NATIVE: test keyword-only arguments"
|
||||
;; keyword-only with default works
|
||||
(let [[kwonly-foo-default-false (fn [&kwonly [foo false]] foo)]]
|
||||
(assert (= (apply kwonly-foo-default-false) false))
|
||||
(assert (= (apply kwonly-foo-default-false [] {"foo" true}) true)))
|
||||
;; keyword-only without default ...
|
||||
(let [[kwonly-foo-no-default (fn [&kwonly foo] foo)]
|
||||
[attempt-to-omit-default (try
|
||||
(kwonly-foo-no-default)
|
||||
(catch [e [Exception]] e))]]
|
||||
;; works
|
||||
(assert (= (apply kwonly-foo-no-default [] {"foo" "quux"}) "quux"))
|
||||
;; raises TypeError with appropriate message if not supplied
|
||||
(assert (isinstance attempt-to-omit-default TypeError))
|
||||
(assert (in "missing 1 required keyword-only argument: 'foo'"
|
||||
(. attempt-to-omit-default args [0]))))
|
||||
;; keyword-only with other arg types works
|
||||
(let [[function-of-various-args
|
||||
(fn [a b &rest args &kwonly foo &kwargs kwargs]
|
||||
(, a b args foo kwargs))]]
|
||||
(assert (= (apply function-of-various-args
|
||||
[1 2 3 4] {"foo" 5 "bar" 6 "quux" 7})
|
||||
(, 1 2 (, 3 4) 5 {"bar" 6 "quux" 7})))))
|
||||
|
Loading…
x
Reference in New Issue
Block a user