Reputation: 14086
I'm trying to use ast
to dynamically create a function with a keyword-only argument that has a default value. However, the resulting function still requires the argument and will raise TypeError
if it's not passed.
This is the code creating the function f
:
import ast
import types
module_ast = ast.Module(
body=[
ast.FunctionDef(
name='f',
args=ast.arguments(
args=[],
vararg=None,
kwarg=None,
defaults=[],
kwonlyargs=[
ast.arg(
arg='x',
lineno=1,
col_offset=0,
),
],
kw_defaults=[
ast.Num(n=42, lineno=1, col_offset=0),
],
posonlyargs=[],
),
body=[ast.Return(
value=ast.Name(id='x', ctx=ast.Load(), lineno=1, col_offset=0),
lineno=1,
col_offset=0,
)],
decorator_list=[],
lineno=1,
col_offset=0,
)
],
type_ignores=[],
)
module_code = compile(module_ast, '<ast>', 'exec')
# This is the part that I'm suspicious of
f_code = next(c for c in module_code.co_consts if isinstance(c, types.CodeType))
f = types.FunctionType(
f_code,
{}
)
If I print(ast.unparse(module_ast))
, I get what I expect:
def f(*, x=42):
return x
Calling f(x=100)
returns 100
as expected, but calling f()
produces:
TypeError: f() missing 1 required keyword-only argument: 'x'
I suspect that the problem is in way the I'm turning the AST into a function. I saw the approach in another question here (which I unfortunately do not have a link to). It looks a bit dodgy, but I'm not sure how else to do it.
Upvotes: 1
Views: 525
Reputation: 280857
A function's default argument values aren't part of the function's code object. They can't be, because the defaults are created at function definition time, not bytecode compilation time. Default argument values are stored in __defaults__
for non-keyword-only arguments and __kwdefaults__
for keyword-only arguments. When you extract f_code
from module_code
, you're not getting any information about defaults.
Execute the function definition, then retrieve the actual function object:
namespace = {}
exec(module_code, namespace)
function = namespace['f']
Upvotes: 3