{"record":{"id":"1455bf6e5c763777","repo":"pandas-dev/pandas","slug":"keyword-error-in-function-call-node-func-id","errorCode":null,"errorMessage":"keyword error in function call '{node.func.id}'","messagePattern":"keyword error in function call '(.+?)'","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"pandas/core/computation/expr.py","lineNumber":708,"sourceCode":"\n        if isinstance(res, FuncNode):\n            new_args = [self.visit(arg) for arg in node.args]\n\n            if node.keywords:\n                raise TypeError(\n                    f'Function \"{res.name}\" does not support keyword arguments'\n                )\n\n            return res(*new_args)\n\n        else:\n            new_args = [self.visit(arg)(self.env) for arg in node.args]\n\n            for key in node.keywords:\n                if not isinstance(key, ast.keyword):\n                    # error: Item \"Attribute\" of \"Attribute | Name\" has no\n                    # attribute \"id\"\n                    raise ValueError(\n                        f\"keyword error in function call '{node.func.id}'\"  # type: ignore[union-attr]\n                    )\n\n                if key.arg:\n                    kwargs[key.arg] = self.visit(key.value)(self.env)\n\n            name = self.env.add_tmp(res(*new_args, **kwargs))\n            return self.term_type(name=name, env=self.env)\n\n    def translate_In(self, op):\n        return op\n\n    def visit_Compare(self, node, **kwargs):\n        ops = node.ops\n        comps = node.comparators\n\n        # base case: we have something like a CMP b\n        if len(comps) == 1:","sourceCodeStart":690,"sourceCodeEnd":726,"githubUrl":"https://github.com/pandas-dev/pandas/blob/71959b8cb9b2459c16e14b34f28b178ccfe14735/pandas/core/computation/expr.py#L690-L726","documentation":"In the non-FuncNode branch of visit_Call (expr.py:701), each element of node.keywords is expected to be an ast.keyword node. The check at expr.py:705 is defensive: for any syntactically valid Python call, Python's parser always produces ast.keyword entries, so reaching the raise indicates either hand-built/malformed AST or a preparser that injected non-keyword nodes. The message names node.func.id to help locate the offending call.","triggerScenarios":"Effectively unreachable from normal string input. Surfaces only with manually constructed ast.Call nodes whose keywords list contains non-ast.keyword items, or with a buggy preparser that mangles the keyword list.","commonSituations":"Internal tooling or third-party libraries that build AST and feed it to pandas. Debugging custom visitors. Note: the surrounding code also references an uninitialized 'kwargs' dict (expr.py:713), so this region is best treated as internal/defensive.","solutions":["Avoid constructing ast.Call nodes manually; pass string expressions instead.","If you must build AST, ensure every keywords entry is an ast.keyword.","Simplify the expression to a plain positional call and report the edge case upstream."],"exampleFix":"// before\n# hand-built ast.Call with a non-keyword entry in node.keywords\n// after\n# pass a plain string expression so Python's parser builds correct AST:\ndf.eval('foo(a, b=1)')","handlingStrategy":"validation","validationCode":"import ast\n\ndef validate_keywords_wellformed(expr: str) -> None:\n    for node in ast.walk(ast.parse(expr, mode='eval')):\n        if isinstance(node, ast.Call):\n            for kw in node.keywords:\n                if not isinstance(kw, ast.keyword):\n                    raise ValueError(\n                        f'malformed keyword node in call {ast.dump(node)}'\n                    )\n\n# this guards only hand-built AST; for string input Python's parser guarantees this\nvalidate_keywords_wellformed(expr)","typeGuard":"import ast\n\ndef call_keywords_wellformed(expr: str) -> bool:\n    return all(\n        isinstance(kw, ast.keyword)\n        for n in ast.walk(ast.parse(expr, mode='eval'))\n        if isinstance(n, ast.Call) for kw in n.keywords\n    )","tryCatchPattern":"try:\n    df.eval(expr)\nexcept ValueError as e:\n    if 'keyword error in function call' in str(e):\n        # simplify the call to positional args and retry\n        df.eval('foo(a, b)')\n    raise","preventionTips":["Pass string expressions rather than hand-built AST to pandas eval.","Avoid custom preparsers that mutate node.keywords structure.","If you must build AST, ensure every keywords entry is ast.keyword."],"tags":["pandas","eval","functions","ast","internal","defensive"],"analyzedSha":"71959b8cb9b2459c16e14b34f28b178ccfe14735","analyzedAt":"2026-08-07T01:30:20.476Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}