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)
|
=> (zig-zag-sum 1 2 3 4 5 6)
|
||||||
-3
|
-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:
|
||||||
|
|
||||||
defn-alias / defun-alias
|
defn-alias / defun-alias
|
||||||
|
@ -485,11 +485,13 @@ class HyASTCompiler(object):
|
|||||||
|
|
||||||
def _parse_lambda_list(self, exprs):
|
def _parse_lambda_list(self, exprs):
|
||||||
""" Return FunctionDef parameter values from lambda list."""
|
""" Return FunctionDef parameter values from lambda list."""
|
||||||
ll_keywords = ("&rest", "&optional", "&key", "&kwargs")
|
ll_keywords = ("&rest", "&optional", "&key", "&kwonly", "&kwargs")
|
||||||
ret = Result()
|
ret = Result()
|
||||||
args = []
|
args = []
|
||||||
defaults = []
|
defaults = []
|
||||||
varargs = None
|
varargs = None
|
||||||
|
kwonlyargs = []
|
||||||
|
kwonlydefaults = []
|
||||||
kwargs = None
|
kwargs = None
|
||||||
lambda_keyword = None
|
lambda_keyword = None
|
||||||
|
|
||||||
@ -506,6 +508,8 @@ class HyASTCompiler(object):
|
|||||||
lambda_keyword = expr
|
lambda_keyword = expr
|
||||||
elif expr == "&key":
|
elif expr == "&key":
|
||||||
lambda_keyword = expr
|
lambda_keyword = expr
|
||||||
|
elif expr == "&kwonly":
|
||||||
|
lambda_keyword = expr
|
||||||
elif expr == "&kwargs":
|
elif expr == "&kwargs":
|
||||||
lambda_keyword = expr
|
lambda_keyword = expr
|
||||||
else:
|
else:
|
||||||
@ -554,6 +558,24 @@ class HyASTCompiler(object):
|
|||||||
args.append(k)
|
args.append(k)
|
||||||
ret += self.compile(v)
|
ret += self.compile(v)
|
||||||
defaults.append(ret.force_expr)
|
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":
|
elif lambda_keyword == "&kwargs":
|
||||||
if kwargs:
|
if kwargs:
|
||||||
raise HyTypeError(expr,
|
raise HyTypeError(expr,
|
||||||
@ -561,7 +583,7 @@ class HyASTCompiler(object):
|
|||||||
"&kwargs argument")
|
"&kwargs argument")
|
||||||
kwargs = str(expr)
|
kwargs = str(expr)
|
||||||
|
|
||||||
return ret, args, defaults, varargs, kwargs
|
return ret, args, defaults, varargs, kwonlyargs, kwonlydefaults, kwargs
|
||||||
|
|
||||||
def _storeize(self, name, func=None):
|
def _storeize(self, name, func=None):
|
||||||
"""Return a new `name` object with an ast.Store() context"""
|
"""Return a new `name` object with an ast.Store() context"""
|
||||||
@ -2026,7 +2048,9 @@ class HyASTCompiler(object):
|
|||||||
if not isinstance(arglist, HyList):
|
if not isinstance(arglist, HyList):
|
||||||
raise HyTypeError(expression,
|
raise HyTypeError(expression,
|
||||||
"First argument to (fn) must be a list")
|
"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):
|
for i, arg in enumerate(args):
|
||||||
if isinstance(arg, HyList):
|
if isinstance(arg, HyList):
|
||||||
# Destructuring argument
|
# Destructuring argument
|
||||||
@ -2049,6 +2073,11 @@ class HyASTCompiler(object):
|
|||||||
lineno=x.start_line,
|
lineno=x.start_line,
|
||||||
col_offset=x.start_column) for x in args]
|
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
|
# XXX: Beware. Beware. This wasn't put into the parse lambda
|
||||||
# list because it's really just an internal parsing thing.
|
# list because it's really just an internal parsing thing.
|
||||||
|
|
||||||
@ -2065,12 +2094,18 @@ class HyASTCompiler(object):
|
|||||||
lineno=x.start_line,
|
lineno=x.start_line,
|
||||||
col_offset=x.start_column) for x in args]
|
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 = ast.arguments(
|
||||||
args=args,
|
args=args,
|
||||||
vararg=stararg,
|
vararg=stararg,
|
||||||
kwarg=kwargs,
|
kwarg=kwargs,
|
||||||
kwonlyargs=[],
|
kwonlyargs=kwonlyargs,
|
||||||
kw_defaults=[],
|
kw_defaults=kwonlydefaults,
|
||||||
defaults=defaults)
|
defaults=defaults)
|
||||||
|
|
||||||
body = self._compile_branch(expression)
|
body = self._compile_branch(expression)
|
||||||
|
@ -54,11 +54,13 @@ def cant_compile(expr):
|
|||||||
# error, otherwise it's a compiler bug.
|
# error, otherwise it's a compiler bug.
|
||||||
assert isinstance(e.expression, HyObject)
|
assert isinstance(e.expression, HyObject)
|
||||||
assert e.message
|
assert e.message
|
||||||
|
return e
|
||||||
except HyCompileError as e:
|
except HyCompileError as e:
|
||||||
# Anything that can't be compiled should raise a user friendly
|
# Anything that can't be compiled should raise a user friendly
|
||||||
# error, otherwise it's a compiler bug.
|
# error, otherwise it's a compiler bug.
|
||||||
assert isinstance(e.exception, HyTypeError)
|
assert isinstance(e.exception, HyTypeError)
|
||||||
assert e.traceback
|
assert e.traceback
|
||||||
|
return e
|
||||||
|
|
||||||
|
|
||||||
def test_ast_bad_type():
|
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))")
|
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():
|
def test_lambda_list_keywords_mixed():
|
||||||
""" Ensure we can mix them up."""
|
""" Ensure we can mix them up."""
|
||||||
can_compile("(fn (x &rest xs &kwargs kw) (list x xs kw))")
|
can_compile("(fn (x &rest xs &kwargs kw) (list x xs kw))")
|
||||||
cant_compile("(fn (x &rest xs &fasfkey {bar \"baz\"}))")
|
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():
|
def test_ast_unicode_strings():
|
||||||
|
@ -10,3 +10,29 @@
|
|||||||
(try (raise ValueError :from NameError)
|
(try (raise ValueError :from NameError)
|
||||||
(except [e [ValueError]]
|
(except [e [ValueError]]
|
||||||
(assert (= (type (. e __cause__)) NameError)))))
|
(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